@mitodl/smoot-design 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.eslintrc.js +142 -0
  2. package/.github/workflows/ci.yml +48 -0
  3. package/.github/workflows/publish-pages.yml +50 -0
  4. package/.github/workflows/release.yml +34 -0
  5. package/.github/workflows/validate-pr.yml +49 -0
  6. package/.pre-commit-config.yaml +90 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc.json +4 -0
  9. package/.releaserc.json +40 -0
  10. package/.secrets.baseline +113 -0
  11. package/.storybook/main.ts +46 -0
  12. package/.storybook/manager-head.html +1 -0
  13. package/.storybook/preview-head.html +5 -0
  14. package/.storybook/preview.tsx +15 -0
  15. package/.storybook/public/pexels-photo-1851188.webp +0 -0
  16. package/.yarn/releases/yarn-4.5.1.cjs +934 -0
  17. package/.yarnrc.yml +23 -0
  18. package/LICENSE +28 -0
  19. package/README.md +13 -0
  20. package/jest.config.ts +22 -0
  21. package/package.json +110 -0
  22. package/src/components/Button/ActionButton.stories.tsx +186 -0
  23. package/src/components/Button/Button.stories.tsx +275 -0
  24. package/src/components/Button/Button.test.tsx +56 -0
  25. package/src/components/Button/Button.tsx +418 -0
  26. package/src/components/LinkAdapter/LinkAdapter.tsx +38 -0
  27. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +94 -0
  28. package/src/components/ThemeProvider/ThemeProvider.tsx +127 -0
  29. package/src/components/ThemeProvider/Typography.stories.tsx +74 -0
  30. package/src/components/ThemeProvider/breakpoints.ts +20 -0
  31. package/src/components/ThemeProvider/buttons.ts +22 -0
  32. package/src/components/ThemeProvider/chips.tsx +167 -0
  33. package/src/components/ThemeProvider/colors.ts +33 -0
  34. package/src/components/ThemeProvider/typography.ts +174 -0
  35. package/src/index.ts +24 -0
  36. package/src/jest-setup.ts +0 -0
  37. package/src/story-utils/index.ts +28 -0
  38. package/src/types/theme.d.ts +106 -0
  39. package/src/types/typography.d.ts +54 -0
  40. package/tsconfig.json +26 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,142 @@
1
+ module.exports = {
2
+ extends: [
3
+ "eslint-config-mitodl",
4
+ "eslint-config-mitodl/jest",
5
+ "plugin:styled-components-a11y/recommended",
6
+ "plugin:import/typescript",
7
+ "plugin:mdx/recommended",
8
+ "prettier",
9
+ ],
10
+ plugins: ["testing-library", "import", "styled-components-a11y"],
11
+ ignorePatterns: ["**/build/**"],
12
+ settings: {
13
+ "jsx-a11y": {
14
+ components: {
15
+ "ListCard.Image": "img",
16
+ "Card.Image": "img",
17
+ Button: "button",
18
+ ButtonLink: "a",
19
+ ActionButton: "button",
20
+ ActionButtonLink: "a",
21
+ },
22
+ },
23
+ },
24
+ rules: {
25
+ ...restrictedImports(),
26
+ // This rule is disabled in the default a11y config, but unclear why.
27
+ // It does catch useful errors, e.g., buttons with no text or label.
28
+ // If it proves to be flaky, we can find other ways to check for this.
29
+ // We need both rules below. One for normal elements, one for styled
30
+ "jsx-a11y/control-has-associated-label": ["error"],
31
+ "styled-components-a11y/control-has-associated-label": ["error"],
32
+ "@typescript-eslint/triple-slash-reference": [
33
+ "error",
34
+ {
35
+ path: "never",
36
+ types: "prefer-import",
37
+ lib: "never",
38
+ },
39
+ ],
40
+ "import/no-extraneous-dependencies": [
41
+ "error",
42
+ {
43
+ devDependencies: [
44
+ "**/*.test.ts",
45
+ "**/*.test.tsx",
46
+ "**/src/setupJest.ts",
47
+ "**/jest-shared-setup.ts",
48
+ "**/test-utils/**",
49
+ "**/test-utils/**",
50
+ "**/webpack.config.js",
51
+ "**/webpack.exports.js",
52
+ "**/postcss.config.js",
53
+ "**/*.stories.ts",
54
+ "**/*.stories.tsx",
55
+ "**/*.mdx",
56
+ ".storybook/**",
57
+ ],
58
+ },
59
+ ],
60
+ "import/no-duplicates": "error",
61
+ quotes: ["error", "double", { avoidEscape: true }],
62
+ "no-restricted-syntax": [
63
+ "error",
64
+ /**
65
+ * See https://eslint.org/docs/latest/rules/no-restricted-syntax
66
+ *
67
+ * The selectors use "ES Query", a css-like syntax for AST querying. A
68
+ * useful tool is https://estools.github.io/esquery/
69
+ */
70
+ {
71
+ selector:
72
+ "Property[key.name=fontWeight][value.raw=/\\d+/], TemplateElement[value.raw=/font-weight: \\d+/]",
73
+ message:
74
+ "Do not specify `fontWeight` manually. Prefer spreading `theme.typography.subtitle1` or similar. If you MUST use a fontWeight, refer to `fontWeights` theme object.",
75
+ },
76
+ {
77
+ selector:
78
+ "Property[key.name=fontFamily][value.raw=/Neue Haas/], TemplateElement[value.raw=/Neue Haas/]",
79
+ message:
80
+ "Do not specify `fontFamily` manually. Prefer spreading `theme.typography.subtitle1` or similar. If using neue-haas-grotesk-text, this is ThemeProvider's default fontFamily.",
81
+ },
82
+ ],
83
+ },
84
+ overrides: [
85
+ {
86
+ files: ["./**/*.test.{ts,tsx}"],
87
+ plugins: ["testing-library"],
88
+ extends: ["plugin:testing-library/react"],
89
+ rules: {
90
+ "testing-library/no-node-access": "off",
91
+ },
92
+ },
93
+ ],
94
+ }
95
+
96
+ function restrictedImports({ paths = [], patterns = [] } = {}) {
97
+ /**
98
+ * With the `no-restricted-imports` rule (and its typescript counterpart),
99
+ * it's difficult to restrict imports but allow a few exceptions.
100
+ *
101
+ * For example:
102
+ * - forbid importing `@mui/material/*`, EXCEPT within `ol-components`.
103
+ *
104
+ * It is possible to do this using overrides.
105
+ *
106
+ * This function exists to make it easier to share config between overrides.
107
+ *
108
+ * See also:
109
+ * - https://github.com/eslint/eslint/discussions/17047 no-restricted-imports: allow some specific imports in some specific directories
110
+ * - https://github.com/eslint/eslint/discussions/15011 Can a rule be specified multiple times without overriding itself?
111
+ *
112
+ * This may be easier if we swtich to ESLint's new "flat config" system.
113
+ */
114
+ return {
115
+ "@typescript-eslint/no-restricted-imports": [
116
+ "error",
117
+ {
118
+ paths: [
119
+ /**
120
+ * No direct imports from large "barrel files". They make Jest slow.
121
+ *
122
+ * For more, see:
123
+ * - https://github.com/jestjs/jest/issues/11234
124
+ * - https://github.com/faker-js/faker/issues/1114#issuecomment-1169532948
125
+ */
126
+ {
127
+ name: "@faker-js/faker",
128
+ message: "Please use @faker-js/faker/locale/en instead.",
129
+ allowTypeImports: true,
130
+ },
131
+ {
132
+ name: "@mui/material",
133
+ message: "Please use @mui/material/<COMPONENT_NAME> instead.",
134
+ allowTypeImports: true,
135
+ },
136
+ ...paths,
137
+ ],
138
+ patterns: [...patterns],
139
+ },
140
+ ],
141
+ }
142
+ }
@@ -0,0 +1,48 @@
1
+ name: CI
2
+ on: [push]
3
+ jobs:
4
+ javascript-tests:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
8
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
9
+ with:
10
+ node-version: "^22"
11
+ cache: yarn
12
+ cache-dependency-path: yarn.lock
13
+
14
+ - name: Install dependencies
15
+ run: yarn install --immutable
16
+
17
+ - name: Format
18
+ run: yarn run fmt-check
19
+
20
+ - name: Lints
21
+ run: yarn run lint-check
22
+
23
+ - name: Typecheck
24
+ run: yarn typecheck
25
+
26
+ - name: Tests
27
+ run: yarn test
28
+ env:
29
+ CODECOV: true
30
+ NODE_ENV: test
31
+
32
+ build-storybook:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Checkout
36
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
37
+
38
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
39
+ with:
40
+ node-version: "^22"
41
+ cache: yarn
42
+ cache-dependency-path: yarn.lock
43
+
44
+ - name: Install dependencies
45
+ run: yarn install
46
+
47
+ - name: Build Storybook
48
+ run: yarn build-storybook
@@ -0,0 +1,50 @@
1
+ name: Publish Storybook
2
+
3
+ on:
4
+ # Runs on pushes targeting the default branch
5
+ push:
6
+ branches: [main, cc/initial]
7
+
8
+ # Allows you to run this workflow manually from the Actions tab
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
17
+
18
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4
19
+ with:
20
+ node-version: "^20"
21
+ cache: yarn
22
+ cache-dependency-path: yarn.lock
23
+
24
+ - name: Install dependencies
25
+ run: yarn install
26
+
27
+ - name: Build Storybook
28
+ run: yarn build-storybook
29
+
30
+ - name: Upload artifact
31
+ uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
32
+ with:
33
+ path: ./storybook-static
34
+
35
+ deploy:
36
+ needs: build
37
+
38
+ permissions:
39
+ pages: write # to deploy to Pages
40
+ id-token: write # to verify the deployment originates from an appropriate source
41
+
42
+ environment:
43
+ name: github-pages
44
+ url: ${{ steps.deployment.outputs.page_url }}
45
+
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - name: Deploy to GitHub Pages
49
+ id: deployment
50
+ uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
@@ -0,0 +1,34 @@
1
+ name: Release
2
+ on:
3
+ workflow_dispatch: # manual trigger
4
+
5
+ permissions:
6
+ contents: read # for checkout
7
+
8
+ jobs:
9
+ release:
10
+ name: Release
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: write # to be able to publish a GitHub release
14
+ issues: write # to be able to comment on released issues
15
+ pull-requests: write # to be able to comment on released pull requests
16
+ id-token: write # to enable use of OIDC for npm provenance
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v3
20
+ with:
21
+ fetch-depth: 0
22
+ - name: Setup Node.js
23
+ uses: actions/setup-node@v3
24
+ with:
25
+ node-version: "lts/*"
26
+ - name: Install dependencies
27
+ run: yarn install --immutable
28
+ - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
29
+ run: npm audit signatures
30
+ - name: Release
31
+ env:
32
+ GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_GITHUB_TOKEN }}
33
+ NPM_TOKEN: ${{ secrets.SEMANTIC_RELEASE_NPM_TOKEN }}
34
+ run: npx semantic-release
@@ -0,0 +1,49 @@
1
+ name: "Lint PR"
2
+
3
+ on:
4
+ pull_request:
5
+ types:
6
+ - opened
7
+ - edited
8
+ - synchronize
9
+ - reopened
10
+
11
+ permissions:
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ main:
16
+ name: Validate PR title
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: amannn/action-semantic-pull-request@v5
20
+ id: lint_pr_title
21
+ env:
22
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23
+ - uses: marocchino/sticky-pull-request-comment@v2
24
+ # When the previous steps fails, the workflow would stop. By adding this
25
+ # condition you can continue the execution with the populated error message.
26
+ if: always() && (steps.lint_pr_title.outputs.error_message != null)
27
+ with:
28
+ header: pr-title-lint-error
29
+ message: |
30
+ Please ensure that your PR title follows [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). It looks like your proposed title needs to be adjusted.
31
+
32
+ ```
33
+ feat: Add variant `outlined` to Button component
34
+ feat!: Remove variant `filled` from Button component
35
+ bug: Fix TextField hover efffect.
36
+ ```
37
+ *Note: `!` denotes a breaking change*.
38
+
39
+ Action failure details:
40
+ ```
41
+ ${{ steps.lint_pr_title.outputs.error_message }}
42
+ ```
43
+
44
+ # Delete a previous comment when the issue has been resolved
45
+ - if: ${{ steps.lint_pr_title.outputs.error_message == null }}
46
+ uses: marocchino/sticky-pull-request-comment@v2
47
+ with:
48
+ header: pr-title-lint-error
49
+ delete: true
@@ -0,0 +1,90 @@
1
+ ---
2
+ # See https://pre-commit.com for more information
3
+ # See https://pre-commit.com/hooks.html for more hooks
4
+ ci:
5
+ skip:
6
+ # Because these are local hooks it seems like they won't easily run in pre-commit CI
7
+ - eslint
8
+ - prettier
9
+ repos:
10
+ - repo: https://github.com/pre-commit/pre-commit-hooks
11
+ rev: v5.0.0
12
+ hooks:
13
+ - id: trailing-whitespace
14
+ - id: end-of-file-fixer
15
+ exclude: ".hbs$"
16
+ - id: check-yaml
17
+ - id: check-added-large-files
18
+ exclude: "yarn.lock|.yarn/releases/.*|frontends/.yarn/releases/.*"
19
+ - id: check-merge-conflict
20
+ - id: check-toml
21
+ - id: debug-statements
22
+ - repo: local
23
+ hooks:
24
+ - id: prettier
25
+ name: prettier
26
+ entry: yarn fmt-fix
27
+ language: node
28
+ types_or:
29
+ [
30
+ javascript,
31
+ jsx,
32
+ ts,
33
+ tsx,
34
+ json,
35
+ scss,
36
+ sass,
37
+ css,
38
+ yaml,
39
+ markdown,
40
+ html,
41
+ ]
42
+ - repo: https://github.com/scop/pre-commit-shfmt
43
+ rev: v3.10.0-1
44
+ hooks:
45
+ - id: shfmt
46
+ - repo: https://github.com/adrienverge/yamllint.git
47
+ rev: v1.35.1
48
+ hooks:
49
+ - id: yamllint
50
+ args: [--format, parsable, -d, relaxed]
51
+ - repo: https://github.com/Yelp/detect-secrets
52
+ rev: v1.5.0
53
+ hooks:
54
+ - id: detect-secrets
55
+ args:
56
+ - --baseline
57
+ - .secrets.baseline
58
+ - --exclude-files
59
+ - .yarn/
60
+ - --exclude-files
61
+ - cassettes/
62
+ - --exclude-files
63
+ - test_json/
64
+ - --exclude-files
65
+ - ".*_test.py"
66
+ - --exclude-files
67
+ - "test_.*.py"
68
+ - --exclude-files
69
+ - poetry.lock
70
+ - --exclude-files
71
+ - yarn.lock
72
+ - --exclude-files
73
+ - ".*/generated/"
74
+ additional_dependencies: ["gibberish-detector"]
75
+ - repo: local
76
+ hooks:
77
+ - id: eslint
78
+ name: eslint
79
+ description: "Lint JS/TS files and apply automatic fixes"
80
+ entry: npx eslint --fix
81
+ language: node
82
+ types_or: [javascript, jsx, ts, tsx]
83
+ args: []
84
+ exclude: "(node_modules/|.yarn/)"
85
+ require_serial: false
86
+ - repo: https://github.com/shellcheck-py/shellcheck-py
87
+ rev: v0.10.0.1
88
+ hooks:
89
+ - id: shellcheck
90
+ args: ["--severity=warning"]
@@ -0,0 +1 @@
1
+ .yarn/
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "http://json.schemastore.org/prettierrc",
3
+ "semi": false
4
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "branches": ["+([0-9])?(.{+([0-9]),x}).x", "main"],
3
+ "plugins": [
4
+ [
5
+ "@semantic-release/commit-analyzer",
6
+ {
7
+ "preset": "conventionalcommits"
8
+ }
9
+ ],
10
+ [
11
+ "@semantic-release/release-notes-generator",
12
+ {
13
+ "preset": "conventionalcommits",
14
+ "presetConfig": {
15
+ "types": [
16
+ {
17
+ "type": "feat",
18
+ "section": "Features"
19
+ },
20
+ {
21
+ "type": "fix",
22
+ "section": "Bug Fixes"
23
+ },
24
+ {
25
+ "type": "chore",
26
+ "section": "Miscellaneous"
27
+ },
28
+ {
29
+ "type": "docs",
30
+ "section": "Miscellaneous"
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ ],
36
+ "@semantic-release/github",
37
+ "@semantic-release/npm"
38
+ ],
39
+ "repositoryUrl": "git@github.com:mitodl/smoot-design.git"
40
+ }
@@ -0,0 +1,113 @@
1
+ {
2
+ "version": "1.4.0",
3
+ "plugins_used": [
4
+ {
5
+ "name": "ArtifactoryDetector"
6
+ },
7
+ {
8
+ "name": "AWSKeyDetector"
9
+ },
10
+ {
11
+ "name": "AzureStorageKeyDetector"
12
+ },
13
+ {
14
+ "name": "Base64HighEntropyString",
15
+ "limit": 4.5
16
+ },
17
+ {
18
+ "name": "BasicAuthDetector"
19
+ },
20
+ {
21
+ "name": "CloudantDetector"
22
+ },
23
+ {
24
+ "name": "DiscordBotTokenDetector"
25
+ },
26
+ {
27
+ "name": "GitHubTokenDetector"
28
+ },
29
+ {
30
+ "name": "HexHighEntropyString",
31
+ "limit": 3.0
32
+ },
33
+ {
34
+ "name": "IbmCloudIamDetector"
35
+ },
36
+ {
37
+ "name": "IbmCosHmacDetector"
38
+ },
39
+ {
40
+ "name": "JwtTokenDetector"
41
+ },
42
+ {
43
+ "name": "KeywordDetector",
44
+ "keyword_exclude": ""
45
+ },
46
+ {
47
+ "name": "MailchimpDetector"
48
+ },
49
+ {
50
+ "name": "NpmDetector"
51
+ },
52
+ {
53
+ "name": "PrivateKeyDetector"
54
+ },
55
+ {
56
+ "name": "SendGridDetector"
57
+ },
58
+ {
59
+ "name": "SlackDetector"
60
+ },
61
+ {
62
+ "name": "SoftlayerDetector"
63
+ },
64
+ {
65
+ "name": "SquareOAuthDetector"
66
+ },
67
+ {
68
+ "name": "StripeDetector"
69
+ },
70
+ {
71
+ "name": "TwilioKeyDetector"
72
+ }
73
+ ],
74
+ "filters_used": [
75
+ {
76
+ "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
77
+ },
78
+ {
79
+ "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
80
+ "min_level": 2
81
+ },
82
+ {
83
+ "path": "detect_secrets.filters.heuristic.is_indirect_reference"
84
+ },
85
+ {
86
+ "path": "detect_secrets.filters.heuristic.is_likely_id_string"
87
+ },
88
+ {
89
+ "path": "detect_secrets.filters.heuristic.is_lock_file"
90
+ },
91
+ {
92
+ "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
93
+ },
94
+ {
95
+ "path": "detect_secrets.filters.heuristic.is_potential_uuid"
96
+ },
97
+ {
98
+ "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
99
+ },
100
+ {
101
+ "path": "detect_secrets.filters.heuristic.is_sequential_string"
102
+ },
103
+ {
104
+ "path": "detect_secrets.filters.heuristic.is_swagger_file"
105
+ },
106
+ {
107
+ "path": "detect_secrets.filters.heuristic.is_templated_secret"
108
+ }
109
+ ],
110
+ "results": {
111
+ },
112
+ "generated_at": "2024-11-12T20:55:24Z"
113
+ }
@@ -0,0 +1,46 @@
1
+ import { join, dirname } from "path"
2
+ import { StorybookConfig } from "@storybook/react-webpack5"
3
+ import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"
4
+
5
+ /**
6
+ * This function is used to resolve the absolute path of a package.
7
+ * It is needed in projects that use Yarn PnP or are set up within a monorepo.
8
+ */
9
+ function getAbsolutePath(value: string) {
10
+ return dirname(require.resolve(join(value, "package.json")))
11
+ }
12
+
13
+ const config: StorybookConfig = {
14
+ stories: ["../src/**/*.mdx", "../src/**/*.stories.tsx"],
15
+
16
+ framework: "@storybook/react-webpack5",
17
+
18
+ staticDirs: ["./public"],
19
+
20
+ addons: [
21
+ getAbsolutePath("@storybook/addon-links"),
22
+ getAbsolutePath("@storybook/addon-essentials"),
23
+ getAbsolutePath("@storybook/addon-interactions"),
24
+ getAbsolutePath("@storybook/addon-webpack5-compiler-swc"),
25
+ ],
26
+
27
+ webpackFinal: async (config) => {
28
+ if (!config?.resolve) {
29
+ throw new Error("Expected config.resolve to be defined")
30
+ }
31
+ config.resolve.plugins = [new TsconfigPathsPlugin()]
32
+ return config
33
+ },
34
+
35
+ typescript: {
36
+ /**
37
+ * Note: In theory react-docgen-typescript should work better with TS, but
38
+ * it seems not to work particularly well:
39
+ * - misses many props
40
+ * - doesn't get docstrings from the source component
41
+ */
42
+ // reactDocgen: "react-docgen-typescript",
43
+ },
44
+ }
45
+
46
+ export default config
@@ -0,0 +1 @@
1
+ <meta name="robots" content="noindex" />
@@ -0,0 +1,5 @@
1
+ <!--
2
+ Font files for Adobe Neue Haas Grotesk.
3
+ WARNING: This is linked to chudzick@mit.edu's Adobe account.
4
+ -->
5
+ <link rel="stylesheet" href="https://use.typekit.net/lbk1xay.css" />
@@ -0,0 +1,15 @@
1
+ import * as React from "react"
2
+ import { Preview } from "@storybook/react"
3
+ import { ThemeProvider } from "../src/components/ThemeProvider/ThemeProvider"
4
+
5
+ const preview: Preview = {
6
+ decorators: [
7
+ (Story) => (
8
+ <ThemeProvider>
9
+ <Story />
10
+ </ThemeProvider>
11
+ ),
12
+ ],
13
+ }
14
+
15
+ export default preview