mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
Merge branch 'permalink' of github.com:coralproject/talk into permalink
* 'permalink' of github.com:coralproject/talk: (42 commits) [next] Support server side jest testing (#1747) Update snapshots Add comments Remove precss Move react-responsive to dev deps Remove comment Mobile first approach Support standard css variables, dynamically set spacing-unit Add docs Fully implement Flex and MatchMedia Responsive Components <3 fix: linting fix: adjusted pageInfo Remove obsoloe snapshot Move jsdom to dev deps Mark comments as always returning a value Add comment Fix unit tests Translate, concept for translation and id strings Add aria props ...
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
displayName: "client",
|
||||
rootDir: "../",
|
||||
roots: ["<rootDir>/src/core/client"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFiles: [
|
||||
"<rootDir>/config/polyfills.js",
|
||||
"<rootDir>/src/core/client/test/setup.ts",
|
||||
],
|
||||
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
|
||||
testEnvironment: "node",
|
||||
testURL: "http://localhost",
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|mjs|ts|tsx|css|json|ftl)$)":
|
||||
"<rootDir>/config/jest/fileTransform.js",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$",
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^talk-admin/(.*)$": "<rootDir>/src/core/client/admin/$1",
|
||||
"^talk-ui/(.*)$": "<rootDir>/src/core/client/ui/$1",
|
||||
"^talk-stream/(.*)$": "<rootDir>/src/core/client/stream/$1",
|
||||
"^talk-framework/(.*)$": "<rootDir>/src/core/client/framework/$1",
|
||||
"^talk-common/(.*)$": "<rootDir>/src/core/common/$1",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
snapshotSerializers: ["enzyme-to-json/serializer"],
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
useBabelrc: true,
|
||||
tsConfigFile: path.resolve(__dirname, "tsconfig.jest.json"),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
displayName: "server",
|
||||
rootDir: "../",
|
||||
roots: ["<rootDir>/src"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
|
||||
testPathIgnorePatterns: ["/node_modules/", "/client/"],
|
||||
testEnvironment: "node",
|
||||
testURL: "http://localhost",
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest",
|
||||
},
|
||||
moduleNameMapper: {
|
||||
"^talk-server/(.*)$": "<rootDir>/src/core/server/$1",
|
||||
"^talk-common/(.*)$": "<rootDir>/src/core/common/$1",
|
||||
},
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
};
|
||||
+3
-40
@@ -1,43 +1,6 @@
|
||||
const paths = require("./paths");
|
||||
|
||||
module.exports = {
|
||||
rootDir: "../",
|
||||
roots: ["<rootDir>/src", "<rootDir>/scripts"],
|
||||
collectCoverageFrom: ["src/**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFiles: ["<rootDir>/config/polyfills.js"],
|
||||
testMatch: [
|
||||
"**/__tests__/**/*.{js,jsx,mjs,ts,tsx}",
|
||||
"**/*.spec.{js,jsx,mjs,ts,tsx}",
|
||||
],
|
||||
testEnvironment: "node",
|
||||
testURL: "http://localhost",
|
||||
transform: {
|
||||
"^.+\\.(js|jsx|mjs|ts|tsx)$": "<rootDir>/node_modules/ts-jest",
|
||||
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
|
||||
"^(?!.*\\.(js|jsx|mjs|css|json|ftl)$)":
|
||||
"<rootDir>/config/jest/fileTransform.js",
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|ts|tsx)$",
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^talk-admin/(.*)$": "<rootDir>/src/core/client/admin/$1",
|
||||
"^talk-ui/(.*)$": "<rootDir>/src/core/client/ui/$1",
|
||||
"^talk-stream/(.*)$": "<rootDir>/src/core/client/stream/$1",
|
||||
"^talk-framework/(.*)$": "<rootDir>/src/core/client/framework/$1",
|
||||
"^talk-common/(.*)$": "<rootDir>/src/core/common/$1",
|
||||
"^talk-server/(.*)$": "<rootDir>/src/core/server/$1",
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
"web.js",
|
||||
"js",
|
||||
"json",
|
||||
"web.jsx",
|
||||
"jsx",
|
||||
"node",
|
||||
"mjs",
|
||||
"ts",
|
||||
"tsx",
|
||||
projects: [
|
||||
"<rootDir>/jest-client.config.js",
|
||||
"<rootDir>/jest-server.config.js",
|
||||
],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Copyright https://github.com/Connormiha/jest-css-modules-transform/graphs/contributors
|
||||
// This oututs `module.exports = cssObject` instead of `module.exports = { default: cssObject }`;
|
||||
|
||||
const { sep: pathSep, resolve } = require("path");
|
||||
const { sep: pathSep, resolve, basename, extname } = require("path");
|
||||
const postcss = require("postcss");
|
||||
const postcssNested = postcss([require("postcss-nested")]);
|
||||
|
||||
@@ -88,7 +88,18 @@ module.exports = {
|
||||
process(src, path, config) {
|
||||
const filename = path.slice(path.lastIndexOf(pathSep) + 1);
|
||||
const textCSS = postcssNested.process(src).css;
|
||||
const exprt = JSON.stringify(getCSSSelectors(textCSS, path));
|
||||
const selectors = getCSSSelectors(textCSS, path);
|
||||
const component = basename(path, extname(path));
|
||||
|
||||
// Prefix class names with component name.
|
||||
Object.keys(selectors).forEach(k => {
|
||||
selectors[k] = selectors[k]
|
||||
.split(/\s+/)
|
||||
.map(v => `${component}-${v}`)
|
||||
.join(" ");
|
||||
});
|
||||
|
||||
const exprt = JSON.stringify(selectors);
|
||||
return `module.exports = ${exprt}`;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -53,6 +53,7 @@ module.exports = {
|
||||
appTsconfig: resolveApp("src/core/client/tsconfig.json"),
|
||||
appLocales: resolveApp("src/locales"),
|
||||
appThemeVariables: resolveApp("src/core/client/ui/theme/variables.ts"),
|
||||
appThemeVariablesCSS: resolveApp("src/core/client/ui/theme/variables.css"),
|
||||
testsSetup: resolveApp("src/setupTests.js"),
|
||||
appNodeModules: resolveApp("node_modules"),
|
||||
|
||||
|
||||
@@ -1,27 +1,73 @@
|
||||
const precss = require("precss");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const fontMagician = require("postcss-font-magician");
|
||||
// Allow importing typescript files.
|
||||
require("ts-node/register");
|
||||
|
||||
const kebabCase = require("lodash/kebabCase");
|
||||
const mapKeys = require("lodash/mapKeys");
|
||||
const mapValues = require("lodash/mapValues");
|
||||
const pickBy = require("lodash/pickBy");
|
||||
const flat = require("flat");
|
||||
const flexbugsFixes = require("postcss-flexbugs-fixes");
|
||||
const paths = require("./paths");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const postcssFontMagician = require("postcss-font-magician");
|
||||
const postcssFlexbugsFixes = require("postcss-flexbugs-fixes");
|
||||
const postcssVariables = require("postcss-css-variables");
|
||||
const postcssPresetEnv = require("postcss-preset-env");
|
||||
const postcssNested = require("postcss-nested");
|
||||
const postcssImport = require("postcss-import");
|
||||
const postcssPrependImports = require("postcss-prepend-imports");
|
||||
const postcssAdvancedVariables = require("postcss-advanced-variables");
|
||||
|
||||
delete require.cache[paths.appThemeVariables];
|
||||
const variables = require(paths.appThemeVariables);
|
||||
const variables = require(paths.appThemeVariables).default;
|
||||
const flatKebabVariables = mapKeys(
|
||||
flat(variables, { delimiter: "-" }),
|
||||
mapValues(flat(variables, { delimiter: "-" }), v => v.toString()),
|
||||
(_, k) => kebabCase(k)
|
||||
);
|
||||
|
||||
// These are the default css standard variables.
|
||||
const cssVariables = pickBy(
|
||||
flatKebabVariables,
|
||||
(v, k) => !k.startsWith("breakpoints-")
|
||||
);
|
||||
|
||||
// These are sass style variables used in media queries.
|
||||
const mediaQueryVariables = mapValues(
|
||||
pickBy(flatKebabVariables, (v, k) => k.startsWith("breakpoints-")),
|
||||
// Add unit to breakpoints.
|
||||
// Add 1 to support mobile first approach where we start
|
||||
// with the smallest screen and gradually add styling for the
|
||||
// next bigger screen. This is realized using `min-width` without
|
||||
// ever using `max-width`.
|
||||
v => `${Number.parseInt(v) + 1}px`
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
// Necessary for external CSS imports to work
|
||||
// https://github.com/facebookincubator/create-react-app/issues/2677
|
||||
ident: "postcss",
|
||||
plugins: [
|
||||
precss({ variables: flatKebabVariables }),
|
||||
fontMagician(),
|
||||
flexbugsFixes,
|
||||
// This allows us to define dynamic css variables.
|
||||
postcssPrependImports({
|
||||
path: "",
|
||||
files: [paths.appThemeVariablesCSS],
|
||||
}),
|
||||
// Needed by above plugin.
|
||||
postcssImport(),
|
||||
// Support nesting.
|
||||
postcssNested(),
|
||||
// Sass style variables to be used in media queries.
|
||||
postcssAdvancedVariables({ variables: mediaQueryVariables }),
|
||||
// CSS standard variables for everything else.
|
||||
postcssVariables({
|
||||
variables: cssVariables,
|
||||
}),
|
||||
// Provides a modern CSS environment.
|
||||
postcssPresetEnv(),
|
||||
// Does all the font handling logic.
|
||||
postcssFontMagician(),
|
||||
// Fix known flexbox bugs.
|
||||
postcssFlexbugsFixes,
|
||||
// Vendor prefixing.
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
">1%",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"module": "es2015"
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -13,9 +13,14 @@ const config: Config = {
|
||||
"core/client/stream/**/*.ts",
|
||||
"core/client/stream/**/*.tsx",
|
||||
"core/client/stream/**/*.graphql",
|
||||
"core/client/server/**/*.graphql",
|
||||
"core/server/**/*.graphql",
|
||||
],
|
||||
ignore: [
|
||||
"core/**/*.d.ts",
|
||||
"core/**/*.graphql.ts",
|
||||
"**/test/**/*",
|
||||
"core/**/*.spec.*",
|
||||
],
|
||||
ignore: ["core/**/*.d.ts", "core/**/*.graphql.ts"],
|
||||
executor: new CommandExecutor("npm run compile:relay-stream", {
|
||||
runOnInit: true,
|
||||
}),
|
||||
|
||||
@@ -224,6 +224,11 @@ module.exports = {
|
||||
jsx: "preserve",
|
||||
noEmit: false,
|
||||
},
|
||||
|
||||
// Overwrites the behavior of `include` and `exclude` to only
|
||||
// include files that are actually being imported and which
|
||||
// are necessary to compile the bundle.
|
||||
onlyCompileBundledFiles: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Generated
+1075
-564
File diff suppressed because it is too large
Load Diff
+21
-2
@@ -57,6 +57,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.0.0-beta.49",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.0.0-beta.49",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0-beta.52",
|
||||
"@babel/polyfill": "7.0.0-beta.49",
|
||||
"@babel/preset-env": "7.0.0-beta.49",
|
||||
"@babel/preset-react": "7.0.0-beta.49",
|
||||
@@ -67,11 +68,14 @@
|
||||
"@types/convict": "^4.2.0",
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/enzyme": "^3.1.11",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.2",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/graphql": "^0.13.1",
|
||||
"@types/ioredis": "^3.2.8",
|
||||
"@types/jest": "^23.1.1",
|
||||
"@types/joi": "^13.0.8",
|
||||
"@types/jsdom": "^11.0.6",
|
||||
"@types/lodash": "^4.14.109",
|
||||
"@types/luxon": "^0.5.3",
|
||||
"@types/mongodb": "^3.0.19",
|
||||
@@ -80,12 +84,15 @@
|
||||
"@types/query-string": "^6.1.0",
|
||||
"@types/react-dom": "^16.0.6",
|
||||
"@types/react-relay": "github:coralproject/patched#types/react-relay",
|
||||
"@types/react-responsive": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.0.1",
|
||||
"@types/recompose": "^0.26.1",
|
||||
"@types/relay-runtime": "github:coralproject/patched#types/relay-runtime",
|
||||
"@types/sinon": "^5.0.1",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/ws": "^5.1.2",
|
||||
"autoprefixer": "^8.6.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-loader": "^8.0.0-beta",
|
||||
"babel-plugin-relay": "github:coralproject/patched#babel-plugin-relay",
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
@@ -99,6 +106,9 @@
|
||||
"css-loader": "^0.28.11",
|
||||
"docz": "^0.2.6",
|
||||
"docz-theme-default": "^0.2.10",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"final-form": "^4.8.1",
|
||||
"flat": "^4.0.0",
|
||||
@@ -109,12 +119,18 @@
|
||||
"graphql-playground-middleware-express": "^1.7.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^23.3.0",
|
||||
"jsdom": "^11.11.0",
|
||||
"loader-utils": "^1.1.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"postcss-advanced-variables": "^2.3.3",
|
||||
"postcss-css-variables": "^0.9.0",
|
||||
"postcss-flexbugs-fixes": "^3.3.1",
|
||||
"postcss-font-magician": "^2.2.1",
|
||||
"postcss-import": "^11.1.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"precss": "^3.1.2",
|
||||
"postcss-nested": "^3.0.0",
|
||||
"postcss-prepend-imports": "^1.0.1",
|
||||
"postcss-preset-env": "^5.2.1",
|
||||
"prettier": "^1.13.4",
|
||||
"query-string": "^6.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
@@ -123,14 +139,17 @@
|
||||
"react-dom": "^16.4.0",
|
||||
"react-final-form": "^3.6.0",
|
||||
"react-relay": "github:coralproject/patched#react-relay",
|
||||
"react-responsive": "^4.1.0",
|
||||
"react-test-renderer": "^16.4.1",
|
||||
"recompose": "^0.27.1",
|
||||
"relay-compiler": "github:coralproject/patched#relay-compiler",
|
||||
"relay-compiler-language-typescript": "github:coralproject/patched#relay-compiler-language-typescript",
|
||||
"relay-local-schema": "^0.7.0",
|
||||
"relay-runtime": "github:coralproject/patched#relay-runtime",
|
||||
"relay-test-utils": "github:coralproject/patched#relay-test-utils",
|
||||
"sinon": "^6.1.2",
|
||||
"style-loader": "^0.21.0",
|
||||
"ts-jest": "^22.4.6",
|
||||
"ts-jest": "^23.0.0",
|
||||
"ts-loader": "^4.3.1",
|
||||
"ts-node": "^6.1.0",
|
||||
"tsconfig-paths": "^3.4.0",
|
||||
|
||||
@@ -8,5 +8,8 @@ module.exports = {
|
||||
production: {
|
||||
plugins: [],
|
||||
},
|
||||
test: {
|
||||
plugins: ["@babel/transform-modules-commonjs"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { hoistStatics, InferableComponentEnhancer } from "recompose";
|
||||
import {
|
||||
hoistStatics,
|
||||
InferableComponentEnhancer,
|
||||
wrapDisplayName,
|
||||
} from "recompose";
|
||||
|
||||
import { TalkContext, TalkContextConsumer } from "./TalkContext";
|
||||
|
||||
@@ -12,11 +16,17 @@ function withContext<T>(
|
||||
propsCallback: (context: TalkContext) => T
|
||||
): InferableComponentEnhancer<T> {
|
||||
return hoistStatics<T>(
|
||||
<U extends T>(WrappedComponent: React.ComponentType<U>) => (props: any) => (
|
||||
<TalkContextConsumer>
|
||||
{context => <WrappedComponent {...props} {...propsCallback(context)} />}
|
||||
</TalkContextConsumer>
|
||||
)
|
||||
<U extends T>(WrappedComponent: React.ComponentType<U>) => {
|
||||
const Component: React.StatelessComponent<any> = props => (
|
||||
<TalkContextConsumer>
|
||||
{context => (
|
||||
<WrappedComponent {...props} {...propsCallback(context)} />
|
||||
)}
|
||||
</TalkContextConsumer>
|
||||
);
|
||||
Component.displayName = wrapDisplayName(WrappedComponent, "withContext");
|
||||
return Component;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const VALIDATION_REQUIRED = () => (
|
||||
);
|
||||
|
||||
export const VALIDATION_TOO_SHORT = () => (
|
||||
<Localized id="framework-validation-too-short">
|
||||
<Localized id="framework-validation-tooShort">
|
||||
<span>This field is too short.</span>
|
||||
</Localized>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import React, { Component } from "react";
|
||||
import { QueryRenderer } from "react-relay";
|
||||
import { CacheConfig, GraphQLTaggedNode, RerunParam } from "relay-runtime";
|
||||
import {
|
||||
QueryRenderer,
|
||||
QueryRendererProps as QueryRendererPropsOrig,
|
||||
} from "react-relay";
|
||||
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { TalkContextConsumer } from "../bootstrap/TalkContext";
|
||||
|
||||
// Taken from relay types and added Generic support for Variables and Response
|
||||
export interface QueryRendererProps<V, R> {
|
||||
cacheConfig?: CacheConfig;
|
||||
query?: GraphQLTaggedNode | null;
|
||||
render(readyState: ReadyState<R>): React.ReactElement<any> | undefined | null;
|
||||
variables: V;
|
||||
rerunParamExperimental?: RerunParam;
|
||||
}
|
||||
|
||||
// Taken from relay types and added Generic support for Variables and Response
|
||||
export interface ReadyState<R> {
|
||||
error: Error | undefined | null;
|
||||
props: R | undefined | null;
|
||||
retry?(): void;
|
||||
}
|
||||
// Omit environment as we are passing this from the context.
|
||||
export type QueryRendererProps<V, R> = Omit<
|
||||
QueryRendererPropsOrig<V, R>,
|
||||
"environment"
|
||||
>;
|
||||
|
||||
/**
|
||||
* TalkQueryRenderer is a wrappper around Relay's `QueryRenderer`.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { compose, hoistStatics, InferableComponentEnhancer } from "recompose";
|
||||
import {
|
||||
compose,
|
||||
hoistStatics,
|
||||
InferableComponentEnhancer,
|
||||
wrapDisplayName,
|
||||
} from "recompose";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import { withContext } from "../bootstrap";
|
||||
@@ -19,6 +24,11 @@ function createMutationContainer<T extends string, I, R>(
|
||||
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
|
||||
hoistStatics((WrappedComponent: React.ComponentType<any>) => {
|
||||
class CreateMutationContainer extends React.Component<any> {
|
||||
public static displayName = wrapDisplayName(
|
||||
WrappedComponent,
|
||||
"createMutationContainer"
|
||||
);
|
||||
|
||||
private commit = (input: I) => {
|
||||
return commit(this.props.relayEnvironment, input);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ export { default as QueryRenderer } from "./QueryRenderer";
|
||||
export * from "./QueryRenderer";
|
||||
export { default as createMutationContainer } from "./createMutationContainer";
|
||||
export { default as createAndRetain } from "./createAndRetain";
|
||||
export { default as wrapFetchWithLogger } from "./wrapFetchWithLogger";
|
||||
export {
|
||||
commitMutationPromise,
|
||||
commitMutationPromiseNormalized,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { InferableComponentEnhancerWithProps } from "recompose";
|
||||
* from Relay.
|
||||
*/
|
||||
export default <T>(
|
||||
fragmentSpec: GraphQLTaggedNode
|
||||
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode }
|
||||
): InferableComponentEnhancerWithProps<T, { [P in keyof T]: any }> => (
|
||||
component: React.ComponentType<any>
|
||||
) => createFragmentContainer(component, fragmentSpec) as any;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import * as React from "react";
|
||||
import { compose, hoistStatics, InferableComponentEnhancer } from "recompose";
|
||||
import { CSelector, CSnapshot, Environment } from "relay-runtime";
|
||||
import {
|
||||
CSelector,
|
||||
CSnapshot,
|
||||
Environment,
|
||||
GraphQLTaggedNode,
|
||||
} from "relay-runtime";
|
||||
|
||||
import { withContext } from "../bootstrap";
|
||||
|
||||
@@ -25,7 +30,7 @@ export const LOCAL_ID = "client:root.local";
|
||||
* must have the `LOCAL_ID`.
|
||||
*/
|
||||
function withLocalStateContainer<T>(
|
||||
fragmentSpec: any
|
||||
fragmentSpec: GraphQLTaggedNode
|
||||
): InferableComponentEnhancer<{ local: T }> {
|
||||
return compose(
|
||||
withContext(({ relayEnvironment }) => ({ relayEnvironment })),
|
||||
@@ -33,7 +38,7 @@ function withLocalStateContainer<T>(
|
||||
class LocalStateContainer extends React.Component<Props, any> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
const fragment = fragmentSpec.data().default;
|
||||
const fragment = (fragmentSpec as any).data().default;
|
||||
if (fragment.kind !== "Fragment") {
|
||||
throw new Error("Expected fragment");
|
||||
}
|
||||
|
||||
@@ -10,9 +10,13 @@ import { InferableComponentEnhancerWithProps } from "recompose";
|
||||
* withPaginationContainer is a curried version of `createPaginationContainers`
|
||||
* from Relay.
|
||||
*/
|
||||
export default <T, InnerProps>(
|
||||
fragmentSpec: GraphQLTaggedNode,
|
||||
connectionConfig: ConnectionConfig<InnerProps>
|
||||
export default <T, InnerProps, FragmentVariables, QueryVariables>(
|
||||
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode },
|
||||
connectionConfig: ConnectionConfig<
|
||||
InnerProps,
|
||||
FragmentVariables,
|
||||
QueryVariables
|
||||
>
|
||||
): InferableComponentEnhancerWithProps<
|
||||
T & { relay: RelayPaginationProp },
|
||||
{ [P in keyof T]: any }
|
||||
|
||||
@@ -10,7 +10,7 @@ import { InferableComponentEnhancerWithProps } from "recompose";
|
||||
* from Relay.
|
||||
*/
|
||||
export default <T>(
|
||||
fragmentSpec: GraphQLTaggedNode,
|
||||
fragmentSpec: { [P in keyof T]: GraphQLTaggedNode },
|
||||
refetchQuery: GraphQLTaggedNode
|
||||
): InferableComponentEnhancerWithProps<
|
||||
T & { relay: RelayRefetchProp },
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { FetchFunction } from "relay-runtime";
|
||||
|
||||
/**
|
||||
* Decorates the fetch function with error logging.
|
||||
* Intended for testing purposes.
|
||||
*/
|
||||
export default function wrapFetchWithLogger(
|
||||
fetch: FetchFunction,
|
||||
logResult?: boolean
|
||||
): FetchFunction {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
const result = await (fetch as any)(...args);
|
||||
if (logResult) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(JSON.stringify(result));
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
extends: "../.babelrc.js",
|
||||
plugins: [
|
||||
|
||||
@@ -1,38 +1,20 @@
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Center } from "talk-ui/components";
|
||||
import { Flex } from "talk-ui/components";
|
||||
|
||||
import AssetListContainer from "../containers/AssetListContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
import StreamContainer from "../containers/StreamContainer";
|
||||
import Logo from "./Logo";
|
||||
|
||||
export interface AppProps {
|
||||
assets?: any | null;
|
||||
asset?: {
|
||||
id: string;
|
||||
isClosed: boolean;
|
||||
comments: any | null;
|
||||
} | null;
|
||||
comment?: any | null;
|
||||
asset: {} | null;
|
||||
}
|
||||
|
||||
const App: StatelessComponent<AppProps> = props => {
|
||||
console.log(props);
|
||||
if (props.comment) {
|
||||
return <div>Comment</div>;
|
||||
}
|
||||
if (props.assets) {
|
||||
return <AssetListContainer assets={props.assets} />;
|
||||
}
|
||||
if (props.asset) {
|
||||
return (
|
||||
<Center>
|
||||
<Logo gutterBottom />
|
||||
<StreamContainer comments={props.asset.comments} />
|
||||
<PostCommentFormContainer assetID={props.asset.id} />
|
||||
</Center>
|
||||
<Flex justifyContent="center">
|
||||
<StreamContainer asset={props.asset} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
return <div>Asset not found </div>;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
export interface AssetListProps {
|
||||
assets: ReadonlyArray<{ id: string; title: string | null }>;
|
||||
}
|
||||
|
||||
const AssetList: StatelessComponent<AssetListProps> = props => {
|
||||
return (
|
||||
<div>
|
||||
{props.assets.map(asset => <div key={asset.id}>{asset.title}</div>)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssetList;
|
||||
@@ -1,11 +1,10 @@
|
||||
.root {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.gutterBottom {
|
||||
margin-bottom: calc(2 px * $spacing-unit);
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.author {
|
||||
font-weight: $font-weight-medium;
|
||||
.topBar {
|
||||
margin-bottom: calc(0.5px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
|
||||
import Comment from "./Comment";
|
||||
|
||||
it("renders username and body", () => {
|
||||
@@ -10,9 +11,8 @@ it("renders username and body", () => {
|
||||
},
|
||||
body: "Woof",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Comment {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
const wrapper = shallow(<Comment {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders with gutterBottom", () => {
|
||||
@@ -24,7 +24,6 @@ it("renders with gutterBottom", () => {
|
||||
body: "Woof",
|
||||
gutterBottom: true,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<Comment {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
const wrapper = shallow(<Comment {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { StatelessComponent } from "react";
|
||||
import { Typography } from "talk-ui/components";
|
||||
import * as styles from "./Comment.css";
|
||||
import PermalinkPopover from "./PermalinkPopover";
|
||||
import Username from "./Username";
|
||||
|
||||
export interface CommentProps {
|
||||
id: string;
|
||||
@@ -15,17 +16,15 @@ export interface CommentProps {
|
||||
gutterBottom?: boolean;
|
||||
}
|
||||
|
||||
// make a permalink popover
|
||||
|
||||
const Comment: StatelessComponent<CommentProps> = props => {
|
||||
const rootClassName = cn(styles.root, props.className, {
|
||||
[styles.gutterBottom]: props.gutterBottom,
|
||||
});
|
||||
return (
|
||||
<div className={rootClassName}>
|
||||
<Typography className={styles.author} gutterBottom>
|
||||
{props.author && props.author.username}
|
||||
</Typography>
|
||||
<div className={rootClassName} role="article">
|
||||
<div className={styles.topBar}>
|
||||
{props.author && <Username>{props.author.username}</Username>}
|
||||
</div>
|
||||
<Typography>{props.body}</Typography>
|
||||
<div className={cn("talk-comment-footer")}>
|
||||
<PermalinkPopover commentId={props.id} />
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.root {
|
||||
border-left: 3px solid;
|
||||
padding-left: calc(1px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.level0 {
|
||||
border-color: var(--palette-secondary-darkest);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import * as styles from "./Indent.css";
|
||||
|
||||
export interface IndentProps {
|
||||
level?: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Indent: StatelessComponent<IndentProps> = props => {
|
||||
return <div className={cn(styles.root, styles.level0)}>{props.children}</div>;
|
||||
};
|
||||
|
||||
export default Indent;
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
display: block;
|
||||
height: 100px;
|
||||
width: 400px;
|
||||
margin-bottom: calc(2px * $spacing-unit);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.postButton {
|
||||
float: right;
|
||||
.postButtonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -39,11 +39,13 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Localized id="postCommentForm-submit">
|
||||
<Button className={styles.postButton} disabled={submitting} primary>
|
||||
Post
|
||||
</Button>
|
||||
</Localized>
|
||||
<div className={styles.postButtonContainer}>
|
||||
<Localized id="comments-postCommentForm-post">
|
||||
<Button disabled={submitting} primary>
|
||||
Post
|
||||
</Button>
|
||||
</Localized>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import sinon, { SinonSpy } from "sinon";
|
||||
|
||||
import ReplyList, { ReplyListProps } from "./ReplyList";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: ReplyListProps = {
|
||||
commentID: "comment-id",
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onShowAll: noop,
|
||||
hasMore: false,
|
||||
disableShowAll: false,
|
||||
};
|
||||
const wrapper = shallow(<ReplyList {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when there is more", () => {
|
||||
const props: ReplyListProps = {
|
||||
commentID: "comment-id",
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onShowAll: sinon.spy(),
|
||||
hasMore: true,
|
||||
disableShowAll: false,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<ReplyList {...props} />);
|
||||
it("renders a load more button", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls onLoadMore", () => {
|
||||
wrapper
|
||||
.find("#talk-comments-replyList-showAll--comment-id")
|
||||
.simulate("click");
|
||||
expect((props.onShowAll as SinonSpy).calledOnce).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Button } from "talk-ui/components";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import Indent from "./Indent";
|
||||
|
||||
export interface ReplyListProps {
|
||||
commentID: string;
|
||||
comments: ReadonlyArray<{ id: string }>;
|
||||
onShowAll: () => void;
|
||||
hasMore: boolean;
|
||||
disableShowAll: boolean;
|
||||
}
|
||||
|
||||
const ReplyList: StatelessComponent<ReplyListProps> = props => {
|
||||
return (
|
||||
<Indent>
|
||||
<div id={`talk-comments-replyList-log--${props.commentID}`} role="log">
|
||||
{props.comments.map(comment => (
|
||||
<CommentContainer key={comment.id} data={comment} gutterBottom />
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-replyList-showAll">
|
||||
<Button
|
||||
id={`talk-comments-replyList-showAll--${props.commentID}`}
|
||||
aria-controls={`talk-comments-replyList-log--${props.commentID}`}
|
||||
onClick={props.onShowAll}
|
||||
disabled={props.disableShowAll}
|
||||
secondary
|
||||
invert
|
||||
fullWidth
|
||||
>
|
||||
Show All Replies
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</div>
|
||||
</Indent>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplyList;
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
import sinon from "sinon";
|
||||
|
||||
import Stream, { StreamProps } from "./Stream";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: StreamProps = {
|
||||
assetID: "asset-id",
|
||||
isClosed: false,
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onLoadMore: noop,
|
||||
disableLoadMore: false,
|
||||
hasMore: false,
|
||||
};
|
||||
const wrapper = shallow(<Stream {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when there is more", () => {
|
||||
const props = {
|
||||
assetID: "asset-id",
|
||||
isClosed: false,
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onLoadMore: sinon.spy(),
|
||||
disableLoadMore: false,
|
||||
hasMore: true,
|
||||
};
|
||||
|
||||
const wrapper = shallow(<Stream {...props} />);
|
||||
it("renders a load more button", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls onLoadMore", () => {
|
||||
wrapper.find("#talk-comments-stream-loadMore").simulate("click");
|
||||
expect(props.onLoadMore.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
const wrapperDisabledButton = shallow(<Stream {...props} disableLoadMore />);
|
||||
it("disables load more button", () => {
|
||||
expect(wrapperDisabledButton).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,52 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { Button } from "talk-ui/components";
|
||||
|
||||
import CommentContainer from "../containers/CommentContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
import ReplyListContainer from "../containers/ReplyListContainer";
|
||||
import Logo from "./Logo";
|
||||
import * as styles from "./Stream.css";
|
||||
|
||||
export interface StreamProps {
|
||||
assetID: string;
|
||||
isClosed: boolean;
|
||||
comments: ReadonlyArray<{ id: string }>;
|
||||
onLoadMore: () => void;
|
||||
hasMore: boolean;
|
||||
disableLoadMore: boolean;
|
||||
}
|
||||
|
||||
const Stream: StatelessComponent<StreamProps> = props => {
|
||||
return (
|
||||
<div>
|
||||
{props.comments.map(comment => (
|
||||
<CommentContainer key={comment.id} data={comment} gutterBottom />
|
||||
))}
|
||||
<div className={styles.root}>
|
||||
<Logo gutterBottom />
|
||||
<PostCommentFormContainer assetID={props.assetID} />
|
||||
<div id="talk-comments-stream-log" role="log" aria-live="polite">
|
||||
{props.comments.map(comment => (
|
||||
<div key={comment.id}>
|
||||
<CommentContainer data={comment} gutterBottom />
|
||||
<ReplyListContainer comment={comment} />
|
||||
</div>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="comments-stream-loadMore">
|
||||
<Button
|
||||
id={"talk-comments-stream-loadMore"}
|
||||
onClick={props.onLoadMore}
|
||||
secondary
|
||||
invert
|
||||
fullWidth
|
||||
disabled={props.disableLoadMore}
|
||||
aria-controls="talk-comments-stream-log"
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
composes: heading4 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import Username from "./Username";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props = {
|
||||
children: "Marvin",
|
||||
};
|
||||
const wrapper = shallow(<Username {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import * as styles from "./Username.css";
|
||||
|
||||
export interface CommentProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
const Username: StatelessComponent<CommentProps> = props => {
|
||||
return <span className={styles.root}>{props.children}</span>;
|
||||
};
|
||||
|
||||
export default Username;
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
exports[`renders username and body 1`] = `
|
||||
<div
|
||||
className="root"
|
||||
className="Comment-root"
|
||||
role="article"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
className="author"
|
||||
gutterBottom={true}
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
Marvin
|
||||
</withPropsOnChange(Typography)>
|
||||
<Username>
|
||||
Marvin
|
||||
</Username>
|
||||
</div>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
@@ -18,14 +20,16 @@ exports[`renders username and body 1`] = `
|
||||
|
||||
exports[`renders with gutterBottom 1`] = `
|
||||
<div
|
||||
className="root gutterBottom"
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
className="author"
|
||||
gutterBottom={true}
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
Marvin
|
||||
</withPropsOnChange(Typography)>
|
||||
<Username>
|
||||
Marvin
|
||||
</Username>
|
||||
</div>
|
||||
<withPropsOnChange(Typography)>
|
||||
Woof
|
||||
</withPropsOnChange(Typography)>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Indent>
|
||||
<div
|
||||
id="talk-comments-replyList-log--comment-id"
|
||||
role="log"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-1"
|
||||
/>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-2"
|
||||
/>
|
||||
</div>
|
||||
</Indent>
|
||||
`;
|
||||
|
||||
exports[`when there is more renders a load more button 1`] = `
|
||||
<Indent>
|
||||
<div
|
||||
id="talk-comments-replyList-log--comment-id"
|
||||
role="log"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-1"
|
||||
/>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
key="comment-2"
|
||||
/>
|
||||
<Localized
|
||||
id="comments-replyList-showAll"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
aria-controls="talk-comments-replyList-log--comment-id"
|
||||
disabled={false}
|
||||
fullWidth={true}
|
||||
id="talk-comments-replyList-showAll--comment-id"
|
||||
invert={true}
|
||||
onClick={[Function]}
|
||||
secondary={true}
|
||||
>
|
||||
Show All Replies
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</Indent>
|
||||
`;
|
||||
@@ -0,0 +1,202 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<Logo
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`when there is more disables load more button 1`] = `
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<Logo
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Localized
|
||||
id="comments-stream-loadMore"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
aria-controls="talk-comments-stream-log"
|
||||
disabled={true}
|
||||
fullWidth={true}
|
||||
id="talk-comments-stream-loadMore"
|
||||
invert={true}
|
||||
onClick={[Function]}
|
||||
secondary={true}
|
||||
>
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`when there is more renders a load more button 1`] = `
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<Logo
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<withContext(createMutationContainer(PostCommentFormContainer))
|
||||
assetID="asset-id"
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
key="comment-1"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
key="comment-2"
|
||||
>
|
||||
<Relay(CommentContainer)
|
||||
data={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
gutterBottom={true}
|
||||
/>
|
||||
<Relay(ReplyListContainer)
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Localized
|
||||
id="comments-stream-loadMore"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
aria-controls="talk-comments-stream-log"
|
||||
disabled={false}
|
||||
fullWidth={true}
|
||||
id="talk-comments-stream-loadMore"
|
||||
invert={true}
|
||||
onClick={[Function]}
|
||||
secondary={true}
|
||||
>
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Marvin
|
||||
</span>
|
||||
`;
|
||||
@@ -15,26 +15,16 @@ const AppContainer: StatelessComponent<InnerProps> = props => {
|
||||
return <App {...props.data} />;
|
||||
};
|
||||
|
||||
const enhanced = withFragmentContainer<{ data: Data }>(
|
||||
graphql`
|
||||
const enhanced = withFragmentContainer<{ data: Data }>({
|
||||
data: graphql`
|
||||
fragment AppContainer on Query
|
||||
@argumentDefinitions(
|
||||
assetID: { type: "ID!" }
|
||||
showAssetList: { type: "Boolean!" }
|
||||
) {
|
||||
assets @include(if: $showAssetList) {
|
||||
...AssetListContainer_assets
|
||||
}
|
||||
asset(id: $assetID) @skip(if: $showAssetList) {
|
||||
id
|
||||
isClosed
|
||||
comments {
|
||||
...StreamContainer_comments
|
||||
}
|
||||
@argumentDefinitions(assetID: { type: "ID!" }) {
|
||||
asset(id: $assetID) {
|
||||
...StreamContainer_asset
|
||||
}
|
||||
}
|
||||
`
|
||||
)(AppContainer);
|
||||
`,
|
||||
})(AppContainer);
|
||||
|
||||
export type AppContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { AssetListContainer_assets as Data } from "talk-stream/__generated__/AssetListContainer_assets.graphql";
|
||||
|
||||
import AssetList from "../components/AssetList";
|
||||
|
||||
interface InnerProps {
|
||||
assets: Data;
|
||||
}
|
||||
|
||||
const AssetListContainer: StatelessComponent<InnerProps> = props => {
|
||||
const assets = props.assets.edges.map(edge => edge.node);
|
||||
return <AssetList assets={assets} />;
|
||||
};
|
||||
|
||||
const enhanced = withFragmentContainer<{ assets: Data }>(
|
||||
graphql`
|
||||
fragment AssetListContainer_assets on AssetsConnection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
)(AssetListContainer);
|
||||
|
||||
export type AssetListContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { CommentContainer } from "./CommentContainer";
|
||||
|
||||
it("renders username and body", () => {
|
||||
const props = {
|
||||
data: {
|
||||
author: {
|
||||
username: "Marvin",
|
||||
},
|
||||
body: "Woof",
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = shallow(<CommentContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -9,22 +9,28 @@ import Comment, { CommentProps } from "../components/Comment";
|
||||
|
||||
type InnerProps = { data: Data } & Omit<CommentProps, keyof Data>;
|
||||
|
||||
const CommentContainer: StatelessComponent<InnerProps> = props => {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
graphql`
|
||||
fragment CommentContainer_comment on Comment {
|
||||
author {
|
||||
username
|
||||
}
|
||||
body
|
||||
}
|
||||
`;
|
||||
|
||||
export const CommentContainer: StatelessComponent<InnerProps> = props => {
|
||||
const { data, ...rest } = props;
|
||||
return <Comment {...rest} {...props.data} />;
|
||||
};
|
||||
|
||||
const enhanced = withFragmentContainer<{ data: Data }>(
|
||||
graphql`
|
||||
const enhanced = withFragmentContainer<{ data: Data }>({
|
||||
data: graphql`
|
||||
fragment CommentContainer on Comment {
|
||||
id
|
||||
author {
|
||||
username
|
||||
}
|
||||
body
|
||||
...CommentContainer_comment @relay(mask: false)
|
||||
}
|
||||
`
|
||||
)(CommentContainer);
|
||||
`,
|
||||
})(CommentContainer);
|
||||
|
||||
export type CommentContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
|
||||
@@ -25,6 +25,8 @@ class PostCommentFormContainer extends Component<InnerProps> {
|
||||
if (error instanceof BadUserInputError) {
|
||||
return error.invalidArgsLocalized;
|
||||
}
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { shallow, ShallowWrapper } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import ReplyList from "../components/ReplyList";
|
||||
import { ReplyListContainer } from "./ReplyListContainer";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: any = {
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: {
|
||||
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
|
||||
},
|
||||
},
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when replies are null", () => {
|
||||
const props: any = {
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: null,
|
||||
},
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(<ReplyListContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when has more replies", () => {
|
||||
let finishLoading: ((error?: Error) => void) | null = null;
|
||||
const props: PropTypesOf<ReplyListContainer> = {
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
replies: {
|
||||
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
|
||||
},
|
||||
},
|
||||
relay: {
|
||||
hasMore: () => true,
|
||||
isLoading: () => false,
|
||||
loadMore: (_: any, callback: () => void) => (finishLoading = callback),
|
||||
} as any,
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => (wrapper = shallow(<ReplyListContainer {...props} />)));
|
||||
|
||||
it("renders hasMore", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when showing all", () => {
|
||||
beforeAll(() => {
|
||||
wrapper
|
||||
.find(ReplyList)
|
||||
.props()
|
||||
.onShowAll();
|
||||
});
|
||||
it("calls relay loadMore", () => {
|
||||
expect(finishLoading).not.toBeNull();
|
||||
});
|
||||
it("disables show all button", () => {
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it("enable show all button after loading is done", () => {
|
||||
finishLoading!();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import React from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { ReplyListContainer_comment as Data } from "talk-stream/__generated__/ReplyListContainer_comment.graphql";
|
||||
import {
|
||||
COMMENT_SORT,
|
||||
ReplyListContainerPaginationQueryVariables,
|
||||
} from "talk-stream/__generated__/ReplyListContainerPaginationQuery.graphql";
|
||||
|
||||
import ReplyList from "../components/ReplyList";
|
||||
|
||||
export interface InnerProps {
|
||||
comment: Data;
|
||||
relay: RelayPaginationProp;
|
||||
}
|
||||
|
||||
export class ReplyListContainer extends React.Component<InnerProps> {
|
||||
public state = {
|
||||
disableShowAll: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (this.props.comment.replies === null) {
|
||||
return null;
|
||||
}
|
||||
const comments = this.props.comment.replies.edges.map(edge => edge.node);
|
||||
return (
|
||||
<ReplyList
|
||||
commentID={this.props.comment.id}
|
||||
comments={comments}
|
||||
onShowAll={this.showAll}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableShowAll={this.state.disableShowAll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private showAll = () => {
|
||||
if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ disableShowAll: true });
|
||||
this.props.relay.loadMore(
|
||||
999999999, // Fetch All Replies
|
||||
error => {
|
||||
this.setState({ disableShowAll: false });
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: (cvle) This should be autogenerated.
|
||||
interface FragmentVariables {
|
||||
count: number;
|
||||
cursor?: string;
|
||||
orderBy: COMMENT_SORT;
|
||||
}
|
||||
|
||||
const enhanced = withPaginationContainer<
|
||||
{ comment: Data },
|
||||
InnerProps,
|
||||
FragmentVariables,
|
||||
ReplyListContainerPaginationQueryVariables
|
||||
>(
|
||||
{
|
||||
comment: graphql`
|
||||
fragment ReplyListContainer_comment on Comment
|
||||
@argumentDefinitions(
|
||||
count: { type: "Int!", defaultValue: 5 }
|
||||
cursor: { type: "Cursor" }
|
||||
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_ASC }
|
||||
) {
|
||||
id
|
||||
replies(first: $count, after: $cursor, orderBy: $orderBy)
|
||||
@connection(key: "ReplyList_replies") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
direction: "forward",
|
||||
getConnectionFromProps(props) {
|
||||
return props.comment && props.comment.replies;
|
||||
},
|
||||
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
|
||||
getFragmentVariables(prevVars, totalCount) {
|
||||
return {
|
||||
...prevVars,
|
||||
count: totalCount,
|
||||
};
|
||||
},
|
||||
getVariables(props, { count, cursor }, fragmentVariables) {
|
||||
return {
|
||||
count,
|
||||
cursor,
|
||||
orderBy: fragmentVariables.orderBy,
|
||||
commentID: props.comment.id,
|
||||
};
|
||||
},
|
||||
query: graphql`
|
||||
# Pagination query to be fetched upon calling 'loadMore'.
|
||||
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
|
||||
query ReplyListContainerPaginationQuery(
|
||||
$count: Int!
|
||||
$cursor: Cursor
|
||||
$orderBy: COMMENT_SORT!
|
||||
$commentID: ID!
|
||||
) {
|
||||
comment(id: $commentID) {
|
||||
...ReplyListContainer_comment
|
||||
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
)(ReplyListContainer);
|
||||
|
||||
export type ReplyListContainerProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { shallow, ShallowWrapper } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Stream from "../components/Stream";
|
||||
import { StreamContainer } from "./StreamContainer";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<StreamContainer> = {
|
||||
asset: {
|
||||
id: "asset-id",
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
|
||||
},
|
||||
},
|
||||
relay: {
|
||||
hasMore: noop,
|
||||
isLoading: noop,
|
||||
} as any,
|
||||
};
|
||||
const wrapper = shallow(<StreamContainer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when has more comments", () => {
|
||||
let finishLoading: ((error?: Error) => void) | null = null;
|
||||
const props: PropTypesOf<StreamContainer> = {
|
||||
asset: {
|
||||
id: "asset-id",
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [{ node: { id: "comment-1" } }, { node: { id: "comment-2" } }],
|
||||
},
|
||||
},
|
||||
relay: {
|
||||
hasMore: () => true,
|
||||
isLoading: () => false,
|
||||
loadMore: (_: any, callback: () => void) => (finishLoading = callback),
|
||||
} as any,
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => (wrapper = shallow(<StreamContainer {...props} />)));
|
||||
|
||||
it("renders hasMore", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when loading more", () => {
|
||||
beforeAll(() => {
|
||||
wrapper
|
||||
.find(Stream)
|
||||
.props()
|
||||
.onLoadMore();
|
||||
});
|
||||
it("calls relay loadMore", () => {
|
||||
expect(finishLoading).not.toBeNull();
|
||||
});
|
||||
it("disables load more button", () => {
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it("enable load more button after loading is done", () => {
|
||||
finishLoading!();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,32 +1,132 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
import React from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { StreamContainer_comments as Data } from "talk-stream/__generated__/StreamContainer_comments.graphql";
|
||||
import { StreamContainer_asset as Data } from "talk-stream/__generated__/StreamContainer_asset.graphql";
|
||||
import {
|
||||
COMMENT_SORT,
|
||||
StreamContainerPaginationQueryVariables,
|
||||
} from "talk-stream/__generated__/StreamContainerPaginationQuery.graphql";
|
||||
|
||||
import Stream from "../components/Stream";
|
||||
|
||||
interface InnerProps {
|
||||
comments: Data;
|
||||
asset: Data;
|
||||
relay: RelayPaginationProp;
|
||||
}
|
||||
|
||||
const StreamContainer: StatelessComponent<InnerProps> = props => {
|
||||
const comments = props.comments.edges.map(edge => edge.node);
|
||||
return <Stream comments={comments} />;
|
||||
};
|
||||
export class StreamContainer extends React.Component<InnerProps> {
|
||||
public state = {
|
||||
disableLoadMore: false,
|
||||
};
|
||||
|
||||
const enhanced = withFragmentContainer<{ comments: Data }>(
|
||||
graphql`
|
||||
fragment StreamContainer_comments on CommentsConnection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
public render() {
|
||||
const comments = this.props.asset.comments.edges.map(edge => edge.node);
|
||||
return (
|
||||
<Stream
|
||||
assetID={this.props.asset.id}
|
||||
isClosed={this.props.asset.isClosed}
|
||||
comments={comments}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private loadMore = () => {
|
||||
if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
this.setState({ disableLoadMore: true });
|
||||
this.props.relay.loadMore(
|
||||
10, // Fetch the next 10 feed items
|
||||
error => {
|
||||
this.setState({ disableLoadMore: false });
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: (cvle) This should be autogenerated.
|
||||
interface FragmentVariables {
|
||||
count: number;
|
||||
cursor?: string;
|
||||
orderBy: COMMENT_SORT;
|
||||
}
|
||||
|
||||
const enhanced = withPaginationContainer<
|
||||
{ asset: Data },
|
||||
InnerProps,
|
||||
FragmentVariables,
|
||||
StreamContainerPaginationQueryVariables
|
||||
>(
|
||||
{
|
||||
asset: graphql`
|
||||
fragment StreamContainer_asset on Asset
|
||||
@argumentDefinitions(
|
||||
count: { type: "Int!", defaultValue: 5 }
|
||||
cursor: { type: "Cursor" }
|
||||
orderBy: { type: "COMMENT_SORT!", defaultValue: CREATED_AT_DESC }
|
||||
) {
|
||||
id
|
||||
isClosed
|
||||
comments(first: $count, after: $cursor, orderBy: $orderBy)
|
||||
@connection(key: "Stream_comments") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...CommentContainer
|
||||
...ReplyListContainer_comment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
direction: "forward",
|
||||
getConnectionFromProps(props) {
|
||||
return props.asset && props.asset.comments;
|
||||
},
|
||||
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
|
||||
getFragmentVariables(prevVars, totalCount) {
|
||||
return {
|
||||
...prevVars,
|
||||
count: totalCount,
|
||||
};
|
||||
},
|
||||
getVariables(props, { count, cursor }, fragmentVariables) {
|
||||
return {
|
||||
count,
|
||||
cursor,
|
||||
orderBy: fragmentVariables.orderBy,
|
||||
// assetID isn't specified as an @argument for the fragment, but it should be a
|
||||
// variable available for the fragment under the query root.
|
||||
assetID: props.asset.id,
|
||||
};
|
||||
},
|
||||
query: graphql`
|
||||
# Pagination query to be fetched upon calling 'loadMore'.
|
||||
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
|
||||
query StreamContainerPaginationQuery(
|
||||
$count: Int!
|
||||
$cursor: Cursor
|
||||
$orderBy: COMMENT_SORT!
|
||||
$assetID: ID!
|
||||
) {
|
||||
asset(id: $assetID) {
|
||||
...StreamContainer_asset
|
||||
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
)(StreamContainer);
|
||||
|
||||
export type StreamContainerProps = PropTypesOf<typeof enhanced>;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders username and body 1`] = `
|
||||
<Comment
|
||||
author={
|
||||
Object {
|
||||
"username": "Marvin",
|
||||
}
|
||||
}
|
||||
body="Woof"
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,78 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ReplyList
|
||||
commentID="comment-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableShowAll={false}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders correctly when replies are null 1`] = `""`;
|
||||
|
||||
exports[`when has more replies renders hasMore 1`] = `
|
||||
<ReplyList
|
||||
commentID="comment-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableShowAll={false}
|
||||
hasMore={true}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`when has more replies when showing all disables show all button 1`] = `
|
||||
<ReplyList
|
||||
commentID="comment-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableShowAll={true}
|
||||
hasMore={true}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`when has more replies when showing all enable show all button after loading is done 1`] = `
|
||||
<ReplyList
|
||||
commentID="comment-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableShowAll={false}
|
||||
hasMore={true}
|
||||
onShowAll={[Function]}
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,80 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Stream
|
||||
assetID="asset-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
isClosed={false}
|
||||
onLoadMore={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`when has more comments renders hasMore 1`] = `
|
||||
<Stream
|
||||
assetID="asset-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
isClosed={false}
|
||||
onLoadMore={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`when has more comments when loading more disables load more button 1`] = `
|
||||
<Stream
|
||||
assetID="asset-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={true}
|
||||
hasMore={true}
|
||||
isClosed={false}
|
||||
onLoadMore={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`when has more comments when loading more enable load more button after loading is done 1`] = `
|
||||
<Stream
|
||||
assetID="asset-id"
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
isClosed={false}
|
||||
onLoadMore={[Function]}
|
||||
/>
|
||||
`;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { render } from "./AppQuery";
|
||||
|
||||
it("renders app", () => {
|
||||
const data = {
|
||||
props: {} as any,
|
||||
error: null,
|
||||
};
|
||||
const wrapper = shallow(React.createElement(() => render(data)));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders loading", () => {
|
||||
const data = {
|
||||
props: null,
|
||||
error: null,
|
||||
};
|
||||
const wrapper = shallow(React.createElement(() => render(data)));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders error", () => {
|
||||
const data = {
|
||||
props: null,
|
||||
error: new Error("error"),
|
||||
};
|
||||
const wrapper = shallow(React.createElement(() => render(data)));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { ReadyState } from "react-relay";
|
||||
|
||||
import {
|
||||
graphql,
|
||||
QueryRenderer,
|
||||
ReadyState,
|
||||
withLocalStateContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ import { AppQueryLocal as Local } from "talk-stream/__generated__/AppQueryLocal.
|
||||
|
||||
import AppContainer from "../containers/AppContainer";
|
||||
|
||||
const render = ({ error, props }: ReadyState<AppQueryResponse>) => {
|
||||
export const render = ({ error, props }: ReadyState<AppQueryResponse>) => {
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
@@ -33,16 +33,12 @@ const AppQuery: StatelessComponent<InnerProps> = props => {
|
||||
return (
|
||||
<QueryRenderer<AppQueryVariables, AppQueryResponse>
|
||||
query={graphql`
|
||||
query AppQuery($assetID: ID!, $showAssetList: Boolean!) {
|
||||
...AppContainer
|
||||
@arguments(assetID: $assetID, showAssetList: $showAssetList)
|
||||
query AppQuery($assetID: ID!) {
|
||||
...AppContainer @arguments(assetID: $assetID)
|
||||
}
|
||||
`}
|
||||
variables={{
|
||||
// We cast `null` to any due to restrictions of the current graphql syntax.
|
||||
assetID: props.local.assetID || (null as any),
|
||||
// TODO: This is set to false, as server does not support querying assets yet.
|
||||
showAssetList: !props.local.assetID && false,
|
||||
assetID: props.local.assetID,
|
||||
}}
|
||||
render={render}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders app 1`] = `
|
||||
<Relay(AppContainer)
|
||||
data={Object {}}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders error 1`] = `
|
||||
<div>
|
||||
error
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders loading 1`] = `
|
||||
<div>
|
||||
Loading
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,214 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loads more comments 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Hey!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="talk-comments-stream-log"
|
||||
className="BaseButton-root Button-root Button-invert Button-fullWidth Button-secondary"
|
||||
disabled={false}
|
||||
id="talk-comments-stream-loadMore"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,138 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
I like yoghurt
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
id="talk-comments-replyList-log--comment-with-replies"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,91 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,226 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
id="talk-comments-replyList-log--comment-0"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="talk-comments-replyList-log--comment-0"
|
||||
className="BaseButton-root Button-root Button-invert Button-fullWidth Button-secondary"
|
||||
disabled={false}
|
||||
id="talk-comments-replyList-showAll--comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Show All Replies
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`show all replies 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="Stream-root"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading1 Typography-gutterBottom"
|
||||
>
|
||||
Talk NEO
|
||||
</h1>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div>
|
||||
<textarea
|
||||
className="PostCommentForm-textarea"
|
||||
name="body"
|
||||
onChange={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="PostCommentForm-postButtonContainer"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
aria-live="polite"
|
||||
id="talk-comments-stream-log"
|
||||
role="log"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Joining Too
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Indent-root Indent-level0"
|
||||
>
|
||||
<div
|
||||
id="talk-comments-replyList-log--comment-0"
|
||||
role="log"
|
||||
>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Lukas
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
What's up?
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="Comment-root Comment-gutterBottom"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Comment-topBar"
|
||||
>
|
||||
<span
|
||||
className="Username-root"
|
||||
>
|
||||
Isabelle
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
className="Typography-root Typography-body1"
|
||||
>
|
||||
Hey!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { IResolvers } from "graphql-tools";
|
||||
import { createFetch } from "relay-local-schema";
|
||||
import {
|
||||
commitLocalUpdate,
|
||||
Environment,
|
||||
Network,
|
||||
RecordProxy,
|
||||
RecordSource,
|
||||
Store,
|
||||
} from "relay-runtime";
|
||||
|
||||
import { loadSchema } from "talk-common/graphql";
|
||||
import {
|
||||
createAndRetain,
|
||||
LOCAL_ID,
|
||||
LOCAL_TYPE,
|
||||
wrapFetchWithLogger,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
export interface CreateEnvironmentParams {
|
||||
/** graphql resolvers */
|
||||
resolvers: IResolvers<any, any>;
|
||||
/** Allows to set initial state for Local state */
|
||||
initLocalState?: (local: RecordProxy) => void;
|
||||
/** If enabled, graphql responses will be logged to the console */
|
||||
logNetwork?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* create Relay environment for integration tests.
|
||||
*/
|
||||
export default function createEnvironment(params: CreateEnvironmentParams) {
|
||||
const schema = loadSchema("tenant", params.resolvers);
|
||||
const environment = new Environment({
|
||||
network: Network.create(
|
||||
wrapFetchWithLogger(createFetch({ schema }), params.logNetwork)
|
||||
),
|
||||
store: new Store(new RecordSource()),
|
||||
});
|
||||
if (params.initLocalState) {
|
||||
commitLocalUpdate(environment, s => {
|
||||
const root = s.getRoot();
|
||||
const localRecord = createAndRetain(environment, s, LOCAL_ID, LOCAL_TYPE);
|
||||
root.setLinkedRecord(localRecord, "local");
|
||||
params.initLocalState!(localRecord);
|
||||
});
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
export const users = [
|
||||
{
|
||||
id: "user-0",
|
||||
username: "Markus",
|
||||
},
|
||||
{
|
||||
id: "user-1",
|
||||
username: "Lukas",
|
||||
},
|
||||
{
|
||||
id: "user-2",
|
||||
username: "Isabelle",
|
||||
},
|
||||
];
|
||||
|
||||
export const comments = [
|
||||
{
|
||||
id: "comment-0",
|
||||
author: users[0],
|
||||
body: "Joining Too",
|
||||
createdAt: "2018-07-06T18:24:00",
|
||||
},
|
||||
{
|
||||
id: "comment-1",
|
||||
author: users[1],
|
||||
body: "What's up?",
|
||||
createdAt: "2018-07-06T18:20:00",
|
||||
},
|
||||
{
|
||||
id: "comment-2",
|
||||
author: users[2],
|
||||
body: "Hey!",
|
||||
createdAt: "2018-07-06T18:14:00",
|
||||
},
|
||||
];
|
||||
|
||||
export const assets = [
|
||||
{
|
||||
id: "asset-1",
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [
|
||||
{ node: comments[0], cursor: comments[0].createdAt },
|
||||
{ node: comments[1], cursor: comments[1].createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const commentWithReplies = {
|
||||
id: "comment-with-replies",
|
||||
author: users[0],
|
||||
body: "I like yoghurt",
|
||||
createdAt: "2018-07-06T18:24:00",
|
||||
replies: {
|
||||
edges: [
|
||||
{ node: comments[0], cursor: comments[0].createdAt },
|
||||
{ node: comments[1], cursor: comments[1].createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const assetWithReplies = {
|
||||
id: "asset-with-replies",
|
||||
isClosed: false,
|
||||
comments: {
|
||||
edges: [
|
||||
{ node: comments[0], cursor: comments[0].createdAt },
|
||||
{ node: commentWithReplies, cursor: commentWithReplies.createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import AppQuery from "talk-stream/queries/AppQuery";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
const connectionStub = sinon.stub().throws();
|
||||
connectionStub.withArgs({ first: 5, orderBy: "CREATED_AT_DESC" }).returns({
|
||||
edges: [
|
||||
{
|
||||
node: comments[0],
|
||||
cursor: comments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: comments[1],
|
||||
cursor: comments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
});
|
||||
connectionStub
|
||||
.withArgs({
|
||||
first: 10,
|
||||
orderBy: "CREATED_AT_DESC",
|
||||
after: comments[1].createdAt,
|
||||
})
|
||||
.returns({
|
||||
edges: [
|
||||
{
|
||||
node: comments[2],
|
||||
cursor: comments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
});
|
||||
|
||||
const assetStub = {
|
||||
...assets[0],
|
||||
comments: connectionStub,
|
||||
};
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
asset: sinon
|
||||
.stub()
|
||||
.throws()
|
||||
.withArgs(undefined, { id: assetStub.id })
|
||||
.returns(assetStub),
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeMessages: [],
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppQuery />
|
||||
</TalkContextProvider>
|
||||
);
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("loads more comments", async () => {
|
||||
testRenderer.root
|
||||
.findByProps({ id: "talk-comments-stream-loadMore" })
|
||||
.props.onClick();
|
||||
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import AppQuery from "talk-stream/queries/AppQuery";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import { assetWithReplies } from "./fixtures";
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
asset: sinon
|
||||
.stub()
|
||||
.throws()
|
||||
.withArgs(undefined, { id: assetWithReplies.id })
|
||||
.returns(assetWithReplies),
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(assetWithReplies.id, "assetID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeMessages: [],
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppQuery />
|
||||
</TalkContextProvider>
|
||||
);
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import AppQuery from "talk-stream/queries/AppQuery";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import { assets } from "./fixtures";
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
asset: sinon
|
||||
.stub()
|
||||
.throws()
|
||||
.withArgs(undefined, { id: assets[0].id })
|
||||
.returns(assets[0]),
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(assets[0].id, "assetID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeMessages: [],
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppQuery />
|
||||
</TalkContextProvider>
|
||||
);
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from "react";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import { RecordProxy } from "relay-runtime";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap";
|
||||
import AppQuery from "talk-stream/queries/AppQuery";
|
||||
|
||||
import createEnvironment from "./createEnvironment";
|
||||
import { assets, comments } from "./fixtures";
|
||||
|
||||
const connectionStub = sinon.stub().throws();
|
||||
connectionStub.withArgs({ first: 5, orderBy: "CREATED_AT_ASC" }).returns({
|
||||
edges: [
|
||||
{
|
||||
node: comments[1],
|
||||
cursor: comments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
});
|
||||
connectionStub
|
||||
.withArgs({
|
||||
first: sinon.match(n => n > 10000),
|
||||
orderBy: "CREATED_AT_ASC",
|
||||
after: comments[1].createdAt,
|
||||
})
|
||||
.returns({
|
||||
edges: [
|
||||
{
|
||||
node: comments[2],
|
||||
cursor: comments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
});
|
||||
|
||||
const commentStub = {
|
||||
...comments[0],
|
||||
replies: connectionStub,
|
||||
};
|
||||
|
||||
const assetStub = {
|
||||
...assets[0],
|
||||
comments: {
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
node: commentStub,
|
||||
cursor: commentStub.createdAt,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
comment: sinon
|
||||
.stub()
|
||||
.throws()
|
||||
.withArgs(undefined, { id: commentStub.id })
|
||||
.returns(commentStub),
|
||||
asset: sinon
|
||||
.stub()
|
||||
.throws()
|
||||
.withArgs(undefined, { id: assetStub.id })
|
||||
.returns(assetStub),
|
||||
},
|
||||
};
|
||||
|
||||
const environment = createEnvironment({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: (localRecord: RecordProxy) => {
|
||||
localRecord.setValue(assetStub.id, "assetID");
|
||||
},
|
||||
});
|
||||
|
||||
const context: TalkContext = {
|
||||
relayEnvironment: environment,
|
||||
localeMessages: [],
|
||||
};
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<TalkContextProvider value={context}>
|
||||
<AppQuery />
|
||||
</TalkContextProvider>
|
||||
);
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("show all replies", async () => {
|
||||
testRenderer.root
|
||||
.findByProps({ id: `talk-comments-replyList-showAll--${comments[0].id}` })
|
||||
.props.onClick();
|
||||
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import Enzyme from "enzyme";
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
|
||||
// React 16 Enzyme adapter
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
@@ -0,0 +1,27 @@
|
||||
import { JSDOM } from "jsdom";
|
||||
|
||||
declare var global: any;
|
||||
|
||||
const jsdom = new JSDOM("<!doctype html><html><body></body></html>");
|
||||
const { window } = jsdom;
|
||||
|
||||
function copyProps(src: any, target: any) {
|
||||
const props = Object.getOwnPropertyNames(src)
|
||||
.filter(prop => typeof target[prop] === "undefined")
|
||||
.reduce(
|
||||
(result, prop) => ({
|
||||
...result,
|
||||
[prop]: Object.getOwnPropertyDescriptor(src, prop),
|
||||
}),
|
||||
{}
|
||||
);
|
||||
Object.defineProperties(target, props);
|
||||
}
|
||||
|
||||
global.window = window;
|
||||
global.document = (window as any).document;
|
||||
global.navigator = {
|
||||
userAgent: "node.js",
|
||||
};
|
||||
|
||||
copyProps(window, global);
|
||||
@@ -0,0 +1,24 @@
|
||||
import "./enzyme";
|
||||
import "./jsdom";
|
||||
|
||||
// TODO: Remove when fixed.
|
||||
// Mock React.createContext because of https://github.com/airbnb/enzyme/issues/1509.
|
||||
function mockReact() {
|
||||
const originalReact = require.requireActual("react");
|
||||
return {
|
||||
...originalReact,
|
||||
createContext: jest.fn(defaultValue => {
|
||||
let value = defaultValue;
|
||||
const Provider = (props: any) => {
|
||||
value = props.value;
|
||||
return props.children;
|
||||
};
|
||||
const Consumer = (props: any) => props.children(value);
|
||||
return {
|
||||
Provider,
|
||||
Consumer,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
jest.mock("react", () => mockReact());
|
||||
@@ -5,40 +5,16 @@
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"allowJs": false,
|
||||
"lib": [
|
||||
"dom",
|
||||
"es7",
|
||||
"scripthost",
|
||||
"es2015",
|
||||
"esnext.asynciterable"
|
||||
],
|
||||
"lib": ["dom", "es7", "scripthost", "es2015", "esnext.asynciterable"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"talk-admin/*": [
|
||||
"./admin/*"
|
||||
],
|
||||
"talk-stream/*": [
|
||||
"./stream/*"
|
||||
],
|
||||
"talk-framework/*": [
|
||||
"./framework/*"
|
||||
],
|
||||
"talk-ui/*": [
|
||||
"./ui/*"
|
||||
],
|
||||
"talk-common/*": [
|
||||
"../common/*"
|
||||
],
|
||||
"talk-locales/*": [
|
||||
"../../locales/*"
|
||||
]
|
||||
"talk-admin/*": ["./admin/*"],
|
||||
"talk-stream/*": ["./stream/*"],
|
||||
"talk-framework/*": ["./framework/*"],
|
||||
"talk-ui/*": ["./ui/*"],
|
||||
"talk-common/*": ["../common/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../types/**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["./**/*", "../../types/**/*.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -1,78 +1,99 @@
|
||||
.root {
|
||||
composes: button from "talk-ui/shared/typography.css";
|
||||
padding: 5px 20px;
|
||||
border-radius: $round-corners;
|
||||
background-color: transparent;
|
||||
/* TODO: hover styles for the default button */
|
||||
|
||||
&:enabled,
|
||||
&:disabled {
|
||||
padding: 5px 20px;
|
||||
border-radius: var(--round-corners);
|
||||
background-color: transparent;
|
||||
/* TODO: hover styles for the default button */
|
||||
}
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
.root:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.fullWidth:enabled,
|
||||
.fullWidth:disabled {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background-color: $palette-primary-main;
|
||||
.primary:enabled,
|
||||
.primary:disabled {
|
||||
background-color: var(--palette-primary-main);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.primary:enabled {
|
||||
&:hover {
|
||||
background-color: $palette-primary-light;
|
||||
background-color: var(--palette-primary-light);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $palette-primary-lighter;
|
||||
}
|
||||
|
||||
&.invert {
|
||||
background-color: transparent;
|
||||
border: 1px solid $palette-primary-main;
|
||||
color: $palette-primary-main;
|
||||
|
||||
&:hover {
|
||||
border-color: $palette-primary-light;
|
||||
color: $palette-primary-light;
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-color: $palette-primary-lighter;
|
||||
color: $palette-primary-lighter;
|
||||
}
|
||||
background-color: var(--palette-primary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: $palette-secondary-main;
|
||||
color: #fff;
|
||||
.primary:enabled.invert,
|
||||
.primary:disabled.invert {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--palette-primary-main);
|
||||
color: var(--palette-primary-main);
|
||||
}
|
||||
|
||||
.primary:enabled.invert {
|
||||
&:hover {
|
||||
background-color: $palette-secondary-light;
|
||||
border-color: var(--palette-primary-light);
|
||||
color: var(--palette-primary-light);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $palette-secondary-lighter;
|
||||
border-color: var(--palette-primary-lighter);
|
||||
color: var(--palette-primary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary:enabled,
|
||||
.secondary:disabled {
|
||||
background-color: var(--palette-secondary-main);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.secondary:enabled {
|
||||
&:hover {
|
||||
background-color: var(--palette-secondary-light);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--palette-secondary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary:enabled.invert,
|
||||
.secondary:disabled.invert {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--palette-secondary-main);
|
||||
color: var(--palette-secondary-main);
|
||||
}
|
||||
|
||||
.secondary:enabled.invert {
|
||||
&:hover {
|
||||
border-color: var(--palette-secondary-light);
|
||||
color: var(--palette-secondary-light);
|
||||
}
|
||||
|
||||
&.invert {
|
||||
background-color: transparent;
|
||||
border: 1px solid $palette-secondary-main;
|
||||
color: $palette-secondary-main;
|
||||
|
||||
&:hover {
|
||||
border-color: $palette-secondary-light;
|
||||
color: $palette-secondary-light;
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-color: $palette-secondary-lighter;
|
||||
color: $palette-secondary-lighter;
|
||||
}
|
||||
&:active {
|
||||
border-color: var(--palette-secondary-lighter);
|
||||
color: var(--palette-secondary-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This seems to be the best way to target modern touch device browsers.
|
||||
*/
|
||||
@media (-moz-touch-enabled: 1), (pointer:coarse) {
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
/* TODO: Remove hover styles */
|
||||
}
|
||||
|
||||
@@ -10,11 +10,18 @@ import Button from './Button'
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Button style={{marginRight: "10px"}}>Push Me</Button>
|
||||
<Button style={{marginRight: "10px"}} anchor>I'm an Anchor Tag</Button>
|
||||
<Button style={{marginRight: "10px"}} primary>Primary</Button>
|
||||
<Button style={{marginRight: "10px"}} secondary>Secondary</Button>
|
||||
<Button style={{marginTop: "10px"}} primary fullWidth>Full Width</Button>
|
||||
<Button style={{marginTop: "10px"}} primary invert fullWidth>Full Width Invert</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}}>Push Me</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} disabled>Push Me</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} anchor>I'm an Anchor Tag</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary disabled>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary disabled>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary invert>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} primary invert disabled>Primary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary invert>Secondary</Button>
|
||||
<Button style={{margin: "0 10px 10px 0"}} secondary invert disabled>Secondary</Button>
|
||||
<Button style={{marginBottom: "10px"}} primary fullWidth>Full Width</Button>
|
||||
<Button style={{marginBottom: "10px"}} primary invert fullWidth>Full Width Invert</Button>
|
||||
</Playground>
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import cn from "classnames";
|
||||
import * as React from "react";
|
||||
import { ReactNode, StatelessComponent } from "react";
|
||||
|
||||
import { withStyles } from "talk-ui/hocs";
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import * as styles from "./Center.css";
|
||||
|
||||
interface InnerProps {
|
||||
/**
|
||||
* This prop can be used to add custom classnames.
|
||||
* It is handled by the `withStyles `HOC.
|
||||
*/
|
||||
classes: Partial<typeof styles>;
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const Center: StatelessComponent<InnerProps> = props => {
|
||||
return (
|
||||
<div className={cn(props.className, props.classes.root)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(Center);
|
||||
export type CenterProps = PropTypesOf<typeof enhanced>;
|
||||
export default enhanced;
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./Center";
|
||||
export { default } from "./Center";
|
||||
@@ -0,0 +1,51 @@
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justifyFlexStart {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.justifyFlexEnd {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.justifyCenter {
|
||||
justify-content: center;
|
||||
}
|
||||
.justifySpaceBetween {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justifySpaceAround {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.justifySpaceEvenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.alignFlexStart {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.alignFlexEnd {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.alignCenter {
|
||||
align-items: center;
|
||||
}
|
||||
.alignBaseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
.alignStretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.directionRow {
|
||||
flex-direction: row;
|
||||
}
|
||||
.directionColumn {
|
||||
flex-direction: column;
|
||||
}
|
||||
.directionRowReverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.directionColumnReverse {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Flex
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
import Flex from './Flex'
|
||||
|
||||
# Flex
|
||||
|
||||
`Flex` is a wrapper around `flexbox`.
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<Flex justifyContent="center">I'm centered</Flex>
|
||||
</Playground>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import Flex from "./Flex";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof Flex> = {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
direction: "row",
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<Flex {...props}>
|
||||
<div>Hello World</div>
|
||||
</Flex>
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import cn from "classnames";
|
||||
import React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
|
||||
import { pascalCase } from "talk-common/utils";
|
||||
|
||||
import * as styles from "./Flex.css";
|
||||
|
||||
interface InnerProps {
|
||||
justifyContent?:
|
||||
| "flex-start"
|
||||
| "flex-end"
|
||||
| "center"
|
||||
| "space-around"
|
||||
| "space-between"
|
||||
| "space-evenly";
|
||||
alignItems?: "flex-start" | "flex-end" | "center" | "baseline" | "stretch";
|
||||
direction?: "row" | "column" | "row-reverse" | "column-reverse";
|
||||
}
|
||||
|
||||
const Flex: StatelessComponent<InnerProps> = props => {
|
||||
const { justifyContent, alignItems, direction, ...rest } = props;
|
||||
|
||||
const classObject: Record<string, boolean> = {};
|
||||
|
||||
if (justifyContent) {
|
||||
classObject[(styles as any)[`justify${pascalCase(justifyContent)}`]] = true;
|
||||
}
|
||||
|
||||
if (alignItems) {
|
||||
classObject[(styles as any)[`align${pascalCase(alignItems)}`]] = true;
|
||||
}
|
||||
|
||||
if (direction) {
|
||||
classObject[(styles as any)[`direction${pascalCase(direction)}`]] = true;
|
||||
}
|
||||
|
||||
const classNames: string = cn(styles.root, classObject);
|
||||
|
||||
return <div className={classNames} {...rest} />;
|
||||
};
|
||||
|
||||
export default Flex;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="Flex-root Flex-justifyCenter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Flex";
|
||||
export { default } from "./Flex";
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: MatchMedia
|
||||
menu: UI Kit
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
import MatchMedia from './MatchMedia'
|
||||
|
||||
# MatchMedia
|
||||
|
||||
`MatchMedia` uses media queries to provide responsive rendering.
|
||||
|
||||
## Basic usage
|
||||
<Playground>
|
||||
<MatchMedia maxWidth="xs">
|
||||
I'm a very small screen
|
||||
</MatchMedia>
|
||||
<MatchMedia minWidth="xs" maxWidth="sm">
|
||||
I'm a small screen
|
||||
</MatchMedia>
|
||||
<MatchMedia minWidth="sm" maxWidth="md">
|
||||
I'm a medium screen
|
||||
</MatchMedia>
|
||||
<MatchMedia minWidth="md" maxWidth="lg">
|
||||
I'm a large screen
|
||||
</MatchMedia>
|
||||
<MatchMedia minWidth="lg" maxWidth="xl">
|
||||
I'm a very large screen
|
||||
</MatchMedia>
|
||||
<MatchMedia minWidth="xl">
|
||||
I'm a super large screen
|
||||
</MatchMedia>
|
||||
</Playground>
|
||||
|
||||
## Using render props
|
||||
<Playground>
|
||||
<MatchMedia minWidth="lg">
|
||||
{matches => <span>{matches ? "I'm big" : "I'm small"}</span>}
|
||||
</MatchMedia>
|
||||
</Playground>
|
||||
@@ -0,0 +1,27 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { PropTypesOf } from "talk-ui/types";
|
||||
|
||||
import MatchMedia from "./MatchMedia";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof MatchMedia> = {
|
||||
minWidth: "xs",
|
||||
maxWidth: "sm",
|
||||
component: "div",
|
||||
screen: true,
|
||||
children: <div>Hello World</div>,
|
||||
};
|
||||
const wrapper = shallow(<MatchMedia {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("map new speech prop to older aural prop", () => {
|
||||
const props: PropTypesOf<typeof MatchMedia> = {
|
||||
speech: true,
|
||||
children: <div>Hello World</div>,
|
||||
};
|
||||
const wrapper = shallow(<MatchMedia {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { ReactNode, StatelessComponent } from "react";
|
||||
import Responsive from "react-responsive";
|
||||
|
||||
import theme from "../../theme/variables";
|
||||
|
||||
type Breakpoints = keyof typeof theme.breakpoints;
|
||||
|
||||
interface InnerProps {
|
||||
minWidth?: Breakpoints;
|
||||
maxWidth?: Breakpoints;
|
||||
children: ReactNode | ((matches: boolean) => React.ReactNode);
|
||||
className?: string;
|
||||
component?:
|
||||
| string
|
||||
| React.SFC<any>
|
||||
| React.ClassType<any, any, any>
|
||||
| React.ComponentClass<any>;
|
||||
all?: boolean;
|
||||
print?: boolean;
|
||||
screen?: boolean;
|
||||
speech?: boolean;
|
||||
}
|
||||
|
||||
const MatchMedia: StatelessComponent<InnerProps> = props => {
|
||||
const { speech, minWidth, maxWidth, ...rest } = props;
|
||||
const mapped = {
|
||||
// TODO: Temporarily map newer speech to older aural type until
|
||||
// react-responsive supports the speech prop.
|
||||
aural: speech,
|
||||
minWidth: minWidth ? theme.breakpoints[minWidth] + 1 : undefined,
|
||||
maxWidth: maxWidth ? theme.breakpoints[maxWidth] : undefined,
|
||||
};
|
||||
return <Responsive {...rest} {...mapped} />;
|
||||
};
|
||||
|
||||
export default MatchMedia;
|
||||
@@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`map new speech prop to older aural prop 1`] = `
|
||||
<MediaQuery
|
||||
aural={true}
|
||||
values={Object {}}
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</MediaQuery>
|
||||
`;
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<MediaQuery
|
||||
component="div"
|
||||
maxWidth={640}
|
||||
minWidth={321}
|
||||
screen={true}
|
||||
values={Object {}}
|
||||
>
|
||||
<div>
|
||||
Hello World
|
||||
</div>
|
||||
</MediaQuery>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./MatchMedia";
|
||||
export { default } from "./MatchMedia";
|
||||
@@ -11,19 +11,19 @@
|
||||
composes: heading2 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.heading3 {
|
||||
.heading3 {
|
||||
composes: heading3 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.heading4 {
|
||||
.heading4 {
|
||||
composes: heading4 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.subtitle1 {
|
||||
.subtitle1 {
|
||||
composes: subtitle1 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.subtitle2 {
|
||||
.subtitle2 {
|
||||
composes: subtitle2 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
composes: body1 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
.body2 {
|
||||
.body2 {
|
||||
composes: body2 from "talk-ui/shared/typography.css";
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
margin-bottom: calc(2px * $spacing-unit);
|
||||
margin-bottom: calc(1px * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.colorInherit {
|
||||
@@ -78,17 +78,17 @@
|
||||
}
|
||||
|
||||
.colorPrimary {
|
||||
color: $palette-primary-main;
|
||||
color: var(--palette-primary-main);
|
||||
}
|
||||
|
||||
.colorSecondary {
|
||||
color: $palette-secondary-main;
|
||||
color: var(--palette-secondary-main);
|
||||
}
|
||||
|
||||
.colorTextSecondary {
|
||||
color: $palette-text-secondary;
|
||||
color: var(--palette-text-secondary);
|
||||
}
|
||||
|
||||
.colorError {
|
||||
color: $palette-error-main;
|
||||
color: var(--palette-error-main);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
export { default as BaseButton } from "./BaseButton";
|
||||
export { default as Button } from "./Button";
|
||||
export { default as Center } from "./Center";
|
||||
export { default as Typography } from "./Typography";
|
||||
<<<<<<< HEAD
|
||||
export { default as Popover } from "./Popover";
|
||||
export { default as TextField } from "./TextField";
|
||||
=======
|
||||
export { default as Flex } from "./Flex";
|
||||
export { default as MatchMedia } from "./MatchMedia";
|
||||
>>>>>>> cac0afa61e79b19ddcae873f543a6910b83c92e7
|
||||
|
||||
@@ -1,59 +1,63 @@
|
||||
.heading1 {
|
||||
font-size: calc(24rem / $rem-base);
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: calc(24rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: "Manuale";
|
||||
line-height: calc(32em / 24);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: $palette-text-primary;
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.heading2 {
|
||||
font-size: calc(20rem / $rem-base);
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: calc(20rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: "Manuale";
|
||||
line-height: calc(24em / 20);
|
||||
color: $palette-text-primary;
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.heading3 {
|
||||
font-size: calc(18rem / $rem-base);
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: calc(18rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: "Manuale";
|
||||
line-height: calc(20em / 18);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: $palette-text-primary;
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.heading4 {
|
||||
font-size: calc(16rem / $rem-base);
|
||||
font-weight: $font-weight-medium;
|
||||
font-size: calc(16rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: "Manuale";
|
||||
line-height: calc(18em / 16);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: $palette-text-primary;
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.subtitle1 {}
|
||||
.subtitle1 {
|
||||
}
|
||||
|
||||
.subtitle2 {}
|
||||
.subtitle2 {
|
||||
}
|
||||
|
||||
.body2 {}
|
||||
.body2 {
|
||||
}
|
||||
|
||||
.body1 {
|
||||
font-size: calc(16rem / $rem-base);
|
||||
font-weight: $font-weight-regular;
|
||||
font-size: calc(16rem / var(--rem-base));
|
||||
font-weight: var(--font-weight-regular);
|
||||
font-family: "Source Sans Pro";
|
||||
line-height: calc(18em / 16);
|
||||
letter-spacing: calc(0.2em / 16);
|
||||
color: $palette-text-primary;
|
||||
color: var(--palette-text-primary);
|
||||
}
|
||||
|
||||
.button {
|
||||
color: $palette-text-secondary;
|
||||
color: var(--palette-text-secondary);
|
||||
font-family: "Source Sans Pro";
|
||||
font-weight: $font-weight-medium;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: 16px;
|
||||
letter-spacing: calc(0.57em / 16);
|
||||
}
|
||||
|
||||
.overline {}
|
||||
.overline {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Variables defined in `variables.ts` are already available
|
||||
flattened and in kebab case.
|
||||
|
||||
These are additionally variables we define.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--spacing-unit: var(--spacing-unit-small);
|
||||
}
|
||||
|
||||
@media (min-width: $breakpoints-xs) {
|
||||
:root {
|
||||
--spacing-unit: var(--spacing-unit-large);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ const variables = {
|
||||
palette: {
|
||||
/* Primary colors */
|
||||
primary: {
|
||||
darker: "#0D5B8F",
|
||||
darkest: "#0D5B8F",
|
||||
dark: "#2B7EB5",
|
||||
main: "#3498DB",
|
||||
light: "#67B2E4",
|
||||
@@ -15,7 +15,7 @@ const variables = {
|
||||
},
|
||||
/* Secondary colors */
|
||||
secondary: {
|
||||
darker: "#404345",
|
||||
darkest: "#404345",
|
||||
dark: "#65696B",
|
||||
main: "#787D80",
|
||||
light: "#9A9DA0",
|
||||
@@ -23,7 +23,7 @@ const variables = {
|
||||
},
|
||||
/* Success colors */
|
||||
success: {
|
||||
darker: "#03AB61",
|
||||
darkest: "#03AB61",
|
||||
dark: "#02BD6B",
|
||||
main: "#00CD73",
|
||||
light: "#40D996",
|
||||
@@ -31,7 +31,7 @@ const variables = {
|
||||
},
|
||||
/* Error colors */
|
||||
error: {
|
||||
darker: "#F50F0C",
|
||||
darkest: "#F50F0C",
|
||||
dark: "#FF1F1C",
|
||||
main: "#FA4643",
|
||||
light: "#F26563",
|
||||
@@ -58,7 +58,8 @@ const variables = {
|
||||
},
|
||||
},
|
||||
/* gitter and spacing */
|
||||
spacingUnit: 5,
|
||||
spacingUnitSmall: 5,
|
||||
spacingUnitLarge: 10,
|
||||
/* Borders */
|
||||
roundCorners: "2px",
|
||||
/* Typography */
|
||||
@@ -68,6 +69,14 @@ const variables = {
|
||||
fontWeightLight: 300,
|
||||
fontWeightRegular: 400,
|
||||
fontWeightMedium: 550,
|
||||
/* Breakpoints */
|
||||
breakpoints: {
|
||||
xs: 320,
|
||||
sm: 640,
|
||||
md: 1024,
|
||||
lg: 1400,
|
||||
xl: 1600,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = variables;
|
||||
export default variables;
|
||||
|
||||
@@ -29,4 +29,6 @@ export type Overwrite<T, U> = Pick<T, Diff<keyof T, keyof U>> & U;
|
||||
*
|
||||
* E.g. type ButtonProps = ReturnPropTypes<Button>;
|
||||
*/
|
||||
export type PropTypesOf<T> = T extends React.ComponentType<infer R> ? R : {};
|
||||
export type PropTypesOf<T> = T extends React.ComponentType<infer R>
|
||||
? R
|
||||
: T extends React.Component<infer S> ? S : {};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
["@babel/env", { targets: "last 2 versions, ie 11", modules: "commonjs" }],
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default as loadSchema } from "./loadSchema";
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as timeout } from "./timeout";
|
||||
export { default as pascalCase } from "./pascalCase";
|
||||
@@ -0,0 +1,5 @@
|
||||
import { camelCase } from "lodash";
|
||||
|
||||
export default function pascalCase(value: string) {
|
||||
return value[0].toUpperCase() + camelCase(value).slice(1);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user