├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── pull_request_template.md
└── workflows
│ ├── main.yaml
│ ├── pr.yaml
│ ├── pr_title.yaml
│ └── release.yaml
├── .gitignore
├── .husky
└── pre-commit
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .storybook
├── main.ts
├── package.json
└── preview.jsx
├── .vscode
└── settings.json
├── .yarnrc.yml
├── LICENSE
├── README.md
├── babel.config.cjs
├── colors
├── deepBlue.ts
├── elevation.ts
├── grayBlue.ts
├── index.ts
├── neon.ts
├── orange.ts
└── red.ts
├── components
├── AccessibleIcon
│ ├── AccessibleIcon.stories.tsx
│ ├── AccessibleIcon.tsx
│ └── index.tsx
├── Accordion
│ ├── Accordion.stories.tsx
│ ├── Accordion.themes.ts
│ ├── Accordion.tsx
│ └── index.ts
├── Alert
│ ├── Alert.stories.tsx
│ ├── Alert.tsx
│ └── index.ts
├── AriaTable
│ ├── AriaTable.stories.tsx
│ ├── AriaTable.test.tsx
│ ├── AriaTable.tsx
│ └── index.ts
├── Avatar
│ ├── Avatar.stories.tsx
│ ├── Avatar.tsx
│ └── index.ts
├── Badge
│ ├── Badge.stories.tsx
│ ├── Badge.test.tsx
│ ├── Badge.themes.ts
│ ├── Badge.tsx
│ └── index.ts
├── Blockquote
│ ├── Blockquote.stories.tsx
│ ├── Blockquote.tsx
│ └── index.ts
├── Box
│ ├── Box.stories.tsx
│ ├── Box.tsx
│ └── index.ts
├── Bubble
│ ├── Bubble.stories.tsx
│ ├── Bubble.tsx
│ └── index.ts
├── Button
│ ├── Button.stories.tsx
│ ├── Button.test.tsx
│ ├── Button.themes.ts
│ ├── Button.tsx
│ └── index.ts
├── ButtonSwitch
│ ├── ButtonSwitch.stories.tsx
│ ├── ButtonSwitch.themes.ts
│ ├── ButtonSwitch.tsx
│ └── index.ts
├── Card
│ ├── Card.stories.tsx
│ ├── Card.themes.ts
│ ├── Card.tsx
│ └── index.ts
├── Checkbox
│ ├── Checkbox.stories.tsx
│ ├── Checkbox.themes.ts
│ ├── Checkbox.tsx
│ └── index.ts
├── Container
│ ├── Container.stories.tsx
│ ├── Container.tsx
│ └── index.ts
├── DateTimePicker
│ ├── DateTimePicker.stories.tsx
│ ├── DateTimePicker.tsx
│ └── index.ts
├── DateTimePickerInput
│ ├── DateTimePickerInput.stories.tsx
│ ├── DateTimePickerInput.tsx
│ └── index.ts
├── Dialog
│ ├── Dialog.stories.tsx
│ ├── Dialog.themes.ts
│ ├── Dialog.tsx
│ └── index.ts
├── DropdownMenu
│ ├── DropdownMenu.stories.tsx
│ ├── DropdownMenu.tsx
│ ├── index.ts
│ └── styles.ts
├── Elevation
│ ├── Elevation.stories.tsx
│ ├── Elevation.tsx
│ └── index.ts
├── FaencyProvider.tsx
├── Flex
│ ├── Flex.stories.tsx
│ ├── Flex.tsx
│ └── index.ts
├── Grid
│ ├── Grid.stories.tsx
│ ├── Grid.tsx
│ └── index.ts
├── Heading
│ ├── Heading.stories.tsx
│ ├── Heading.test.tsx
│ ├── Heading.themes.ts
│ ├── Heading.tsx
│ └── index.ts
├── IconButton
│ ├── IconButton.stories.tsx
│ ├── IconButton.themes.ts
│ ├── IconButton.tsx
│ └── index.ts
├── Image
│ ├── Image.stories.tsx
│ ├── Image.tsx
│ └── index.ts
├── Input
│ ├── Input.stories.tsx
│ ├── Input.themes.ts
│ ├── Input.tsx
│ └── index.ts
├── Label
│ ├── Label.stories.tsx
│ ├── Label.tsx
│ └── index.ts
├── Link
│ ├── Link.stories.tsx
│ ├── Link.themes.ts
│ ├── Link.tsx
│ └── index.ts
├── List
│ ├── List.stories.tsx
│ ├── List.themes.ts
│ ├── List.tsx
│ └── index.ts
├── Navigation
│ ├── Navigation.stories.tsx
│ ├── Navigation.themes.ts
│ ├── Navigation.tsx
│ ├── NavigationItem.stories.tsx
│ └── index.ts
├── NavigationMenu
│ ├── NavigationMenu.stories.tsx
│ ├── NavigationMenu.tsx
│ └── index.ts
├── NavigationTree
│ ├── NavigationTree.stories.tsx
│ ├── NavigationTreeContainer.tsx
│ ├── NavigationTreeDrawer.tsx
│ ├── NavigationTreeItem.stories.tsx
│ ├── NavigationTreeItem.tsx
│ └── index.tsx
├── Overlay.tsx
├── Panel.tsx
├── Paragraph.tsx
├── Popover
│ ├── Popover.stories.tsx
│ ├── Popover.tsx
│ └── index.ts
├── Radio
│ ├── Radio.stories.tsx
│ ├── Radio.themes.ts
│ ├── Radio.tsx
│ └── index.ts
├── RadioAccordion
│ ├── RadioAccordion.stories.tsx
│ ├── RadioAccordion.tsx
│ └── index.tsx
├── Select
│ ├── Select.stories.tsx
│ ├── Select.themes.ts
│ ├── Select.tsx
│ └── index.ts
├── SidePanel
│ ├── SidePanel.stories.tsx
│ ├── SidePanel.themes.ts
│ ├── SidePanel.tsx
│ └── index.ts
├── Skeleton
│ ├── Skeleton.stories.tsx
│ ├── Skeleton.themes.ts
│ ├── Skeleton.tsx
│ └── index.ts
├── Switch
│ ├── Switch.stories.tsx
│ ├── Switch.themes.ts
│ ├── Switch.tsx
│ └── index.tsx
├── Table
│ ├── Table.stories.tsx
│ ├── Table.themes.ts
│ ├── Table.tsx
│ └── index.ts
├── Tabs
│ ├── Tabs.stories.tsx
│ ├── Tabs.tsx
│ └── index.ts
├── Text
│ ├── Text.stories.tsx
│ ├── Text.themes.ts
│ ├── Text.tsx
│ └── index.ts
├── TextField
│ ├── TextField.stories.tsx
│ ├── TextField.tsx
│ └── index.ts
├── Textarea
│ ├── Textarea.stories.tsx
│ ├── Textarea.themes.ts
│ ├── Textarea.tsx
│ └── index.tsx
├── Tooltip
│ ├── Tooltip.stories.tsx
│ ├── Tooltip.themes.ts
│ ├── Tooltip.tsx
│ └── index.ts
└── VisuallyHidden
│ ├── VisuallyHidden.stories.tsx
│ ├── VisuallyHidden.tsx
│ └── index.tsx
├── eslint.config.js
├── index.ts
├── jest.config.cjs
├── jest.setup.js
├── package.json
├── patches
└── @stitches+react+1.2.8.patch
├── rollup.config.js
├── stitches.config.ts
├── stories
├── Introduction.mdx
├── colors.stories.tsx
└── examples
│ ├── dashboard.stories.tsx
│ └── form.stories.tsx
├── tsconfig-rollup.json
├── tsconfig.jest.json
├── tsconfig.json
├── types
└── index.d.ts
├── utils
├── getPrimaryColorInfo.ts
├── ignoreArgType.ts
└── modifyVariantsForStory.ts
├── vite.config.ts
└── yarn.lock
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Create a report to help us improve.
3 | body:
4 | - type: checkboxes
5 | id: terms
6 | attributes:
7 | label: Welcome!
8 | description: |
9 | The issue tracker is for reporting bugs and feature requests only.
10 |
11 | All new/updated issues are triaged regularly by the maintainers.
12 | All issues closed by a bot are subsequently double-checked by the maintainers.
13 |
14 | DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
15 |
16 | options:
17 | - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/faency/issues) and didn't find any.
18 | required: true
19 |
20 | - type: textarea
21 | attributes:
22 | label: What did you do?
23 | description: |
24 | How to write a good bug report?
25 |
26 | - Respect the issue template as much as possible.
27 | - The title should be short and descriptive.
28 | - Explain the conditions which led you to report this issue: the context.
29 | - The context should lead to something, an idea or a problem that you’re facing.
30 | - Remain clear and concise.
31 | - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
32 | placeholder: What did you do?
33 | validations:
34 | required: true
35 |
36 | - type: textarea
37 | attributes:
38 | label: What were you expecting?
39 | placeholder: What were you expecting?
40 | validations:
41 | required: true
42 |
43 | - type: textarea
44 | attributes:
45 | label: What version are you using?
46 | description: |
47 | `latest` is not considered as a valid version.
48 | placeholder: Paste your output here.
49 | validations:
50 | required: true
51 |
52 | - type: textarea
53 | attributes:
54 | label: What is your environment & configuration?
55 | description: arguments, toml, provider, platform, ...
56 | placeholder: Add information here.
57 | value: |
58 | ```yaml
59 | # (paste your configuration here)
60 | ```
61 |
62 | Add more configuration information here.
63 | validations:
64 | required: true
65 |
66 | - type: textarea
67 | attributes:
68 | label: If applicable, please paste the log output in DEBUG level
69 | description: "`--log.level=DEBUG` switch."
70 | placeholder: Paste your output here.
71 | validations:
72 | required: false
73 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Traefik Community Support
4 | url: https://community.traefik.io/
5 | about: If you have a question, or are looking for advice, please post on our Discuss forum! The community loves to chime in to help. Happy Coding!
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea for this project.
3 | body:
4 | - type: checkboxes
5 | id: terms
6 | attributes:
7 | label: Welcome!
8 | description: |
9 | The issue tracker is for reporting bugs and feature requests only.
10 |
11 | DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
12 | options:
13 | - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/faency/issues) and didn't find any.
14 | required: true
15 |
16 | - type: textarea
17 | attributes:
18 | label: What did you expect to see?
19 | description: |
20 | How to write a good issue?
21 |
22 | - Respect the issue template as much as possible.
23 | - The title should be short and descriptive.
24 | - Explain the conditions which led you to report this issue: the context.
25 | - The context should lead to something, an idea or a problem that you’re facing.
26 | - Remain clear and concise.
27 | - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
28 | placeholder: What did you expect to see?
29 | validations:
30 | required: true
31 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | ## Description
8 |
9 |
13 |
14 | ## Preview
15 |
16 |
19 |
20 | ## Dependency changes
21 |
22 |
26 |
27 | | Dependency | Version | State |
28 | | ---------- | -------------- | ------- |
29 | | lib1 | 0.1.1 | Added |
30 | | lib2 | 0.0.1 -> 1.0.1 | Updated |
31 | | lib3 | 2.0.1 | Removed |
32 |
33 | - lib1 is responsible for...
34 |
35 | ## Breaking changes
36 |
37 |
41 |
42 | ## How to test?
43 |
44 |
47 |
48 | 1. Go to the page ...
49 | 2. Click on ...
50 |
51 | ## Good PR checkboxes
52 |
53 | - [ ] Change has been tested
54 | - [ ] Added/Updated tests
55 | - [ ] Added/Updated stories
56 | - [ ] PR follows [conventions](https://github.com/traefik/faency#how-to-contribute)
57 | - [ ] Labels are set
58 | - [ ] Project is linked
59 |
60 | ## Good Review checkboxes
61 |
62 |
63 | ℹ️ Copy the snippet and paste in the review field to fill it
64 |
65 | ```markdown
66 | - [ ] I've tested the changes
67 | - [ ] I've agreed on the unit tests (soon to come)
68 | - [ ] I've checked the stories
69 | - [ ] I've read the code and understood it
70 | - [ ] I don't have any more questions
71 | - [ ] I've described any optional improvements
72 | - [ ] I checked PR follows [conventions](https://github.com/traefik/faency#how-to-contribute)
73 | ```
74 |
75 |
76 |
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: Main
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | permissions:
9 | contents: read
10 | pages: write
11 | id-token: write
12 |
13 | jobs:
14 | faency:
15 | name: Deploy
16 | if: github.repository == 'traefik/faency'
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Checkout code
21 | uses: actions/checkout@v4
22 |
23 | - name: enable corepack
24 | run: corepack enable
25 |
26 | - name: Setup node
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version-file: .nvmrc
30 | cache: yarn
31 |
32 | - name: Config git
33 | run: |
34 | git config --local user.email "30906710+traefiker@users.noreply.github.com"
35 | git config --local user.name "traefiker"
36 |
37 | - name: Deploy
38 | uses: bitovi/github-actions-storybook-to-github-pages@v1.0.3
39 | env:
40 | NODE_ENV: production
41 | with:
42 | install_command: yarn workspaces focus --all --production && yarn patch-package
43 | build_command: yarn build-storybook
44 | path: storybook-static
45 | checkout: false
46 |
--------------------------------------------------------------------------------
/.github/workflows/pr.yaml:
--------------------------------------------------------------------------------
1 | name: Pull Request Lint
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | faency:
8 | name: Test, lint and build
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v4
14 |
15 | - name: enable corepack
16 | run: corepack enable
17 |
18 | - name: Setup node
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version-file: .nvmrc
22 | cache: yarn
23 |
24 | - name: Install
25 | run: yarn install
26 |
27 | - name: Patch
28 | run: yarn patch-package
29 |
30 | - name: Lint
31 | run: yarn lint
32 |
33 | - name: Test
34 | run: yarn test:ci
35 |
36 | - name: Build
37 | run: yarn build
38 |
--------------------------------------------------------------------------------
/.github/workflows/pr_title.yaml:
--------------------------------------------------------------------------------
1 | name: Pull Request Title
2 |
3 | on:
4 | pull_request_target:
5 | types: ['opened', 'reopened', 'edited', 'synchronize']
6 |
7 | permissions:
8 | pull-requests: read
9 |
10 | jobs:
11 | faency:
12 | name: Lint PR Title
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Semantic pull request
17 | env:
18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19 | uses: amannn/action-semantic-pull-request@v5
20 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | faency:
10 | name: Release
11 | if: github.repository == 'traefik/faency'
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 |
17 | - name: enable corepack
18 | run: corepack enable
19 |
20 | - name: Setup Node.js
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version-file: .nvmrc
24 | cache: yarn
25 |
26 | - name: Install dependencies
27 | run: yarn workspaces focus --all --production
28 |
29 | - name: Patch
30 | run: yarn patch-package
31 |
32 | - name: Build
33 | run: yarn build
34 |
35 | - name: Release
36 | run: yarn semantic-release
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
37 | # https://github.com/hashicorp/next-mdx-enhanced#installation
38 | .mdx-data
39 |
40 | /dist
41 |
42 | /storybook-static
43 |
44 | .parcel-cache
45 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn pre-commit
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | out
4 | .mdx-data
5 | .parcel-cache
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": true,
4 | "printWidth": 100
5 | }
6 |
--------------------------------------------------------------------------------
/.storybook/main.ts:
--------------------------------------------------------------------------------
1 | import type { StorybookConfig } from '@storybook/react-vite';
2 |
3 | const config: StorybookConfig = {
4 | stories: [
5 | '../stories/**/*.mdx',
6 | '../stories/**/*.stories.@(js|jsx|ts|tsx)',
7 | '../components/**/*.stories.@(js|jsx|ts|tsx)',
8 | ],
9 |
10 | addons: [
11 | '@storybook/addon-links',
12 | {
13 | name: '@storybook/addon-essentials',
14 | options: {
15 | backgrounds: false,
16 | },
17 | },
18 | 'storybook-dark-mode',
19 | ],
20 |
21 | core: {
22 | builder: '@storybook/builder-vite',
23 | },
24 |
25 | framework: {
26 | name: '@storybook/react-vite',
27 | options: {},
28 | },
29 |
30 | typescript: {
31 | reactDocgen: 'react-docgen-typescript',
32 | },
33 | };
34 |
35 | export default config;
36 |
--------------------------------------------------------------------------------
/.storybook/package.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.storybook/preview.jsx:
--------------------------------------------------------------------------------
1 | import { DocsContainer } from '@storybook/addon-docs';
2 | import { addons } from '@storybook/preview-api';
3 | import { themes } from '@storybook/theming';
4 | import React from 'react';
5 | import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
6 |
7 | import { globalCss } from '../';
8 | import { FaencyProvider } from '../components/FaencyProvider';
9 | import { darkTheme, lightTheme } from '../stitches.config';
10 |
11 | const channel = addons.getChannel();
12 |
13 | export const parameters = {
14 | controls: {
15 | matchers: {
16 | color: /(background|color)$/i,
17 | date: /Date$/,
18 | },
19 | },
20 | darkMode: {
21 | stylePreview: true,
22 | },
23 | docs: {
24 | container: (context) => {
25 | // eslint-disable-next-line react-hooks/rules-of-hooks
26 | const [isDark, setDark] = React.useState();
27 |
28 | // eslint-disable-next-line react-hooks/rules-of-hooks
29 | React.useEffect(() => {
30 | channel.on(DARK_MODE_EVENT_NAME, setDark);
31 | return () => channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
32 | }, [setDark]);
33 |
34 | const props = {
35 | ...context,
36 | theme: isDark ? themes.dark : themes.light,
37 | };
38 |
39 | return React.createElement(DocsContainer, props);
40 | },
41 | },
42 | };
43 |
44 | const globalStyle = globalCss({
45 | body: {
46 | bc: '$contentBg',
47 | },
48 | });
49 |
50 | export const decorators = [
51 | (renderStory) => {
52 | React.useEffect(() => {
53 | darkTheme('neon').toString();
54 | lightTheme('neon').toString();
55 | }, []);
56 |
57 | return (
58 |
59 | {globalStyle()}
60 | {renderStory()}
61 |
62 | );
63 | },
64 | ];
65 |
66 | export const tags = ['autodocs'];
67 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "cSpell.words": ["faency"],
4 | "typescript.tsdk": "node_modules/typescript/lib",
5 | "editor.tabSize": 2
6 | }
7 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['@babel/plugin-transform-react-pure-annotations'],
3 | presets: [
4 | [
5 | '@babel/preset-env',
6 | {
7 | targets: {
8 | node: 'current',
9 | },
10 | },
11 | ],
12 | [
13 | '@babel/preset-react',
14 | {
15 | runtime: 'automatic',
16 | },
17 | ],
18 | '@babel/preset-typescript',
19 | ],
20 | };
21 |
--------------------------------------------------------------------------------
/colors/deepBlue.ts:
--------------------------------------------------------------------------------
1 | export const deepBlue = {
2 | deepBlue1: 'hsl(240, 14.0%, 99.0%)',
3 | deepBlue2: 'hsl(210, 90.0%, 96.1%)',
4 | deepBlue3: 'hsl(210, 67.8%, 94.6%)',
5 | deepBlue4: 'hsl(210, 49.1%, 92.2%)',
6 | deepBlue5: 'hsl(210, 34.8%, 88.1%)',
7 | deepBlue6: 'hsl(209, 24.6%, 81.0%)',
8 | deepBlue7: 'hsl(209, 17.6%, 67.9%)',
9 | deepBlue8: 'hsl(208, 16.0%, 36.0%)',
10 | deepBlue9: 'hsl(208, 24.0%, 27.0%)',
11 | deepBlue10: 'hsl(208, 40.0%, 18.0%)',
12 | deepBlue11: 'hsl(209, 88.0%, 9.0%)',
13 | deepBlue12: 'hsl(208, 89.0%, 7.0%)',
14 | };
15 |
16 | export const deepBlueA = {
17 | deepBlueA1: 'hsl(200, 94.9%, 38.7%, 0.016)',
18 | deepBlueA2: 'hsl(210, 98.6%, 47.7%, 0.075)',
19 | deepBlueA3: 'hsl(212, 97.9%, 41.8%, 0.091)',
20 | deepBlueA4: 'hsl(210, 99.1%, 33.5%, 0.118)',
21 | deepBlueA5: 'hsl(209, 99.5%, 25.7%, 0.161)',
22 | deepBlueA6: 'hsl(209, 98.5%, 19.4%, 0.236)',
23 | deepBlueA7: 'hsl(209, 99.1%, 15.2%, 0.377)',
24 | deepBlueA8: 'hsl(207 99.7% 8.3% / 0.682)',
25 | deepBlueA9: 'hsl(207, 98.6%, 8.2%, 0.797)',
26 | deepBlueA10: 'hsl(208, 98.9%, 8.0%, 0.891)',
27 | deepBlueA11: 'hsl(210, 100%, 7.6%, 0.980)',
28 | deepBlueA12: 'hsl(211, 100%, 5.8%, 0.980)',
29 | };
30 |
31 | export const deepBlueDark = {
32 | deepBlue1: 'hsl(208, 53.0%, 4.0%)',
33 | deepBlue2: 'hsl(209, 88.0%, 7.0%)',
34 | deepBlue3: 'hsl(209, 88.0%, 9.0%)',
35 | deepBlue4: 'hsl(209, 45.6%, 16.0%)',
36 | deepBlue5: 'hsl(209, 44.5%, 17.7%)',
37 | deepBlue6: 'hsl(208, 43.4%, 19.6%)',
38 | deepBlue7: 'hsl(208, 42.2%, 22.6%)',
39 | deepBlue8: 'hsl(208, 40.5%, 29.0%)',
40 | deepBlue9: 'hsl(208, 40.0%, 58.0%)',
41 | deepBlue10: 'hsl(209, 40.0%, 68.0%)',
42 | deepBlue11: 'hsl(209, 40.0%, 78.0%)',
43 | deepBlue12: 'hsl(209, 60.0%, 94.0%)',
44 | };
45 |
46 | export const deepBlueDarkA = {
47 | deepBlueA1: 'hsl(0, 0%, 0%, 0)',
48 | deepBlueA2: 'hsl(216 100% 49.6% / 0.076)',
49 | deepBlueA3: 'hsl(210 100% 50% / 0.113)',
50 | deepBlueA4: 'hsl(210, 99.9%, 69.5%, 0.180)',
51 | deepBlueA5: 'hsl(209, 98.5%, 69.8%, 0.206)',
52 | deepBlueA6: 'hsl(209, 99.1%, 70.0%, 0.235)',
53 | deepBlueA7: 'hsl(208, 99.0%, 70.7%, 0.277)',
54 | deepBlueA8: 'hsl(208, 99.3%, 71.6%, 0.369)',
55 | deepBlueA9: 'hsl(208, 99.6%, 77.7%, 0.733)',
56 | deepBlueA10: 'hsl(210, 100%, 84.5%, 0.795)',
57 | deepBlueA11: 'hsl(208, 99.7%, 90.0%, 0.858)',
58 | deepBlueA12: 'hsl(210, 99.7%, 96.4%, 0.975)',
59 | };
60 |
--------------------------------------------------------------------------------
/colors/elevation.ts:
--------------------------------------------------------------------------------
1 | export const elevation = {
2 | '00dp': 'hsl(240, 4%, 95%)',
3 | '01dp': 'hsl(0, 0%, 99%)',
4 | '02dp': 'hsl(0, 0%, 99%)',
5 | '03dp': 'hsl(0, 0%, 99%)',
6 | '04dp': 'hsl(0, 0%, 99%)',
7 | '05dp': 'hsl(0, 0%, 99%)',
8 | };
9 |
10 | export const elevationDark = {
11 | '00dp': 'hsl(210, 68%, 9%)',
12 | '01dp': 'hsl(208, 37%, 15%)',
13 | '02dp': 'hsl(207, 32%, 17%)',
14 | '03dp': 'hsl(209, 28%, 19%)',
15 | '04dp': 'hsl(209, 23%, 21%)',
16 | '05dp': 'hsl(209, 21%, 23%)',
17 | };
18 |
--------------------------------------------------------------------------------
/colors/grayBlue.ts:
--------------------------------------------------------------------------------
1 | export const grayBlue = {
2 | grayBlue1: 'hsl(208 9.0% 99.0%)',
3 | grayBlue2: 'hsl(210 10.0% 96.1%)',
4 | grayBlue3: 'hsl(210 9.9% 95.7%)',
5 | grayBlue4: 'hsl(210 9.8% 94.9%)',
6 | grayBlue5: 'hsl(210 9.7% 93.1%)',
7 | grayBlue6: 'hsl(210 9.6% 89.8%)',
8 | grayBlue7: 'hsl(210 9.4% 83.2%)',
9 | grayBlue8: 'hsl(208, 9.0%, 80.0%)',
10 | grayBlue9: 'hsl(208, 9.0%, 73.0%)',
11 | grayBlue10: 'hsl(208 9.0% 53.0%)',
12 | grayBlue11: 'hsl(208 9.0% 33.0%)',
13 | grayBlue12: 'hsl(208 9.0% 13.0%)',
14 | };
15 |
16 | export const grayBlueA = {
17 | grayBlueA1: 'hsl(240 89.3% 18.3% / 0.012)',
18 | grayBlueA2: 'hsl(182 89% 7.1% / 0.032)',
19 | grayBlueA3: 'hsl(210 80.6% 10.1% / 0.048)',
20 | grayBlueA4: 'hsl(210 97.6% 7.3% / 0.055)',
21 | grayBlueA5: 'hsl(220 92.3% 8.5% / 0.075)',
22 | grayBlueA6: 'hsl(204 97.3% 8.8% / 0.114)',
23 | grayBlueA7: 'hsl(210 95.8% 8.9% / 0.185)',
24 | grayBlueA8: 'hsl(207 96.4% 8.9% / 0.211)',
25 | grayBlueA9: 'hsl(205 96.4% 8.3% / 0.295)',
26 | grayBlueA10: 'hsl(207 99.4% 8.4% / 0.514)',
27 | grayBlueA11: 'hsl(208 96.8% 4.3% / 0.699)',
28 | grayBlueA12: 'hsl(210 94.8% 1.4% / 0.883)',
29 | };
30 |
31 | export const grayBlueDark = {
32 | grayBlue1: 'hsl(209 40.0% 2.0%)',
33 | grayBlue2: 'hsl(210 40.0% 3.9%)',
34 | grayBlue3: 'hsl(209 38.1% 4.8%)',
35 | grayBlue4: 'hsl(208 35.4% 5.8%)',
36 | grayBlue5: 'hsl(208 32.4% 6.9%)',
37 | grayBlue6: 'hsl(209 31.0% 8.2%)',
38 | grayBlue7: 'hsl(209, 38.0%, 12.0%)',
39 | grayBlue8: 'hsl(209, 38.0%, 25.0%)',
40 | grayBlue9: 'hsl(208, 11.0%, 45.0%)',
41 | grayBlue10: 'hsl(208 11.0% 68.0%)',
42 | grayBlue11: 'hsl(208 11.0% 78.0%)',
43 | grayBlue12: 'hsl(208 11.0% 88.0%)',
44 | };
45 |
46 | export const grayBlueDarkA = {
47 | grayBlueA1: 'hsl(0 0% 0% / 0)',
48 | grayBlueA2: 'hsl(210 91.5% 69.6% / 0.029)',
49 | grayBlueA3: 'hsl(216 93.9% 73.7% / 0.041)',
50 | grayBlueA4: 'hsl(210 95.6% 76.0% / 0.053)',
51 | grayBlueA5: 'hsl(206 96.8% 77.4% / 0.065)',
52 | grayBlueA6: 'hsl(207 98.1% 77% / 0.081)',
53 | grayBlueA7: 'hsl(208 97.8% 72.4% / 0.142)',
54 | grayBlueA8: 'hsl(209 99.6% 72.7% / 0.327)',
55 | grayBlueA9: 'hsl(206 99.7% 90.7% / 0.484)',
56 | grayBlueA10: 'hsl(206 99.0% 95.3% / 0.706)',
57 | grayBlueA11: 'hsl(210 97.4% 97.2% / 0.799)',
58 | grayBlueA12: 'hsl(205 93.8% 98.5% / 0.892)',
59 | };
60 |
--------------------------------------------------------------------------------
/colors/index.ts:
--------------------------------------------------------------------------------
1 | import * as elevation from './elevation';
2 | import * as deepBlue from './deepBlue';
3 | import * as grayBlue from './grayBlue';
4 | import * as neon from './neon';
5 | import * as orange from './orange';
6 | import * as red from './red';
7 |
8 | import {
9 | blackA,
10 | blue,
11 | blueA,
12 | blueDark,
13 | blueDarkA,
14 | gray,
15 | grayA,
16 | grayDark,
17 | grayDarkA,
18 | green,
19 | greenA,
20 | greenDark,
21 | greenDarkA,
22 | purple,
23 | purpleA,
24 | purpleDark,
25 | purpleDarkA,
26 | slate,
27 | slateA,
28 | slateDark,
29 | slateDarkA,
30 | whiteA,
31 | } from '@radix-ui/colors';
32 |
33 | const customColors = {
34 | ...elevation,
35 | ...deepBlue,
36 | ...grayBlue,
37 | ...neon,
38 | ...orange,
39 | ...red,
40 | };
41 |
42 | export type ColorMap = {
43 | [key: string]: string;
44 | };
45 |
46 | export const lightColors: ColorMap = Object.entries(customColors)
47 | .filter(([colorName]) => !colorName.includes('Dark'))
48 | .reduce(
49 | (acc, [_, colors]) => ({
50 | ...acc,
51 | ...colors,
52 | }),
53 | {
54 | // radix colors
55 | ...gray,
56 | ...grayA,
57 | ...blue,
58 | ...blueA,
59 | ...green,
60 | ...greenA,
61 | ...slate,
62 | ...slateA,
63 | ...purple,
64 | ...purpleA,
65 | ...whiteA,
66 | ...blackA,
67 | }
68 | );
69 |
70 | export const darkColors: ColorMap = Object.entries(customColors)
71 | .filter(([colorName]) => colorName.includes('Dark'))
72 | .reduce(
73 | (acc, [_, colors]) => ({
74 | ...acc,
75 | ...colors,
76 | }),
77 | {
78 | // radix colors
79 | ...grayDark,
80 | ...grayDarkA,
81 | ...blueDark,
82 | ...blueDarkA,
83 | ...greenDark,
84 | ...greenDarkA,
85 | ...slateDark,
86 | ...slateDarkA,
87 | ...purpleDark,
88 | ...purpleDarkA,
89 | ...whiteA,
90 | ...blackA,
91 | }
92 | );
93 |
94 | export * as elevation from './elevation';
95 | export * as deepBlue from './deepBlue';
96 | export * as grayBlue from './grayBlue';
97 | export * as neon from './neon';
98 | export * as orange from './orange';
99 | export * as red from './red';
100 |
101 | export default customColors;
102 |
--------------------------------------------------------------------------------
/colors/neon.ts:
--------------------------------------------------------------------------------
1 | export const neon = {
2 | neon1: 'hsl(68, 60.0%, 99.0%)',
3 | neon2: 'hsl(67, 90.0%, 96.1%)',
4 | neon3: 'hsl(67, 86.3%, 94.4%)',
5 | neon4: 'hsl(67, 82.5%, 90.5%)',
6 | neon5: 'hsl(67, 79.8%, 82.3%)',
7 | neon6: 'hsl(68, 78.5%, 69.8%)',
8 | neon7: 'hsl(68, 78.5%, 60.6%)',
9 | neon8: 'hsl(68, 53.0%, 48.0%)',
10 | neon9: 'hsl(68, 53.0%, 36.0%)',
11 | neon10: 'hsl(68, 53.0%, 24.0%)',
12 | neon11: 'hsl(68, 53.0%, 18.0%)',
13 | neon12: 'hsl(68, 53.0%, 10.0%)',
14 | };
15 |
16 | export const neonA = {
17 | neonA1: 'hsl(60, 94.9%, 38.7%, 0.016)',
18 | neonA2: 'hsl(67, 98.6%, 47.7%, 0.075)',
19 | neonA3: 'hsl(67, 99.8%, 46.4%, 0.106)',
20 | neonA4: 'hsl(68, 99.4%, 45.6%, 0.173)',
21 | neonA5: 'hsl(68, 99.8%, 44.5%, 0.318)',
22 | neonA6: 'hsl(68, 99.7%, 43.9%, 0.538)',
23 | neonA7: 'hsl(68, 100%, 43.9%, 0.702)',
24 | neonA8: 'hsl(68, 99.7%, 33.3%, 0.762)',
25 | neonA9: 'hsl(68, 99.7%, 22.9%, 0.832)',
26 | neonA10: 'hsl(68, 99.4%, 14.5%, 0.887)',
27 | neonA11: 'hsl(68, 99.7%, 10.3%, 0.914)',
28 | neonA12: 'hsl(69, 99.9%, 5.6%, 0.953)',
29 | };
30 |
31 | export const neonDark = {
32 | neon1: 'hsl(68, 53.0%, 4.0%)',
33 | neon2: 'hsl(68, 53.3%, 5.9%)',
34 | neon3: 'hsl(72, 47.3%, 10.0%)',
35 | neon4: 'hsl(73, 52.0%, 13.2%)',
36 | neon5: 'hsl(73, 58.3%, 16.3%)',
37 | neon6: 'hsl(72, 65.6%, 20.0%)',
38 | neon7: 'hsl(70, 76.7%, 24.7%)',
39 | neon8: 'hsl(68, 100%, 29.0%)',
40 | neon9: 'hsl(68, 79.0%, 60.0%)',
41 | neon10: 'hsl(68, 79.0%, 68.0%)',
42 | neon11: 'hsl(68, 79.0%, 78.0%)',
43 | neon12: 'hsl(68, 79.0%, 88.0%)',
44 | };
45 |
46 | export const neonDarkA = {
47 | neonA1: 'hsl(0, 0%, 0%, 0)',
48 | neonA2: 'hsl(61, 94.0%, 62.9%, 0.030)',
49 | neonA3: 'hsl(73, 97.1%, 67.4%, 0.093)',
50 | neonA4: 'hsl(72, 99.0%, 65.5%, 0.147)',
51 | neonA5: 'hsl(74, 99.1%, 62.0%, 0.210)',
52 | neonA6: 'hsl(72, 99.6%, 59.8%, 0.285)',
53 | neonA7: 'hsl(70, 99.7%, 55.8%, 0.398)',
54 | neonA8: 'hsl(68, 100%, 49.9%, 0.553)',
55 | neonA9: 'hsl(68, 99.7%, 65.3%, 0.913)',
56 | neonA10: 'hsl(68, 100%, 72.9%, 0.929)',
57 | neonA11: 'hsl(68, 99.9%, 81.9%, 0.950)',
58 | neonA12: 'hsl(69, 99.9%, 90.2%, 0.975)',
59 | };
60 |
--------------------------------------------------------------------------------
/colors/orange.ts:
--------------------------------------------------------------------------------
1 | export const orange = {
2 | orange1: 'hsl(24 70.0% 99.0%)',
3 | orange2: 'hsl(27 81.8% 97.8%)',
4 | orange3: 'hsl(27 98.5% 95.4%)',
5 | orange4: 'hsl(27 100% 92.2%)',
6 | orange5: 'hsl(27 100% 88.1%)',
7 | orange6: 'hsl(26 100% 82.5%)',
8 | orange7: 'hsl(25 99.2% 74.8%)',
9 | orange8: 'hsl(24 94.6% 63.9%)',
10 | orange9: 'hsl(24 94.0% 50.0%)',
11 | orange10: 'hsl(24 100% 46.0%)',
12 | orange11: 'hsl(24 100% 37.0%)',
13 | orange12: 'hsl(24 60.0% 17.0%)',
14 | };
15 |
16 | export const orangeA = {
17 | orangeA1: 'hsl(20 94.9% 38.7% / 0.016)',
18 | orangeA2: 'hsl(27 95.7% 46.1% / 0.040)',
19 | orangeA3: 'hsl(26 100% 50.4% / 0.091)',
20 | orangeA4: 'hsl(27 100% 50.0% / 0.157)',
21 | orangeA5: 'hsl(28 100% 50.2% / 0.240)',
22 | orangeA6: 'hsl(26 100% 50.1% / 0.350)',
23 | orangeA7: 'hsl(25 100% 49.6% / 0.502)',
24 | orangeA8: 'hsl(24 100% 48.6% / 0.702)',
25 | orangeA9: 'hsl(24 99.9% 48.4% / 0.969)',
26 | orangeA10: 'hsl(23 100% 46.4% / 0.980)',
27 | orangeA11: 'hsl(23 100% 36.8% / 0.980)',
28 | orangeA12: 'hsl(15 99.4% 11.0% / 0.934)',
29 | };
30 |
31 | export const orangeDark = {
32 | orange1: 'hsl(35 100% 7.2%)',
33 | orange2: 'hsl(35 100% 8.0%)',
34 | orange3: 'hsl(34 87.5% 11.4%)',
35 | orange4: 'hsl(33 84.1% 14.0%)',
36 | orange5: 'hsl(33 83.6% 16.6%)',
37 | orange6: 'hsl(33 85.7% 19.8%)',
38 | orange7: 'hsl(34 91.5% 23.9%)',
39 | orange8: 'hsl(35 100% 29.0%)',
40 | orange9: 'hsl(24 94.0% 50.0%)',
41 | orange10: 'hsl(35, 100%, 50.0%)',
42 | orange11: 'hsl(35 100% 62.2%)',
43 | orange12: 'hsl(24 97.0% 93.2%)',
44 | };
45 |
46 | export const orangeDarkA = {
47 | orangeA1: 'hsl(0 0% 100% / 0)',
48 | orangeA2: 'hsl(30 100% 50.2% / 0.064)',
49 | orangeA3: 'hsl(33 100% 50.2% / 0.116)',
50 | orangeA4: 'hsl(33 100% 50.2% / 0.176)',
51 | orangeA5: 'hsl(33 99.4% 14.4% / 0.973)',
52 | orangeA6: 'hsl(33 99.5% 17.6% / 0.973)',
53 | orangeA7: 'hsl(34 100% 22.4% / 0.98)',
54 | orangeA8: 'hsl(34 100% 28.6% / 0.98)',
55 | orangeA9: 'hsl(24 99.8% 48.4% / 0.969)',
56 | orangeA10: 'hsl(35 100% 50% / 0.98)',
57 | orangeA11: 'hsl(36 100% 62.4% / 0.98)',
58 | orangeA12: 'hsl(26 100% 94.2% / 0.98)',
59 | };
60 |
--------------------------------------------------------------------------------
/colors/red.ts:
--------------------------------------------------------------------------------
1 | export const red = {
2 | red1: 'hsl(347 100% 99.0%)',
3 | red2: 'hsl(348 100% 99.0%)',
4 | red3: 'hsl(348 99.7% 97.2%)',
5 | red4: 'hsl(348 92.2% 94.5%)',
6 | red5: 'hsl(348 83.3% 90.1%)',
7 | red6: 'hsl(348 75.7% 84.0%)',
8 | red7: 'hsl(347 71.9% 78.7%)',
9 | red8: 'hsl(347 100% 67.0%)',
10 | red9: 'hsl(347, 100%, 60.0%)',
11 | red10: 'hsl(347 77.4% 52.4%)',
12 | red11: 'hsl(347, 80.0%, 40.0%)',
13 | red12: 'hsl(347 60.0% 15.0%)',
14 | };
15 |
16 | export const redA = {
17 | redA1: 'hsl(348 100% 51.0% / 0.020)',
18 | redA2: 'hsl(348 100% 51.0% / 0.020)',
19 | redA3: 'hsl(347 100% 50.1% / 0.055)',
20 | redA4: 'hsl(348 99.8% 48.2% / 0.106)',
21 | redA5: 'hsl(349 99.3% 45.8% / 0.181)',
22 | redA6: 'hsl(347 99.5% 43.2% / 0.283)',
23 | redA7: 'hsl(347 99.8% 42.0% / 0.365)',
24 | redA8: 'hsl(347 100% 50% / 0.641)',
25 | redA9: 'hsl(347 100% 50.0% / 0.800)',
26 | redA10: 'hsl(347 99.8% 43.8% / 0.844)',
27 | redA11: 'hsl(347 99.9% 34.9% / 0.922)',
28 | redA12: 'hsl(347 99.1% 9.7% / 0.942)',
29 | };
30 |
31 | export const redDark = {
32 | red1: 'hsl(347 23.0% 10.0%)',
33 | red2: 'hsl(346 34.4% 12.0%)',
34 | red3: 'hsl(345 42.6% 16.5%)',
35 | red4: 'hsl(345 46.5% 19.3%)',
36 | red5: 'hsl(346 49.9% 22.1%)',
37 | red6: 'hsl(346 53.9% 26.2%)',
38 | red7: 'hsl(346 59.0% 32.3%)',
39 | red8: 'hsl(347 64.8% 41.2%)',
40 | red9: 'hsl(347, 100%, 60.0%)',
41 | red10: 'hsl(347, 100%, 67.0%)',
42 | red11: 'hsl(347 100% 72.0%)',
43 | red12: 'hsl(347 100% 98.0%)',
44 | };
45 |
46 | export const redDarkA = {
47 | redA1: 'hsl(0 0% 0% / 0)',
48 | redA2: 'hsl(342 98.5% 53.6% / 0.045)',
49 | redA3: 'hsl(344 99.1% 59.8% / 0.130)',
50 | redA4: 'hsl(343 98.8% 60.1% / 0.184)',
51 | redA5: 'hsl(346 99.6% 60.5% / 0.237)',
52 | redA6: 'hsl(345 99.6% 60.5% / 0.322)',
53 | redA7: 'hsl(346 99.7% 60.0% / 0.447)',
54 | redA8: 'hsl(347 100% 59.2% / 0.634)',
55 | redA9: 'hsl(347 100% 60.1% / 0.980)',
56 | redA10: 'hsl(347 100% 67.3% / 0.980)',
57 | redA11: 'hsl(347 100% 72.3% / 0.980)',
58 | redA12: 'hsl(338 100% 98.9% / 0.980)',
59 | };
60 |
--------------------------------------------------------------------------------
/components/AccessibleIcon/AccessibleIcon.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as Icons from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { Flex } from '../Flex';
6 | import { IconButton } from '../IconButton';
7 | import { AccessibleIcon } from './AccessibleIcon';
8 |
9 | const Component: Meta = {
10 | title: 'Components/AccessibleIcon',
11 | component: AccessibleIcon,
12 | tags: ['autodocs'],
13 | };
14 |
15 | export const Basic: StoryFn = () => (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 |
31 | export default Component;
32 |
--------------------------------------------------------------------------------
/components/AccessibleIcon/AccessibleIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as AccessibleIconPrimitive from '@radix-ui/react-accessible-icon';
2 |
3 | export const AccessibleIcon = AccessibleIconPrimitive.Root;
4 |
--------------------------------------------------------------------------------
/components/AccessibleIcon/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './AccessibleIcon';
2 |
--------------------------------------------------------------------------------
/components/Accordion/Accordion.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | accordionBackground: Property.Color;
9 | accordionHoverShadow: Property.Color;
10 | accordionActiveShadow: Property.Color;
11 | accordionText: Property.Color;
12 | accordionLabel: Property.Color;
13 | };
14 |
15 | type Factory = (primaryColor: ColorInfo) => Colors;
16 |
17 | export const getLight: Factory = () => ({
18 | accordionBackground: 'white',
19 | accordionHoverShadow: tinycolor('black').setAlpha(0.05).toHslString(),
20 | accordionActiveShadow: tinycolor('black').setAlpha(0.2).toHslString(),
21 | accordionText: tinycolor('black').setAlpha(0.74).toHslString(),
22 | accordionLabel: tinycolor('black').setAlpha(0.51).toHslString(),
23 | });
24 |
25 | export const getDark: Factory = () => ({
26 | accordionBackground: '$deepBlue2',
27 | accordionHoverShadow: tinycolor('white').setAlpha(0.05).toHslString(),
28 | accordionActiveShadow: tinycolor('white').setAlpha(0.2).toHslString(),
29 | accordionText: tinycolor('white').setAlpha(0.74).toHslString(),
30 | accordionLabel: tinycolor('white').setAlpha(0.51).toHslString(),
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/components/Accordion/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Accordion';
2 |
--------------------------------------------------------------------------------
/components/Alert/Alert.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { H2 } from '../Heading';
6 | import { Text } from '../Text';
7 | import { Alert, AlertProps, AlertVariants } from './Alert';
8 |
9 | const AlertWrapper = (props: AlertProps): JSX.Element => ;
10 |
11 | const AlertForStory = modifyVariantsForStory(AlertWrapper);
12 |
13 | const Component: Meta = {
14 | title: 'Components/Alert',
15 | component: AlertForStory,
16 | };
17 |
18 | export const Variants: StoryFn = (args) => (
19 |
20 | Alert
21 |
22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
23 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
24 | laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
25 | voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
26 | non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
27 |
28 |
29 | );
30 |
31 | Variants.args = {
32 | variant: 'info',
33 | };
34 |
35 | Variants.argTypes = {
36 | variant: {
37 | control: 'inline-radio',
38 | options: ['gray', 'info', 'success', 'warning', 'error'],
39 | },
40 | };
41 |
42 | export default Component;
43 |
--------------------------------------------------------------------------------
/components/Alert/Alert.tsx:
--------------------------------------------------------------------------------
1 | import { styled, VariantProps } from '../../stitches.config';
2 | import { Card } from '../Card';
3 |
4 | export const Alert = styled(Card, {
5 | variants: {
6 | variant: {
7 | gray: {
8 | '&::before': {
9 | boxShadow: 'inset 0 0 0 1px $colors$slate9',
10 | },
11 | },
12 | info: {
13 | '&::before': {
14 | boxShadow: 'inset 0 0 0 1px $colors$blue9',
15 | },
16 | },
17 | success: {
18 | '&::before': {
19 | boxShadow: 'inset 0 0 0 1px $colors$green9',
20 | },
21 | },
22 | warning: {
23 | '&::before': {
24 | boxShadow: 'inset 0 0 0 1px $colors$orange9',
25 | },
26 | },
27 | error: {
28 | '&::before': {
29 | boxShadow: 'inset 0 0 0 1px $colors$red9',
30 | },
31 | },
32 | },
33 | },
34 | defaultVariants: {
35 | variant: 'gray',
36 | },
37 | });
38 |
39 | export type AlertVariants = VariantProps;
40 | export type AlertProps = AlertVariants & NonNullable;
41 |
--------------------------------------------------------------------------------
/components/Alert/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Alert';
2 |
--------------------------------------------------------------------------------
/components/AriaTable/AriaTable.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 | import { axe } from 'jest-axe';
3 |
4 | import { Table, Tbody, Td, Tfoot, Th, Thead, Tr } from './AriaTable';
5 |
6 | describe('AriaTable', () => {
7 | it('should render table with no a11y issue', async () => {
8 | const { container, getByRole, getAllByRole } = render(
9 |
10 |
11 |
12 | Col1 |
13 |
14 |
15 |
16 |
17 | Cell1 |
18 |
19 |
20 |
21 |
22 | FooterCell1 |
23 |
24 |
25 |
26 | );
27 |
28 | expect(getByRole('table')).toBeInTheDocument();
29 | expect(getByRole('columnheader')).toBeInTheDocument();
30 | expect(getAllByRole('rowgroup')).toHaveLength(3);
31 | expect(getAllByRole('row')).toHaveLength(3);
32 | expect(getAllByRole('cell')).toHaveLength(2);
33 |
34 | const results = await axe(container);
35 |
36 | expect(results).toHaveNoViolations();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/components/AriaTable/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AriaTable';
2 |
--------------------------------------------------------------------------------
/components/Avatar/Avatar.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Avatar } from './Avatar';
5 |
6 | const Component: Meta = {
7 | title: 'Components/Avatar',
8 | component: Avatar,
9 | };
10 |
11 | export const Template: StoryFn = (args) => ;
12 |
13 | Template.args = {
14 | src: 'https://picsum.photos/100',
15 | size: '4',
16 | };
17 |
18 | Template.argTypes = {
19 | size: {
20 | control: 'inline-radio',
21 | options: ['1', '2', '3', '4', '5', '6'],
22 | },
23 | };
24 |
25 | export const Shape: StoryFn = Template.bind({});
26 |
27 | Shape.args = {
28 | src: 'https://picsum.photos/100',
29 | shape: 'square',
30 | size: '4',
31 | };
32 |
33 | Shape.argTypes = {
34 | size: {
35 | control: 'inline-radio',
36 | options: ['1', '2', '3', '4', '5', '6'],
37 | },
38 | shape: {
39 | control: 'inline-radio',
40 | options: ['square', 'circle'],
41 | },
42 | };
43 |
44 | export const Fallback: StoryFn = Template.bind({});
45 |
46 | Fallback.args = {
47 | fallback: 'M',
48 | shape: 'circle',
49 | size: '4',
50 | variant: 'red',
51 | };
52 |
53 | Fallback.argTypes = {
54 | size: {
55 | control: 'inline-radio',
56 | options: ['1', '2', '3', '4', '5', '6'],
57 | },
58 | shape: {
59 | control: 'inline-radio',
60 | options: ['square', 'circle'],
61 | },
62 | variant: {
63 | control: 'inline-radio',
64 | options: ['gray', 'red', 'purple', 'blue', 'green', 'orange'],
65 | },
66 | fallback: {
67 | control: 'text',
68 | },
69 | };
70 |
71 | export const Variants: StoryFn = Template.bind({});
72 |
73 | Variants.args = {
74 | fallback: 'M',
75 | shape: 'circle',
76 | size: '4',
77 | variant: 'blue',
78 | };
79 |
80 | Variants.argTypes = {
81 | size: {
82 | control: 'inline-radio',
83 | options: ['1', '2', '3', '4', '5', '6'],
84 | },
85 | shape: {
86 | control: 'inline-radio',
87 | options: ['square', 'circle'],
88 | },
89 | variant: {
90 | control: 'inline-radio',
91 | options: ['gray', 'red', 'purple', 'blue', 'green', 'orange'],
92 | },
93 | };
94 |
95 | export default Component;
96 |
--------------------------------------------------------------------------------
/components/Avatar/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Avatar';
2 |
--------------------------------------------------------------------------------
/components/Badge/Badge.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Flex } from '../Flex';
7 | import { UnstyledLink } from '../Link';
8 | import { Badge, COLORS } from './Badge';
9 |
10 | type BadgeVariants = VariantProps;
11 | type BadgeProps = BadgeVariants & NonNullable;
12 |
13 | const BaseBadge = (props: BadgeProps): JSX.Element => ;
14 | const BadgeForStory = modifyVariantsForStory(BaseBadge);
15 |
16 | const Component: Meta = {
17 | title: 'Components/Badge',
18 | component: BadgeForStory,
19 | argTypes: {
20 | size: { control: 'inline-radio' },
21 | variant: { control: 'select' },
22 | borderless: { control: 'boolean' },
23 | },
24 | };
25 |
26 | export const Colors: StoryFn = (args) => (
27 |
28 | Default
29 | {COLORS.map((color) => (
30 |
31 | {color}
32 |
33 | ))}
34 |
35 | );
36 |
37 | Colors.args = {
38 | interactive: false,
39 | size: 'small',
40 | variant: 'gray',
41 | borderless: false,
42 | };
43 |
44 | export const AlphaBackground: StoryFn = Colors.bind({});
45 |
46 | AlphaBackground.args = {
47 | alphaBg: true,
48 | };
49 |
50 | export const Small: StoryFn = (args) => Small badge;
51 |
52 | Small.args = {
53 | interactive: false,
54 | size: 'small',
55 | variant: 'blue',
56 | borderless: false,
57 | };
58 |
59 | export const Large: StoryFn = (args) => Large badge;
60 |
61 | Large.args = {
62 | interactive: false,
63 | size: 'large',
64 | variant: 'green',
65 | borderless: false,
66 | };
67 |
68 | export const Interactive: StoryFn = (args) => (
69 |
70 | Default
71 | {COLORS.map((color) => (
72 |
73 | {color}
74 |
75 | ))}
76 |
77 | );
78 |
79 | Interactive.args = {
80 | interactive: true,
81 | size: 'small',
82 | variant: 'gray',
83 | borderless: false,
84 | };
85 |
86 | export const BadgeLink: StoryFn = (args) => (
87 |
88 | Link
89 |
90 | );
91 |
92 | export const Borderless: StoryFn = (args) => (
93 | Borderless badge
94 | );
95 |
96 | Borderless.args = {
97 | interactive: true,
98 | size: 'small',
99 | variant: 'neon',
100 | borderless: true,
101 | };
102 |
103 | export default Component;
104 |
--------------------------------------------------------------------------------
/components/Badge/Badge.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 |
3 | import { UnstyledLink } from '../Link';
4 | import { Badge } from './Badge';
5 |
6 | describe('Badge', () => {
7 | it('should render Badge as link', () => {
8 | const { getByRole } = render(
9 |
10 | Link
11 |
12 | );
13 |
14 | expect(getByRole('link')).toBeInTheDocument();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/components/Badge/Badge.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | badgeInteractiveBackgroundActive: Property.Color;
9 | badgeInteractiveBackgroundHover: Property.Color;
10 | };
11 |
12 | type Factory = (primaryColor: ColorInfo) => Colors;
13 |
14 | export const getLight: Factory = () => ({
15 | badgeInteractiveBackgroundActive: tinycolor('black').setAlpha(0.1).toHslString(),
16 | badgeInteractiveBackgroundHover: tinycolor('black').setAlpha(0.05).toHslString(),
17 | });
18 |
19 | export const getDark: Factory = () => ({
20 | badgeInteractiveBackgroundActive: tinycolor('black').setAlpha(0.2).toHslString(),
21 | badgeInteractiveBackgroundHover: tinycolor('black').setAlpha(0.1).toHslString(),
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/components/Badge/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Badge';
2 |
--------------------------------------------------------------------------------
/components/Blockquote/Blockquote.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { Blockquote, BlockquoteProps, BlockquoteVariants } from './Blockquote';
6 |
7 | const BaseBlockquote = (props: BlockquoteProps): JSX.Element => ;
8 |
9 | const BlockquoteForStory = modifyVariantsForStory(
10 | BaseBlockquote,
11 | );
12 |
13 | const Component: Meta = {
14 | title: 'Components/Blockquote',
15 | component: BlockquoteForStory,
16 | };
17 |
18 | const Template: StoryFn = (args) => (
19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit.
20 | );
21 |
22 | export const Basic: StoryFn = Template.bind({});
23 |
24 | export default Component;
25 |
--------------------------------------------------------------------------------
/components/Blockquote/Blockquote.tsx:
--------------------------------------------------------------------------------
1 | import { CSS, styled, VariantProps } from '../../stitches.config';
2 | import { Text } from '../Text';
3 |
4 | export const Blockquote = styled(Text, {
5 | borderLeft: '2px solid $textDefault',
6 | p: '$2 $3',
7 | });
8 |
9 | export type BlockquoteVariants = VariantProps;
10 | export type BlockquoteProps = BlockquoteVariants & { css?: CSS };
11 |
--------------------------------------------------------------------------------
/components/Blockquote/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Blockquote';
2 |
--------------------------------------------------------------------------------
/components/Box/Box.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Text } from '../Text';
7 | import { Box } from './Box';
8 |
9 | type BoxVariants = VariantProps;
10 | type BoxProps = BoxVariants & NonNullable;
11 |
12 | const BaseBox = (props: BoxProps): JSX.Element => ;
13 | const BoxForStory = modifyVariantsForStory>(
14 | BaseBox
15 | );
16 |
17 | const Component: Meta = {
18 | title: 'Components/Box',
19 | component: BoxForStory,
20 | };
21 |
22 | export const Basic: StoryFn = (args) => (
23 |
24 |
25 | Traefik Labs develops the world's most popular cloud-native application networking software.
26 | It helps developers and operations teams of all sizes build, deploy and run modern
27 | microservices applications quickly and easily.
28 |
29 |
30 | );
31 |
32 | export default Component;
33 |
--------------------------------------------------------------------------------
/components/Box/Box.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | export const Box = styled('div', {
4 | // Reset
5 | boxSizing: 'border-box',
6 | });
7 |
--------------------------------------------------------------------------------
/components/Box/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Box';
2 |
--------------------------------------------------------------------------------
/components/Bubble/Bubble.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Flex } from '../Flex';
7 | import { Bubble } from './Bubble';
8 |
9 | type BubbleVariants = VariantProps;
10 | type BubbleProps = BubbleVariants & NonNullable;
11 |
12 | const BaseBubble = (props: BubbleProps): JSX.Element => ;
13 | const BubbleForStory = modifyVariantsForStory(BaseBubble);
14 |
15 | const Component: Meta = {
16 | title: 'Components/Bubble',
17 | component: BubbleForStory,
18 | };
19 |
20 | export const Colors: StoryFn = (args) => (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | Colors.args = {
33 | size: 'small',
34 | noAnimation: false,
35 | };
36 |
37 | Colors.argTypes = {
38 | size: {
39 | control: 'inline-radio',
40 | options: ['x-small', 'small', 'medium', 'large', 'x-large'],
41 | },
42 | };
43 |
44 | export const Sizes: StoryFn = (args) => (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | );
53 |
54 | Sizes.args = {
55 | variant: 'purple',
56 | };
57 |
58 | Sizes.argTypes = {
59 | size: {
60 | control: false,
61 | noAnimation: false,
62 | },
63 | variant: {
64 | control: 'select',
65 | },
66 | };
67 |
68 | export default Component;
69 |
--------------------------------------------------------------------------------
/components/Bubble/Bubble.tsx:
--------------------------------------------------------------------------------
1 | import { keyframes, styled } from '../../stitches.config';
2 |
3 | const bip = keyframes({
4 | '0%': {
5 | top: 0,
6 | right: 0,
7 | bottom: 0,
8 | left: 0,
9 | opacity: 1,
10 | },
11 | '100%': {
12 | top: -8,
13 | right: -8,
14 | bottom: -8,
15 | left: -8,
16 | opacity: 0,
17 | },
18 | });
19 |
20 | export const Bubble = styled('div', {
21 | display: 'inline-block',
22 | size: '$4',
23 | bc: '$red8',
24 | borderRadius: '50%',
25 | position: 'relative',
26 |
27 | '&::before': {
28 | animation: `${bip} 1s ease infinite`,
29 | boxSizing: 'border-box',
30 | content: '""',
31 | position: 'absolute',
32 | top: 0,
33 | right: 0,
34 | bottom: 0,
35 | left: 0,
36 | boxShadow: 'inset 0 0 0 1px rgba(255,255,255,.5)',
37 | borderRadius: '50%',
38 | pointerEvents: 'none',
39 | zIndex: -1,
40 | },
41 |
42 | '&::after': {
43 | boxSizing: 'border-box',
44 | content: '""',
45 | position: 'absolute',
46 | top: 5,
47 | right: 5,
48 | bottom: 5,
49 | left: 5,
50 | bc: 'rgba(255,255,255,.1)',
51 | borderRadius: '50%',
52 | pointerEvents: 'none',
53 | },
54 |
55 | variants: {
56 | variant: {
57 | red: { bc: '$red8', '&::before': { bc: '$red8' } },
58 | green: { bc: '$green8', '&::before': { bc: '$green8' } },
59 | orange: { bc: '$orange8', '&::before': { bc: '$orange8' } },
60 | blue: { bc: '$blue8', '&::before': { bc: '$blue8' } },
61 | yellow: { bc: '$neon8', '&::before': { bc: '$neon8' } },
62 | purple: { bc: '$purple8', '&::before': { bc: '$purple8' } },
63 | gray: { bc: '$slate8', '&::before': { bc: '$slate8' } },
64 | },
65 | size: {
66 | 'x-small': { size: '$1' },
67 | small: { size: '$2' },
68 | medium: { size: '$3' },
69 | large: { size: '$4' },
70 | 'x-large': { size: '$5' },
71 | },
72 | noAnimation: {
73 | true: {
74 | '&::before': { content: 'none' },
75 | },
76 | },
77 | },
78 |
79 | defaultVariants: {
80 | size: 'small',
81 | noAnimation: false,
82 | },
83 | });
84 |
--------------------------------------------------------------------------------
/components/Bubble/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Bubble';
2 |
--------------------------------------------------------------------------------
/components/Button/Button.stories.tsx:
--------------------------------------------------------------------------------
1 | import { InfoCircledIcon } from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { Flex } from '../Flex';
6 | import { UnstyledLink } from '../Link';
7 | import { Text } from '../Text';
8 | import { ButtonForStory } from './Button';
9 |
10 | const Component: Meta = {
11 | title: 'Components/Button',
12 | component: ButtonForStory,
13 | argTypes: { onClick: { action: 'clicked' } },
14 | };
15 |
16 | const Template: StoryFn = (args) => (
17 | Button
18 | );
19 |
20 | export const Primary: StoryFn = Template.bind({});
21 |
22 | Primary.args = {};
23 |
24 | export const Secondary: StoryFn = Template.bind({});
25 |
26 | Secondary.args = {
27 | variant: 'secondary',
28 | };
29 |
30 | export const Red: StoryFn = Template.bind({});
31 |
32 | Red.args = {
33 | variant: 'red',
34 | };
35 |
36 | export const Ghost: StoryFn = Template.bind({});
37 |
38 | Ghost.args = {
39 | ghost: true,
40 | variant: 'secondary',
41 | };
42 |
43 | export const Disabled: StoryFn = Template.bind({});
44 |
45 | Disabled.args = {
46 | disabled: true,
47 | };
48 |
49 | const TemplateWithIcon: StoryFn = (args) => (
50 |
51 |
52 |
53 | Button
54 |
55 |
56 | );
57 |
58 | export const WithIcon: StoryFn = TemplateWithIcon.bind({});
59 |
60 | const TemplateWithActive: StoryFn = ({ ...args }) => {
61 | const [active, setActive] = React.useState(0);
62 |
63 | return (
64 | <>
65 | Tab {active + 1} is active
66 |
67 | {[...Array(4)].map((_, i) => (
68 | setActive(i)}
73 | >
74 | Tab {i + 1}
75 |
76 | ))}
77 |
78 | >
79 | );
80 | };
81 |
82 | export const Active: StoryFn = TemplateWithActive.bind({});
83 |
84 | Active.args = {};
85 |
86 | export const Waiting: StoryFn = Template.bind({});
87 |
88 | Waiting.args = {
89 | state: 'waiting',
90 | };
91 |
92 | export const ButtonLink: StoryFn = (args) => (
93 |
94 | Button
95 |
96 | );
97 | ButtonLink.argTypes = {
98 | state: {
99 | options: ['waiting', undefined],
100 | control: 'inline-radio',
101 | },
102 | };
103 |
104 | export default Component;
105 |
--------------------------------------------------------------------------------
/components/Button/Button.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 |
3 | import { UnstyledLink } from '../Link';
4 | import { Button } from './Button';
5 |
6 | describe('Button', () => {
7 | it('should render Button as link', () => {
8 | const { getByRole } = render(
9 |
12 | );
13 |
14 | expect(getByRole('link')).toBeInTheDocument();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/components/Button/Button.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | buttonPrimaryBg: Property.Color;
9 | buttonPrimaryText: Property.Color;
10 | buttonPrimaryFocusBorder: Property.Color;
11 | buttonPrimaryGhostHoverText: Property.Color;
12 |
13 | buttonSecondaryBg: Property.Color;
14 | buttonSecondaryText: Property.Color;
15 | buttonSecondaryBorder: Property.Color;
16 | buttonSecondaryFocusBorder: Property.Color;
17 |
18 | buttonRedBg: Property.Color;
19 | buttonRedText: Property.Color;
20 | buttonRedHoverText: Property.Color;
21 | buttonRedFocusBg: Property.Color;
22 | };
23 |
24 | type Factory = (primaryColor: ColorInfo) => Colors;
25 |
26 | export const getLight: Factory = (primaryColor) => ({
27 | buttonPrimaryBg: '$primary',
28 | buttonPrimaryFocusBg: tinycolor(primaryColor.value).lighten(10).toHslString(),
29 | buttonPrimaryText: 'white',
30 | buttonPrimaryFocusBorder: primaryColor.helpers.pickScale(6, { alpha: true }),
31 | buttonPrimaryGhostHoverText: '$deepBlue9',
32 |
33 | buttonSecondaryBg: 'transparent',
34 | buttonSecondaryText: 'hsla(0, 0%, 0%, 0.54)',
35 | buttonSecondaryBorder: '$grayBlue9',
36 | buttonSecondaryFocusBorder: tinycolor(primaryColor.value).setAlpha(0.19).toHslString(),
37 |
38 | buttonRedBg: '$red9',
39 | buttonRedText: '$loContrast',
40 | buttonRedHoverText: '$red10',
41 | buttonRedFocusBg: '$redA8',
42 | });
43 |
44 | export const getDark: Factory = (primaryColor) => ({
45 | buttonPrimaryBg: '$primary',
46 | buttonPrimaryFocusBg: tinycolor(primaryColor.value).lighten(10).toHslString(),
47 | buttonPrimaryText: '$deepBlue2',
48 | buttonPrimaryFocusBorder: primaryColor.helpers.pickScale(12, { alpha: true }),
49 | buttonPrimaryGhostHoverText: tinycolor(primaryColor.value).lighten(10).toHslString(),
50 |
51 | buttonSecondaryBg: 'transparent',
52 | buttonSecondaryText: tinycolor('white').setAlpha(0.74).toHslString(),
53 | buttonSecondaryBorder: '$grayBlue9',
54 | buttonSecondaryFocusBorder: '$primary',
55 |
56 | buttonRedBg: '$red10',
57 | buttonRedText: '$hiContrast',
58 | buttonRedHoverText: '$red10',
59 | buttonRedFocusBg: '$redA11',
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Button';
2 |
--------------------------------------------------------------------------------
/components/ButtonSwitch/ButtonSwitch.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 | import { useState } from 'react';
4 |
5 | import { ButtonSwitchContainer, ButtonSwitchItem } from './ButtonSwitch';
6 |
7 | const Component: Meta = {
8 | title: 'Components/ButtonSwitch',
9 | component: ButtonSwitchContainer,
10 | };
11 |
12 | export const Basic: StoryFn = () => {
13 | const [value, setValue] = useState('left');
14 |
15 | return (
16 |
17 | Left
18 | Right
19 |
20 | );
21 | };
22 |
23 | export const Multiple: StoryFn = () => {
24 | const [value, setValue] = useState([]);
25 |
26 | return (
27 |
28 | Option 1
29 | Option 2
30 | Option 3
31 | Option 4
32 |
33 | );
34 | };
35 |
36 | export default Component;
37 |
--------------------------------------------------------------------------------
/components/ButtonSwitch/ButtonSwitch.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | buttonSwitchContainerBg: Property.Color;
8 | buttonSwitchActiveBg: Property.Color;
9 | buttonSwitchOffBg: Property.Color;
10 | buttonSwitchOffColor: Property.Color;
11 | buttonSwitchActiveColor: Property.Color;
12 | };
13 |
14 | type Factory = (primaryColor: ColorInfo) => Colors;
15 |
16 | export const getLight: Factory = () => ({
17 | buttonSwitchContainerBg: '$02dp',
18 | buttonSwitchActiveBg: '$primary',
19 | buttonSwitchOffBg: 'transparent',
20 | buttonSwitchOffColor: '$hiContrast',
21 | buttonSwitchActiveColor: 'white',
22 | });
23 |
24 | export const getDark: Factory = () => ({
25 | buttonSwitchContainerBg: '$02dp',
26 | buttonSwitchActiveBg: '$primary',
27 | buttonSwitchOffBg: 'transparent',
28 | buttonSwitchOffColor: '$hiContrast',
29 | buttonSwitchActiveColor: '$deepBlue2',
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/components/ButtonSwitch/ButtonSwitch.tsx:
--------------------------------------------------------------------------------
1 | import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
2 |
3 | import { styled } from '../../stitches.config';
4 |
5 | export const ButtonSwitchContainer = styled(ToggleGroupPrimitive.Root, {
6 | display: 'inline-flex',
7 | bc: '$buttonSwitchContainerBg',
8 | borderRadius: '$3',
9 | p: '3px',
10 | gap: '$1',
11 | });
12 |
13 | export const ButtonSwitchItem = styled(ToggleGroupPrimitive.Item, {
14 | display: 'inline-flex',
15 | bc: '$buttonSwitchOffBg',
16 | c: '$buttonSwitchOffColor',
17 | p: '$1',
18 | width: '$10',
19 | justifyContent: 'center',
20 | fontWeight: 600,
21 | border: 'none',
22 | position: 'relative',
23 |
24 | '&::before': {
25 | boxSizing: 'border-box',
26 | content: '""',
27 | position: 'absolute',
28 | inset: 0,
29 | borderRadius: '$3',
30 | },
31 | '&::after': {
32 | boxSizing: 'border-box',
33 | content: '""',
34 | position: 'absolute',
35 | inset: 0,
36 | borderRadius: '$3',
37 | },
38 |
39 | '&:focus-visible': {
40 | borderRadius: '$3',
41 | '&::before': {
42 | backgroundColor: 'rgba(255, 255, 255, 0.15)',
43 | },
44 | '&::after': {
45 | opacity: 0.15,
46 | },
47 | },
48 |
49 | '@hover': {
50 | '&:hover': {
51 | cursor: 'pointer',
52 | '&::before': {
53 | backgroundColor: 'rgba(255, 255, 255, 0.15)',
54 | },
55 | '&::after': {
56 | opacity: 0.05,
57 | },
58 | },
59 | },
60 |
61 | '&[data-state=on]': {
62 | bc: '$buttonSwitchActiveBg',
63 | c: '$buttonSwitchActiveColor',
64 | borderRadius: '$3',
65 | },
66 | });
67 |
--------------------------------------------------------------------------------
/components/ButtonSwitch/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ButtonSwitch';
2 |
--------------------------------------------------------------------------------
/components/Card/Card.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | cardBackground: Property.Color;
9 | cardBorder: Property.Color;
10 | cardShadow: Property.Color;
11 | cardHoverBackground: Property.Color;
12 | cardHoverBorder: Property.Color;
13 | cardActiveBackground: Property.Color;
14 | cardActiveBorder: Property.Color;
15 | cardGhostBackground: Property.Color;
16 | innerCardBgColor: Property.Color;
17 | };
18 |
19 | type Factory = (primaryColor: ColorInfo) => Colors;
20 |
21 | export const getLight: Factory = (primaryColor) => ({
22 | cardBackground: 'white',
23 | cardBorder: '$deepBlue3',
24 | cardShadow: 'rgba(0,0,0,.1)',
25 | cardHoverBackground: 'rgba(0,0,0,.05)',
26 | cardHoverBorder: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
27 | cardActiveBackground: 'rgba(0,0,0,.03)',
28 | cardActiveBorder: '$primary',
29 | cardGhostBackground: '$deepBlue2',
30 | innerCardBgColor: tinycolor('black').setAlpha(0.04).toHslString(),
31 | });
32 |
33 | export const getDark: Factory = (primaryColor) => ({
34 | cardBackground: '$deepBlue2',
35 | cardBorder: '$deepBlue3',
36 | cardShadow: 'transparent',
37 | cardHoverBackground: 'rgba(255,255,255,.12)',
38 | cardHoverBorder: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
39 | cardActiveBackground: 'rgba(255,255,255,.07)',
40 | cardActiveBorder: tinycolor(primaryColor.value).setAlpha(0.4).toHslString(),
41 | cardGhostBackground: '$deepBlue1',
42 | innerCardBgColor: tinycolor('white').setAlpha(0.07).toHslString(),
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/components/Card/Card.tsx:
--------------------------------------------------------------------------------
1 | import React, { ComponentProps, ElementRef } from 'react';
2 |
3 | import { styled, VariantProps } from '../../stitches.config';
4 | import { elevationVariants } from '../Elevation/Elevation';
5 |
6 | const StyledCard = styled('div', {
7 | appearance: 'none',
8 | border: 'none',
9 | boxSizing: 'border-box',
10 | font: 'inherit',
11 | lineHeight: '1',
12 | outline: 'none',
13 | padding: '$3',
14 | textAlign: 'inherit',
15 | verticalAlign: 'middle',
16 | WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',
17 |
18 | backgroundColor: '$cardBackground',
19 | display: 'block',
20 | textDecoration: 'none',
21 | color: 'inherit',
22 | borderRadius: '$3',
23 | position: 'relative',
24 |
25 | '&::before': {
26 | boxSizing: 'border-box',
27 | content: '""',
28 | position: 'absolute',
29 | top: 0,
30 | right: 0,
31 | bottom: 0,
32 | left: 0,
33 | borderRadius: '$3',
34 | pointerEvents: 'none',
35 | },
36 |
37 | variants: {
38 | elevation: elevationVariants,
39 | variant: {
40 | inner: {
41 | backgroundColor: '$innerCardBgColor',
42 | },
43 | ghost: {
44 | backgroundColor: 'transparent',
45 | boxShadow: 'none',
46 | },
47 | },
48 | active: {
49 | true: {
50 | '&::before': {
51 | outline: '1px solid $colors$cardActiveBorder',
52 | backgroundColor: '$cardActiveBackground',
53 | },
54 | },
55 | },
56 | },
57 | defaultVariants: {
58 | elevation: 1,
59 | },
60 | });
61 |
62 | const StyledInteractiveCard = styled('button', StyledCard, {
63 | '@hover': {
64 | '&:hover': {
65 | cursor: 'pointer',
66 | '&::before': {
67 | outline: '1px solid $colors$cardHoverBorder',
68 | backgroundColor: '$cardHoverBackground',
69 | },
70 | },
71 | },
72 | '&:focus': {
73 | outline: '2px solid $primary',
74 | },
75 | '&:active': {
76 | '&::before': {
77 | outline: '1px solid $colors$cardActiveBorder',
78 | backgroundColor: '$cardActiveBackground',
79 | },
80 | },
81 | });
82 |
83 | export type CardVariantProps = ComponentProps &
84 | VariantProps & {
85 | interactive?: false;
86 | };
87 | export type InteractiveCardProps = VariantProps &
88 | ComponentProps & {
89 | interactive: true;
90 | };
91 | export type CardProps = CardVariantProps | InteractiveCardProps;
92 |
93 | export const Card = React.forwardRef, CardProps>(
94 | ({ interactive, ...props }, forwardedRef) => {
95 | if (interactive) {
96 | return (
97 | }
100 | {...(props as InteractiveCardProps)}
101 | />
102 | );
103 | }
104 | return (
105 | }
107 | {...(props as CardVariantProps)}
108 | />
109 | );
110 | },
111 | );
112 |
--------------------------------------------------------------------------------
/components/Card/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Card';
2 |
--------------------------------------------------------------------------------
/components/Checkbox/Checkbox.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { Checkbox, CheckboxProps, CheckboxVariants } from './Checkbox';
6 |
7 | const BaseCheckbox = (props: CheckboxProps): JSX.Element => ;
8 | const CheckboxForStory = modifyVariantsForStory<
9 | CheckboxVariants,
10 | CheckboxProps & React.InputHTMLAttributes
11 | >(BaseCheckbox);
12 |
13 | const Component: Meta = {
14 | title: 'Components/Checkbox',
15 | component: CheckboxForStory,
16 | argTypes: { onCheckedChange: { action: 'checkedChange' } },
17 | };
18 |
19 | const Template: StoryFn = (args) => ;
20 |
21 | export const Basic: StoryFn = Template.bind({});
22 |
23 | Basic.args = {};
24 |
25 | export const Size: StoryFn = Template.bind({});
26 |
27 | Size.args = {
28 | size: 'large',
29 | };
30 |
31 | export const Disabled: StoryFn = Template.bind({});
32 |
33 | Disabled.args = {
34 | disabled: true,
35 | checked: true,
36 | size: 'large',
37 | };
38 |
39 | export default Component;
40 |
--------------------------------------------------------------------------------
/components/Checkbox/Checkbox.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | checkboxIcon: Property.Color;
9 | checkboxBg: Property.Color;
10 | checkboxCheckedBg: Property.Color;
11 | checkboxBorder: Property.Color;
12 | checkboxCheckedIcon: Property.Color;
13 | checkboxCheckedHoverBg: Property.Color;
14 | checkboxHoverBg: Property.Color;
15 | checkboxHoverBorder: Property.Color;
16 | checkboxFocusBorder: Property.Color;
17 | checkboxDisabledBg: Property.Color;
18 | checkboxDisabledBorder: Property.Color;
19 | checkboxIndicatorDisabledBg: Property.Color;
20 | };
21 |
22 | type Factory = (primaryColor: ColorInfo) => Colors;
23 |
24 | export const getLight: Factory = (primaryColor) => ({
25 | checkboxIcon: '$deepBlue11',
26 | checkboxBg: 'transparent',
27 | checkboxBorder: '$slate8',
28 | checkboxCheckedBg: '$primary',
29 | checkboxCheckedIcon: 'white',
30 | checkboxCheckedHoverBg: tinycolor(primaryColor.value).darken().toHslString(),
31 | checkboxHoverBg: 'transparent',
32 | checkboxHoverBorder: '$primary',
33 | checkboxFocusBorder: '$primary',
34 | checkboxDisabledBg: '$deepBlue3',
35 | checkboxDisabledBorder: '$deepBlue5',
36 | checkboxIndicatorDisabledBg: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
37 | });
38 |
39 | export const getDark: Factory = (primaryColor) => ({
40 | checkboxIcon: '$deepBlue1',
41 | checkboxBg: 'transparent',
42 | checkboxCheckedBg: '$primary',
43 | checkboxBorder: '$slate9',
44 | checkboxCheckedIcon: '$deepBlue1',
45 | checkboxCheckedHoverBg: tinycolor(primaryColor.value).lighten(10).toHslString(),
46 | checkboxHoverBg: '$deepBlue3',
47 | checkboxHoverBorder: '$primary',
48 | checkboxFocusBorder: '$primary',
49 | checkboxDisabledBg: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
50 | checkboxDisabledBorder: 'transparent',
51 | checkboxIndicatorDisabledBg: '$deepBlue1',
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/components/Checkbox/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
2 | import { CheckIcon } from '@radix-ui/react-icons';
3 | import React from 'react';
4 |
5 | import { CSS, styled, VariantProps } from '../../stitches.config';
6 |
7 | const StyledIndicator = styled(CheckboxPrimitive.Indicator, {
8 | alignItems: 'center',
9 | display: 'flex',
10 | height: '100%',
11 | justifyContent: 'center',
12 | width: '100%',
13 | });
14 |
15 | const StyledCheckbox = styled(CheckboxPrimitive.Root, {
16 | all: 'unset',
17 | border: 'none',
18 | boxSizing: 'border-box',
19 | userSelect: 'none',
20 | '&::before': {
21 | boxSizing: 'border-box',
22 | },
23 | '&::after': {
24 | boxSizing: 'border-box',
25 | },
26 |
27 | alignItems: 'center',
28 | appearance: 'none',
29 | display: 'inline-flex',
30 | justifyContent: 'center',
31 | lineHeight: '1',
32 | margin: '0',
33 | outline: 'none',
34 | padding: '0',
35 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
36 | overflow: 'hidden',
37 |
38 | '&[data-state=checked]': {
39 | backgroundColor: '$checkboxCheckedBg',
40 | boxShadow: 'inset 0 0 0 1px transparent',
41 | color: '$checkboxCheckedIcon',
42 |
43 | '@hover': {
44 | '&:hover': {
45 | backgroundColor: '$checkboxCheckedHoverBg',
46 | },
47 | },
48 | },
49 |
50 | '&[data-state=unchecked]': {
51 | backgroundColor: '$checkboxBg',
52 | boxShadow: 'inset 0 0 0 1px $colors$checkboxBorder',
53 | color: '$checkboxIcon',
54 |
55 | '@hover': {
56 | '&:hover': {
57 | backgroundColor: '$checkboxHoverBg',
58 | boxShadow: 'inset 0 0 0 1px $colors$checkboxHoverBorder',
59 | },
60 | },
61 | },
62 |
63 | '&:focus': {
64 | outline: 'none',
65 | boxShadow: 'inset 0 0 0 1px $colors$checkboxFocusBorder',
66 | },
67 |
68 | '&:disabled': {
69 | pointerEvents: 'none',
70 | backgroundColor: '$checkboxDisabledBg',
71 | boxShadow: 'inset 0 0 0 1px $colors$checkboxDisabledBorder',
72 | '&::placeholder': {
73 | color: '$checkboxDisabledText',
74 | },
75 |
76 | [`& ${StyledIndicator}`]: {
77 | '&::after': {
78 | backgroundColor: '$checkboxIndicatorDisabledBg',
79 | },
80 | },
81 | },
82 |
83 | variants: {
84 | size: {
85 | medium: {
86 | width: 18,
87 | height: 18,
88 | borderRadius: '$2',
89 | },
90 | large: {
91 | width: '$5',
92 | height: '$5',
93 | borderRadius: '$3',
94 | },
95 | },
96 | },
97 | defaultVariants: {
98 | size: 'medium',
99 | },
100 | });
101 |
102 | type CheckboxPrimitiveProps = Omit, 'as'>;
103 | export type CheckboxVariants = VariantProps;
104 | export type CheckboxProps = CheckboxPrimitiveProps & CheckboxVariants & { css?: CSS };
105 |
106 | export const Checkbox = React.forwardRef, CheckboxProps>(
107 | (props, forwardedRef) => {
108 | return (
109 |
110 |
111 |
112 |
113 |
114 | );
115 | }
116 | );
117 |
--------------------------------------------------------------------------------
/components/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Checkbox';
2 |
--------------------------------------------------------------------------------
/components/Container/Container.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Paragraph } from '../Paragraph';
7 | import { Container } from './Container';
8 |
9 | type ContainerVariants = VariantProps;
10 | type ContainerProps = ContainerVariants & NonNullable;
11 |
12 | const BaseContainer = (props: ContainerProps): JSX.Element => ;
13 | const ContainerForStory = modifyVariantsForStory<
14 | ContainerVariants,
15 | ContainerProps & React.HTMLAttributes
16 | >(BaseContainer);
17 |
18 | const Component: Meta = {
19 | title: 'Components/Container',
20 | component: ContainerForStory,
21 | };
22 |
23 | const Template: StoryFn = (args) => (
24 |
25 |
26 | Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quo, iste. Perferendis saepe aperiam
27 | repudiandae, a ea labore error iure! Doloribus sunt earum, aperiam facilis ex corporis veniam
28 | deleniti voluptatibus laudantium? Lorem ipsum dolor sit amet consectetur, adipisicing elit.
29 | Reiciendis consequatur harum quibusdam! Facilis aspernatur est fugiat laudantium officiis. Aut
30 | labore asperiores qui iure earum culpa, voluptatum explicabo commodi quis dolorem? Lorem ipsum
31 | dolor sit, amet consectetur adipisicing elit. Dolore dolorum deserunt, consectetur numquam
32 | minus error dolorem? Explicabo iure quidem, maxime fugit quos obcaecati, molestiae nemo nobis
33 | aliquid saepe, impedit at.
34 |
35 |
36 | );
37 |
38 | export const Basic: StoryFn = Template.bind({});
39 |
40 | Basic.args = {};
41 |
42 | export const Size: StoryFn = Template.bind({});
43 |
44 | Size.args = { size: '1' };
45 |
46 | export const NoGutter: StoryFn = Template.bind({});
47 |
48 | NoGutter.args = { noGutter: true };
49 |
50 | export default Component;
51 |
--------------------------------------------------------------------------------
/components/Container/Container.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | export const Container = styled('div', {
4 | // Reset
5 | boxSizing: 'border-box',
6 | flexShrink: 0,
7 |
8 | // Custom
9 | ml: 'auto',
10 | mr: 'auto',
11 | px: '$5',
12 |
13 | variants: {
14 | size: {
15 | '1': {
16 | maxWidth: '425px',
17 | },
18 | '2': {
19 | maxWidth: '768px',
20 | },
21 | '3': {
22 | maxWidth: '1440px',
23 | },
24 | '4': {
25 | maxWidth: 'none',
26 | },
27 | },
28 | noGutter: {
29 | true: {
30 | px: 0,
31 | },
32 | },
33 | },
34 | defaultVariants: {
35 | size: '4',
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/components/Container/index.ts:
--------------------------------------------------------------------------------
1 | export { Container } from './Container';
2 |
--------------------------------------------------------------------------------
/components/DateTimePicker/DateTimePicker.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React, { useState } from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { Flex } from '../Flex';
6 | import { Label } from '../Label';
7 | import { Text } from '../Text';
8 | import { DateTimePicker, DateTimePickerProps, DateTimePickerVariants } from './DateTimePicker';
9 |
10 | const DateTimePickerWrapper = (props: DateTimePickerProps): JSX.Element => (
11 |
12 | );
13 |
14 | const DateTimePickerForStory = modifyVariantsForStory<
15 | DateTimePickerVariants,
16 | DateTimePickerProps & {
17 | calendarMode?: 'fluid' | 'static' | undefined;
18 | maxDate?: Date;
19 | minDate?: Date;
20 | }
21 | >(DateTimePickerWrapper);
22 |
23 | const Component: Meta = {
24 | title: 'Components/DateTimePicker',
25 | component: DateTimePickerForStory,
26 | argTypes: {
27 | calendarMode: {
28 | control: 'inline-radio',
29 | options: ['fluid', 'static'],
30 | },
31 | minDate: {
32 | control: 'date',
33 | },
34 | showDatePresets: {
35 | control: 'boolean',
36 | },
37 | showTimePicker: {
38 | control: 'boolean',
39 | },
40 | },
41 | };
42 |
43 | const DateTimePickerTemplate: StoryFn = (args) => {
44 | const [selectedDates, onDatesChange] = useState([]);
45 |
46 | return (
47 |
48 |
57 |
58 |
59 |
60 | {selectedDates.length
61 | ? selectedDates.map((date) => date.toISOString()).join(', ')
62 | : 'None.'}
63 |
64 |
65 |
66 | );
67 | };
68 |
69 | export const Base: StoryFn = DateTimePickerTemplate.bind({});
70 |
71 | Base.args = {
72 | calendarMode: 'static',
73 | minDate: new Date(),
74 | showDatePresets: false,
75 | showTimePicker: false,
76 | };
77 |
78 | export const WithDatePresets: StoryFn = DateTimePickerTemplate.bind(
79 | {},
80 | );
81 |
82 | WithDatePresets.args = {
83 | calendarMode: 'static',
84 | minDate: new Date(),
85 | showDatePresets: true,
86 | showTimePicker: false,
87 | };
88 |
89 | export const WithTimePicker: StoryFn = DateTimePickerTemplate.bind(
90 | {},
91 | );
92 |
93 | WithTimePicker.args = {
94 | calendarMode: 'static',
95 | minDate: new Date(),
96 | showDatePresets: false,
97 | showTimePicker: true,
98 | };
99 |
100 | export default Component;
101 |
--------------------------------------------------------------------------------
/components/DateTimePicker/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DateTimePicker';
2 |
--------------------------------------------------------------------------------
/components/DateTimePickerInput/DateTimePickerInput.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React, { useState } from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { Flex } from '../Flex';
6 | import { Label } from '../Label';
7 | import { Text } from '../Text';
8 | import {
9 | DateTimePickerInput,
10 | DateTimePickerInputProps,
11 | DateTimePickerInputVariants,
12 | } from './DateTimePickerInput';
13 |
14 | const DateTimePickerInputWrapper = (props: DateTimePickerInputProps): JSX.Element => (
15 |
16 | );
17 |
18 | const DateTimePickerInputForStory = modifyVariantsForStory<
19 | DateTimePickerInputVariants,
20 | DateTimePickerInputProps
21 | >(DateTimePickerInputWrapper);
22 |
23 | const Component: Meta = {
24 | title: 'Components/DateTimePickerInput',
25 | component: DateTimePickerInputForStory,
26 | argTypes: {
27 | pickerPlacement: {
28 | control: 'inline-radio',
29 | options: [
30 | 'top-start',
31 | 'right-start',
32 | 'bottom-start',
33 | 'left-start',
34 | 'top',
35 | 'right',
36 | 'bottom',
37 | 'left',
38 | 'top-end',
39 | 'right-end',
40 | 'bottom-end',
41 | 'left-end',
42 | ],
43 | },
44 | showDatePresets: {
45 | control: 'boolean',
46 | },
47 | showTimePicker: {
48 | control: 'boolean',
49 | },
50 | },
51 | };
52 |
53 | const DateTimePickerTemplate: StoryFn = (args) => {
54 | const [selectedDates, onDatesChange] = useState([]);
55 |
56 | return (
57 |
78 | );
79 | };
80 |
81 | export const Base: StoryFn = DateTimePickerTemplate.bind({});
82 |
83 | Base.args = {
84 | showDatePresets: true,
85 | showTimePicker: true,
86 | };
87 |
88 | export default Component;
89 |
--------------------------------------------------------------------------------
/components/DateTimePickerInput/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DateTimePickerInput';
2 |
--------------------------------------------------------------------------------
/components/Dialog/Dialog.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | dialogBackground: Property.Color;
8 | };
9 |
10 | type Factory = (primaryColor?: ColorInfo) => Colors;
11 |
12 | export const getLight: Factory = () => ({
13 | dialogBackground: '$deepBlue2',
14 | });
15 |
16 | export const getDark: Factory = () => ({
17 | dialogBackground: '$deepBlue3',
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/components/Dialog/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as DialogPrimitive from '@radix-ui/react-dialog';
2 | import { Cross1Icon } from '@radix-ui/react-icons';
3 | import React, { ComponentProps } from 'react';
4 |
5 | import { CSS, styled, VariantProps } from '../../stitches.config';
6 | import { Card } from '../Card';
7 | import { elevationVariants } from '../Elevation/Elevation';
8 | import { IconButton } from '../IconButton';
9 | import { overlayStyles } from '../Overlay';
10 |
11 | type DialogProps = React.ComponentProps & {
12 | children: React.ReactNode;
13 | };
14 |
15 | export const DialogOverlay = styled(DialogPrimitive.Overlay, overlayStyles, {
16 | position: 'fixed',
17 | top: 0,
18 | right: 0,
19 | bottom: 0,
20 | left: 0,
21 | });
22 |
23 | export function Dialog({ children, ...props }: DialogProps) {
24 | return {children};
25 | }
26 |
27 | export const StyledContent = styled(DialogPrimitive.Content, Card, {
28 | position: 'fixed',
29 | top: '50%',
30 | left: '50%',
31 | transform: 'translate(-50%, -50%)',
32 | minWidth: 200,
33 | maxHeight: '85vh',
34 | padding: '$4',
35 | marginTop: '-5vh',
36 | backgroundColor: '$dialogBackground',
37 | boxShadow: 'inset 0 0 0 1px $colors$deepBlue4',
38 | borderRadius: '$3',
39 | // animation: `${fadeIn} 125ms linear, ${moveDown} 125ms cubic-bezier(0.22, 1, 0.36, 1)`,
40 | willChange: 'transform',
41 | overflow: 'auto',
42 |
43 | '&::before': {
44 | boxShadow: 'none',
45 | },
46 |
47 | '&:focus': {
48 | outline: 'none',
49 | },
50 |
51 | variants: {
52 | elevation: elevationVariants,
53 | },
54 | defaultVariants: {
55 | elevation: 5,
56 | },
57 | });
58 |
59 | const StyledCloseButton = styled(DialogPrimitive.Close, {
60 | position: 'absolute',
61 | top: '$2',
62 | right: '$2',
63 | cursor: 'pointer',
64 | });
65 |
66 | interface DialogCloseButtonProps
67 | extends VariantProps,
68 | ComponentProps {}
69 | export const DialogCloseIconButton = React.forwardRef<
70 | React.ElementRef,
71 | DialogCloseButtonProps
72 | >((props, forwardedRef) => (
73 |
74 |
75 |
76 |
77 |
78 | ));
79 |
80 | type DialogContentPrimitiveProps = React.ComponentProps;
81 | type DialogContentProps = DialogContentPrimitiveProps &
82 | VariantProps & { css?: CSS };
83 |
84 | export const DialogContent = React.forwardRef<
85 | React.ElementRef,
86 | DialogContentProps
87 | >(({ children, ...props }, forwardedRef) => (
88 |
89 | {children}
90 |
91 |
92 | ));
93 |
94 | export const DialogClosePrimitive = DialogPrimitive.Close;
95 | export const DialogDescription = DialogPrimitive.Description;
96 | export const DialogPortal = DialogPrimitive.Portal;
97 | export const DialogTitle = DialogPrimitive.Title;
98 | export const DialogTrigger = DialogPrimitive.Trigger;
99 |
--------------------------------------------------------------------------------
/components/Dialog/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Dialog';
2 |
--------------------------------------------------------------------------------
/components/DropdownMenu/DropdownMenu.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Button } from '../Button';
5 | import {
6 | DropdownMenu,
7 | DropdownMenuCheckboxItem,
8 | DropdownMenuContent,
9 | DropdownMenuGroup,
10 | DropdownMenuItem,
11 | DropdownMenuLabel,
12 | DropdownMenuPortal,
13 | DropdownMenuRadioGroup,
14 | DropdownMenuRadioItem,
15 | DropdownMenuSeparator,
16 | DropdownMenuTrigger,
17 | } from './DropdownMenu';
18 |
19 | const Component: Meta = {
20 | title: 'Components/DropdownMenu',
21 | component: DropdownMenu,
22 | argTypes: { onOpenChange: { action: 'clicked' } },
23 | };
24 |
25 | const Template: StoryFn = (args) => (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Item
34 | Item
35 | Item
36 |
37 | Item
38 | Item
39 | Item
40 |
41 | Choose one
42 |
43 | Item
44 | Item
45 | Item
46 |
47 |
48 |
49 |
50 |
51 | );
52 |
53 | export const Basic: StoryFn = Template.bind({});
54 |
55 | export const Modal: StoryFn = Template.bind({});
56 |
57 | Modal.args = {
58 | modal: true,
59 | };
60 |
61 | export const DefaultOpen: StoryFn = Template.bind({});
62 |
63 | DefaultOpen.args = {
64 | defaultOpen: true,
65 | };
66 |
67 | export default Component;
68 |
--------------------------------------------------------------------------------
/components/DropdownMenu/DropdownMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
2 | import { CheckIcon } from '@radix-ui/react-icons';
3 | import React from 'react';
4 |
5 | import { CSS, styled } from '../../stitches.config';
6 | import { Box } from '../Box';
7 | import { Flex } from '../Flex';
8 | import { panelStyles } from '../Panel';
9 | import { itemCss, labelCss, menuCss, separatorCss } from './styles';
10 |
11 | export const DropdownMenu = DropdownMenuPrimitive.Root;
12 | export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
13 | export const DropdownMenuContent = styled(DropdownMenuPrimitive.Content, menuCss, panelStyles);
14 | export const DropdownMenuSeparator = styled(DropdownMenuPrimitive.Separator, separatorCss);
15 | export const DropdownMenuItem = styled(DropdownMenuPrimitive.Item, itemCss);
16 |
17 | const StyledDropdownMenuRadioItem = styled(DropdownMenuPrimitive.RadioItem, itemCss);
18 |
19 | type DialogMenuRadioItemPrimitiveProps = Omit<
20 | React.ComponentProps,
21 | 'as'
22 | >;
23 | type DialogMenuRadioItemProps = DialogMenuRadioItemPrimitiveProps & { css?: CSS };
24 |
25 | export const DropdownMenuRadioItem = React.forwardRef<
26 | React.ElementRef,
27 | DialogMenuRadioItemProps
28 | >(({ children, ...props }, forwardedRef) => (
29 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
44 | {children}
45 |
46 | ));
47 |
48 | const StyledDropdownMenuCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, itemCss);
49 |
50 | type DialogMenuCheckboxItemPrimitiveProps = Omit<
51 | React.ComponentProps,
52 | 'as'
53 | >;
54 | type DialogMenuCheckboxItemProps = DialogMenuCheckboxItemPrimitiveProps & { css?: CSS };
55 |
56 | export const DropdownMenuCheckboxItem = React.forwardRef<
57 | React.ElementRef,
58 | DialogMenuCheckboxItemProps
59 | >(({ children, ...props }, forwardedRef) => (
60 |
61 |
62 |
63 |
64 |
65 |
66 | {children}
67 |
68 | ));
69 |
70 | export const DropdownMenuLabel = styled(DropdownMenuPrimitive.Label, labelCss);
71 | export const DropdownMenuRadioGroup = styled(DropdownMenuPrimitive.RadioGroup, {});
72 | export const DropdownMenuGroup = styled(DropdownMenuPrimitive.Group, {});
73 | export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
74 |
--------------------------------------------------------------------------------
/components/DropdownMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DropdownMenu';
2 |
--------------------------------------------------------------------------------
/components/DropdownMenu/styles.ts:
--------------------------------------------------------------------------------
1 | import { css } from '../../stitches.config';
2 | import { elevationVariants } from '../Elevation';
3 |
4 | export const baseItemCss = css({
5 | display: 'flex',
6 | alignItems: 'center',
7 | justifyContent: 'space-between',
8 | fontFamily: '$rubik',
9 | fontSize: '$1',
10 | fontVariantNumeric: 'tabular-nums',
11 | lineHeight: '1',
12 | cursor: 'default',
13 | userSelect: 'none',
14 | whiteSpace: 'nowrap',
15 | height: '$5',
16 | px: '$5',
17 | });
18 |
19 | export const itemCss = css(baseItemCss, {
20 | position: 'relative',
21 | color: '$hiContrast',
22 |
23 | '&:focus': {
24 | outline: 'none',
25 | backgroundColor: '$blue9',
26 | color: 'white',
27 | },
28 |
29 | '&[data-disabled]': {
30 | color: '$slate9',
31 | },
32 | });
33 |
34 | export const labelCss = css(baseItemCss, {
35 | color: '$slate11',
36 | });
37 |
38 | export const menuCss = css({
39 | boxSizing: 'border-box',
40 | minWidth: 120,
41 | py: '$1',
42 | variants: {
43 | elevation: elevationVariants,
44 | },
45 | defaultVariants: {
46 | elevation: 2,
47 | },
48 | });
49 |
50 | export const separatorCss = css({
51 | height: 1,
52 | my: '$1',
53 | backgroundColor: '$slate6',
54 | });
55 |
--------------------------------------------------------------------------------
/components/Elevation/Elevation.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Flex } from '../Flex';
5 | import { Text } from '../Text';
6 | import { Elevation } from './Elevation';
7 |
8 | const Component: Meta = {
9 | title: 'Components/Elevation',
10 | component: Elevation,
11 | };
12 |
13 | export const Basic: StoryFn = () => (
14 |
15 |
16 |
21 | Hello world
22 |
23 |
24 |
25 |
30 | Hello world
31 |
32 |
33 |
34 |
39 | Hello world
40 |
41 |
42 |
43 |
48 | Hello world
49 |
50 |
51 |
52 |
57 | Hello world
58 |
59 |
60 |
61 |
66 | Hello world
67 |
68 |
69 |
70 | );
71 |
72 | Basic.args = {};
73 |
74 | Basic.argTypes = {
75 | variant: {
76 | control: 'inline-radio',
77 | options: [0, 1, 2, 3, 4, 5],
78 | },
79 | };
80 |
81 | export default Component;
82 |
--------------------------------------------------------------------------------
/components/Elevation/Elevation.tsx:
--------------------------------------------------------------------------------
1 | import { CSS, styled } from '../../stitches.config';
2 |
3 | type ElevationVariant = Record;
4 |
5 | export const elevationVariants: ElevationVariant = {
6 | 0: {
7 | boxShadow: 'none',
8 | },
9 | 1: {
10 | bc: '$01dp',
11 | boxShadow:
12 | '0 1px 5px 0 hsla(0, 0%, 0%, 0.2), 0 3px 1px -2px hsla(0, 0%, 0%, 0.12), 0 2px 2px 0 hsla(0, 0%, 0%, 0.14)',
13 | },
14 | 2: {
15 | bc: '$02dp',
16 | boxShadow:
17 | '0 2px 4px -1px hsla(0, 0%, 0%, 0.2), 0 1px 10px 0 hsla(0, 0%, 0%, 0.12), 0 4px 5px 0 hsla(0, 0%, 0%, 0.14)',
18 | },
19 | 3: {
20 | bc: '$03dp',
21 | boxShadow:
22 | '0 3px 5px -1px hsla(0, 0%, 0%, 0.2), 0 1px 18px 0 hsla(0, 0%, 0%, 0.12), 0 6px 10px 0 hsla(0, 0%, 0%, 0.14)',
23 | },
24 | 4: {
25 | bc: '$04dp',
26 | boxShadow:
27 | '0 7px 8px -4px hsla(0, 0%, 0%, 0.2), 0 5px 22px 4px hsla(0, 0%, 0%, 0.12), 0 12px 17px 2px hsla(0, 0%, 0%, 0.14)',
28 | },
29 | 5: {
30 | bc: '$05dp',
31 | boxShadow:
32 | '0 11px 15px -7px hsla(0, 0%, 0%, 0.2), 0 9px 46px 8px hsla(0, 0%, 0%, 0.12), 0 24px 38px 3px hsla(0, 0%, 0%, 0.14)',
33 | },
34 | };
35 |
36 | export const Elevation = styled('div', {
37 | display: 'inline-block',
38 |
39 | variants: {
40 | variant: elevationVariants,
41 | },
42 | defaultVariants: {
43 | variant: 1,
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/components/Elevation/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Elevation';
2 |
--------------------------------------------------------------------------------
/components/FaencyProvider.tsx:
--------------------------------------------------------------------------------
1 | import { Provider as TooltipProvider } from '@radix-ui/react-tooltip';
2 | import React from 'react';
3 |
4 | interface FaencyProviderProps {
5 | children?: React.ReactNode;
6 | }
7 |
8 | export const FaencyProvider = ({ children }: FaencyProviderProps) => (
9 | {children}
10 | );
11 |
--------------------------------------------------------------------------------
/components/Flex/Flex.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Box } from '../Box';
5 | import { Flex } from './Flex';
6 |
7 | const Component: Meta = {
8 | title: 'Components/Flex',
9 | component: Flex,
10 | };
11 |
12 | export const Basic: StoryFn = (args) => (
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | Basic.args = {
21 | direction: 'column',
22 | align: 'center',
23 | gap: '6',
24 | };
25 |
26 | export default Component;
27 |
--------------------------------------------------------------------------------
/components/Flex/Flex.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | export const Flex = styled('div', {
4 | boxSizing: 'border-box',
5 | display: 'flex',
6 |
7 | variants: {
8 | direction: {
9 | row: {
10 | flexDirection: 'row',
11 | },
12 | column: {
13 | flexDirection: 'column',
14 | },
15 | rowReverse: {
16 | flexDirection: 'row-reverse',
17 | },
18 | columnReverse: {
19 | flexDirection: 'column-reverse',
20 | },
21 | },
22 | align: {
23 | start: {
24 | alignItems: 'flex-start',
25 | },
26 | center: {
27 | alignItems: 'center',
28 | },
29 | end: {
30 | alignItems: 'flex-end',
31 | },
32 | stretch: {
33 | alignItems: 'stretch',
34 | },
35 | baseline: {
36 | alignItems: 'baseline',
37 | },
38 | ['space-evenly']: {
39 | alignItems: 'space-evenly',
40 | },
41 | ['space-between']: {
42 | alignItems: 'space-between',
43 | },
44 | ['space-around']: {
45 | alignItems: 'space-around',
46 | },
47 | },
48 | justify: {
49 | start: {
50 | justifyContent: 'flex-start',
51 | },
52 | center: {
53 | justifyContent: 'center',
54 | },
55 | end: {
56 | justifyContent: 'flex-end',
57 | },
58 | between: {
59 | justifyContent: 'space-between',
60 | },
61 | ['space-evenly']: {
62 | justifyContent: 'space-evenly',
63 | },
64 | ['space-between']: {
65 | justifyContent: 'space-between',
66 | },
67 | ['space-around']: {
68 | justifyContent: 'space-around',
69 | },
70 | },
71 | wrap: {
72 | noWrap: {
73 | flexWrap: 'nowrap',
74 | },
75 | wrap: {
76 | flexWrap: 'wrap',
77 | },
78 | wrapReverse: {
79 | flexWrap: 'wrap-reverse',
80 | },
81 | },
82 | gap: {
83 | 1: {
84 | gap: '$1',
85 | },
86 | 2: {
87 | gap: '$2',
88 | },
89 | 3: {
90 | gap: '$3',
91 | },
92 | 4: {
93 | gap: '$4',
94 | },
95 | 5: {
96 | gap: '$5',
97 | },
98 | 6: {
99 | gap: '$6',
100 | },
101 | 7: {
102 | gap: '$7',
103 | },
104 | 8: {
105 | gap: '$8',
106 | },
107 | 9: {
108 | gap: '$9',
109 | },
110 | },
111 | },
112 | defaultVariants: {
113 | direction: 'row',
114 | align: 'stretch',
115 | justify: 'start',
116 | wrap: 'noWrap',
117 | },
118 | });
119 |
--------------------------------------------------------------------------------
/components/Flex/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Flex';
2 |
--------------------------------------------------------------------------------
/components/Grid/Grid.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Box } from '../Box';
5 | import { Text } from '../Text';
6 | import { Grid } from './Grid';
7 |
8 | const Component: Meta = {
9 | title: 'Components/Grid',
10 | component: Grid,
11 | };
12 |
13 | export const Basic: StoryFn = (args) => (
14 |
15 |
16 |
17 | Traefik Labs develops the world's most popular cloud-native application networking software.
18 | It helps developers and operations teams of all sizes build, deploy and run modern
19 | microservices applications quickly and easily.
20 |
21 |
22 |
23 |
24 | Traefik Labs develops the world's most popular cloud-native application networking software.
25 | It helps developers and operations teams of all sizes build, deploy and run modern
26 | microservices applications quickly and easily.
27 |
28 |
29 |
30 |
31 | Traefik Labs develops the world's most popular cloud-native application networking software.
32 | It helps developers and operations teams of all sizes build, deploy and run modern
33 | microservices applications quickly and easily.
34 |
35 |
36 |
37 | );
38 |
39 | Basic.args = {
40 | columns: '3',
41 | gap: '6',
42 | };
43 |
44 | export default Component;
45 |
--------------------------------------------------------------------------------
/components/Grid/Grid.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | export const Grid = styled('div', {
4 | boxSizing: 'border-box',
5 | display: 'grid',
6 |
7 | variants: {
8 | align: {
9 | start: {
10 | alignItems: 'start',
11 | },
12 | center: {
13 | alignItems: 'center',
14 | },
15 | end: {
16 | alignItems: 'end',
17 | },
18 | stretch: {
19 | alignItems: 'stretch',
20 | },
21 | baseline: {
22 | alignItems: 'baseline',
23 | },
24 | },
25 | justify: {
26 | start: {
27 | justifyContent: 'start',
28 | },
29 | center: {
30 | justifyContent: 'center',
31 | },
32 | end: {
33 | justifyContent: 'end',
34 | },
35 | between: {
36 | justifyContent: 'space-between',
37 | },
38 | },
39 | flow: {
40 | row: {
41 | gridAutoFlow: 'row',
42 | },
43 | column: {
44 | gridAutoFlow: 'column',
45 | },
46 | dense: {
47 | gridAutoFlow: 'dense',
48 | },
49 | rowDense: {
50 | gridAutoFlow: 'row dense',
51 | },
52 | columnDense: {
53 | gridAutoFlow: 'column dense',
54 | },
55 | },
56 | columns: {
57 | 1: {
58 | gridTemplateColumns: 'repeat(1, 1fr)',
59 | },
60 | 2: {
61 | gridTemplateColumns: 'repeat(2, 1fr)',
62 | },
63 | 3: {
64 | gridTemplateColumns: 'repeat(3, 1fr)',
65 | },
66 | 4: {
67 | gridTemplateColumns: 'repeat(4, 1fr)',
68 | },
69 | },
70 | gap: {
71 | 1: {
72 | gap: '$1',
73 | },
74 | 2: {
75 | gap: '$2',
76 | },
77 | 3: {
78 | gap: '$3',
79 | },
80 | 4: {
81 | gap: '$4',
82 | },
83 | 5: {
84 | gap: '$5',
85 | },
86 | 6: {
87 | gap: '$6',
88 | },
89 | 7: {
90 | gap: '$7',
91 | },
92 | 8: {
93 | gap: '$8',
94 | },
95 | 9: {
96 | gap: '$9',
97 | },
98 | },
99 | gapX: {
100 | 1: {
101 | columnGap: '$1',
102 | },
103 | 2: {
104 | columnGap: '$2',
105 | },
106 | 3: {
107 | columnGap: '$3',
108 | },
109 | 4: {
110 | columnGap: '$4',
111 | },
112 | 5: {
113 | columnGap: '$5',
114 | },
115 | 6: {
116 | columnGap: '$6',
117 | },
118 | 7: {
119 | columnGap: '$7',
120 | },
121 | 8: {
122 | columnGap: '$8',
123 | },
124 | 9: {
125 | columnGap: '$9',
126 | },
127 | },
128 | gapY: {
129 | 1: {
130 | rowGap: '$1',
131 | },
132 | 2: {
133 | rowGap: '$2',
134 | },
135 | 3: {
136 | rowGap: '$3',
137 | },
138 | 4: {
139 | rowGap: '$4',
140 | },
141 | 5: {
142 | rowGap: '$5',
143 | },
144 | 6: {
145 | rowGap: '$6',
146 | },
147 | 7: {
148 | rowGap: '$7',
149 | },
150 | 8: {
151 | rowGap: '$8',
152 | },
153 | 9: {
154 | rowGap: '$9',
155 | },
156 | },
157 | },
158 | });
159 |
--------------------------------------------------------------------------------
/components/Grid/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Grid';
2 |
--------------------------------------------------------------------------------
/components/Heading/Heading.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Flex } from '../Flex';
5 | import { H1, H2, H3, H4, H5, H6 } from './Heading';
6 |
7 | const Component: Meta = {
8 | title: 'Components/Heading',
9 | component: H1,
10 | };
11 |
12 | const Template: StoryFn = (args) => (
13 |
14 | Heading level 1
15 | Heading level 2
16 | Heading level 3
17 | Heading level 4
18 | Heading level 5
19 | Heading level 6
20 |
21 | );
22 |
23 | export const Basic: StoryFn = Template.bind({});
24 |
25 | Basic.args = {};
26 | Basic.argTypes = {
27 | transform: {
28 | options: ['uppercase', 'capitalize', 'capitalizeWords'],
29 | control: 'inline-radio',
30 | },
31 | };
32 |
33 | export const Transform: StoryFn = (args) => (
34 |
35 | heading level 1 default
36 |
37 | heading level 1 uppercase
38 |
39 |
40 | heading level 1 capitalize
41 |
42 |
43 | heading level 1 capitalize each word
44 |
45 |
46 | );
47 |
48 | export default Component;
49 |
--------------------------------------------------------------------------------
/components/Heading/Heading.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@testing-library/react';
2 |
3 | import { H1, H2, H3, H4, H5, H6 } from './Heading';
4 |
5 | const HEADINGS = [H1, H2, H3, H4, H5, H6];
6 |
7 | describe('Heading', () => {
8 | it.each(HEADINGS)('should render heading with proper role', (HeadingComponent) => {
9 | const { getByRole } = render(Heading);
10 |
11 | expect(getByRole('heading')).toBeInTheDocument();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/components/Heading/Heading.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | headingDefault: Property.Color;
9 | };
10 |
11 | type Factory = (primaryColor: ColorInfo) => Colors;
12 |
13 | export const getLight: Factory = () => ({
14 | headingDefault: tinycolor('black').setAlpha(0.74).toHslString(),
15 | });
16 |
17 | export const getDark: Factory = () => ({
18 | headingDefault: tinycolor('white').setAlpha(0.74).toHslString(),
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/components/Heading/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | const HEADING_BASE_STYLES = {
4 | fontFamily: '$rubik',
5 | fontVariantNumeric: 'proportional-nums',
6 | display: 'block',
7 | lineHeight: '1.25',
8 | fontWeight: '$medium',
9 | margin: 0,
10 | color: '$headingDefault',
11 | variants: {
12 | transform: {
13 | uppercase: {
14 | textTransform: 'uppercase',
15 | },
16 | capitalize: {
17 | // WARNING: this will only work with block elements (display block/inline-block)
18 | // @see https://developer.mozilla.org/en-US/docs/Web/CSS/::first-letter
19 | display: 'block',
20 | '&::first-letter': {
21 | textTransform: 'uppercase',
22 | },
23 | },
24 | capitalizeWords: {
25 | textTransform: 'capitalize',
26 | },
27 | },
28 | },
29 | };
30 |
31 | export const H1 = styled('h1', {
32 | ...HEADING_BASE_STYLES,
33 | fontSize: '$12',
34 | });
35 |
36 | export const H2 = styled('h2', {
37 | ...HEADING_BASE_STYLES,
38 | fontSize: '$10',
39 | });
40 |
41 | export const H3 = styled('h3', {
42 | ...HEADING_BASE_STYLES,
43 | fontSize: '$8',
44 | });
45 |
46 | export const H4 = styled('h4', {
47 | ...HEADING_BASE_STYLES,
48 | fontSize: '$7',
49 | });
50 |
51 | export const H5 = styled('h5', {
52 | ...HEADING_BASE_STYLES,
53 | fontSize: '$6',
54 | });
55 |
56 | export const H6 = styled('h6', {
57 | ...HEADING_BASE_STYLES,
58 | fontSize: '$4',
59 | });
60 |
--------------------------------------------------------------------------------
/components/Heading/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Heading';
2 |
--------------------------------------------------------------------------------
/components/IconButton/IconButton.stories.tsx:
--------------------------------------------------------------------------------
1 | import * as Icons from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { Flex } from '../Flex';
6 | import { IconButton } from './IconButton';
7 |
8 | const Component: Meta = {
9 | title: 'Components/IconButton',
10 | component: IconButton,
11 | };
12 |
13 | export const Sizes: StoryFn = (args) => (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | Sizes.args = {
31 | variant: 'default',
32 | };
33 |
34 | Sizes.argTypes = {
35 | variant: {
36 | control: 'inline-radio',
37 | options: ['default', 'primary', 'red', 'green', 'orange'],
38 | },
39 | };
40 |
41 | export const Variants: StoryFn = (args) => (
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 |
64 | Variants.args = {
65 | size: '3',
66 | };
67 |
68 | Variants.argTypes = {
69 | size: {
70 | control: 'inline-radio',
71 | options: ['1', '2', '3', '4'],
72 | },
73 | };
74 |
75 | export default Component;
76 |
--------------------------------------------------------------------------------
/components/IconButton/IconButton.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | iconButtonBackground: Property.Color;
8 | iconButtonHoverBorder: Property.Color;
9 | iconButtonHoverBackground: Property.Color;
10 | iconButtonFocusBorder: Property.Color;
11 | };
12 |
13 | type Factory = (primaryColor?: ColorInfo) => Colors;
14 |
15 | export const getLight: Factory = () => ({
16 | iconButtonBackground: 'white',
17 | iconButtonHoverBorder: '$slate9',
18 | iconButtonHoverBackground: '$slateA3',
19 | iconButtonFocusBorder: '$slate10',
20 | });
21 |
22 | export const getDark: Factory = () => ({
23 | iconButtonBackground: '$deepBlue2',
24 | iconButtonHoverBorder: '$deepBlue6',
25 | iconButtonHoverBackground: '$slateA4',
26 | iconButtonFocusBorder: '$deepBlue7',
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/components/IconButton/IconButton.tsx:
--------------------------------------------------------------------------------
1 | import { styled, VariantProps } from '../../stitches.config';
2 | import { BUTTON_BASE_STYLES } from '../Button';
3 |
4 | export const IconButton = styled('button', {
5 | ...BUTTON_BASE_STYLES,
6 | // Reset
7 | alignItems: 'center',
8 | display: 'inline-flex',
9 | flexShrink: 0,
10 | fontFamily: 'inherit',
11 | fontSize: '14px',
12 | justifyContent: 'center',
13 | lineHeight: '1',
14 | // Custom
15 | position: 'relative',
16 | padding: '0',
17 | textDecoration: 'none',
18 | backgroundColor: 'transparent',
19 |
20 | '&::before': {
21 | boxSizing: 'border-box',
22 | content: '""',
23 | position: 'absolute',
24 | inset: 0,
25 | borderRadius: 'inherit',
26 | },
27 | '&::after': {
28 | boxSizing: 'border-box',
29 | content: '""',
30 | position: 'absolute',
31 | inset: 0,
32 | borderRadius: 'inherit',
33 | },
34 | '@hover': {
35 | '&:hover': {
36 | cursor: 'pointer',
37 | '&::before': {
38 | backgroundColor: 'rgba(255, 255, 255, 0.15)',
39 | },
40 | '&::after': {
41 | opacity: 0.05,
42 | },
43 | },
44 | },
45 |
46 | '&:focus-visible': {
47 | outline: '2px solid currentColor',
48 | '&::before': {
49 | backgroundColor: 'rgba(255, 255, 255, 0.15)',
50 | },
51 | '&::after': {
52 | opacity: 0.15,
53 | },
54 | },
55 |
56 | '&:active': {
57 | '&::before': {
58 | backgroundColor: 'rgba(0, 0, 0, 0.15)',
59 | },
60 | },
61 |
62 | variants: {
63 | size: {
64 | '1': {
65 | borderRadius: '$1',
66 | height: '$5',
67 | width: '$5',
68 | },
69 | '2': {
70 | borderRadius: '$2',
71 | height: '$6',
72 | width: '$6',
73 | },
74 | '3': {
75 | borderRadius: '$2',
76 | height: '$7',
77 | width: '$7',
78 | },
79 | '4': {
80 | borderRadius: '$3',
81 | height: '$8',
82 | width: '$8',
83 | },
84 | },
85 | variant: {
86 | default: {
87 | color: '$slate10',
88 | },
89 | contrast: {
90 | color: '$hiContrast',
91 | },
92 | primary: {
93 | color: '$primary',
94 | },
95 | red: {
96 | color: '$red9',
97 | },
98 | green: {
99 | color: '$green9',
100 | },
101 | orange: {
102 | color: '$orange9',
103 | },
104 | },
105 | },
106 | defaultVariants: {
107 | variant: 'default',
108 | size: '2',
109 | },
110 | });
111 |
112 | export type IconButtonVariants = VariantProps;
113 |
--------------------------------------------------------------------------------
/components/IconButton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './IconButton';
2 |
--------------------------------------------------------------------------------
/components/Image/Image.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { Image } from './Image';
5 |
6 | const Component: Meta = {
7 | title: 'Components/Image',
8 | component: Image,
9 | };
10 |
11 | const Template: StoryFn = (args) => ;
12 |
13 | export const Basic: StoryFn = Template.bind({});
14 |
15 | Basic.args = {
16 | src: 'https://picsum.photos/200/300',
17 | };
18 |
19 | export const Large: StoryFn = Template.bind({});
20 |
21 | Large.args = {
22 | src: 'https://picsum.photos/2000/3000',
23 | };
24 |
25 | export default Component;
26 |
--------------------------------------------------------------------------------
/components/Image/Image.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 |
3 | export const Image = styled('img', {
4 | // Reset
5 | verticalAlign: 'middle',
6 | maxWidth: '100%',
7 | });
8 |
--------------------------------------------------------------------------------
/components/Image/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Image';
2 |
--------------------------------------------------------------------------------
/components/Input/Input.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | export type Colors = {
8 | inputBg: Property.Color;
9 | inputBorder: Property.Color;
10 | inputFocusBg: Property.Color;
11 | inputFocusBorder: Property.Color;
12 | inputHoverBg: Property.Color;
13 | inputText: Property.Color;
14 | inputPlaceholder: Property.Color;
15 | inputDisabledText: Property.Color;
16 | inputInvalidBorder: Property.Color;
17 | };
18 |
19 | type Factory = (primaryColor: ColorInfo) => Colors;
20 |
21 | export const getLight: Factory = (primaryColor) => ({
22 | inputBg: '$deepBlue1',
23 | inputBorder: '$grayBlue9',
24 | inputFocusBg: tinycolor('black').setAlpha(0.15).toHslString(),
25 | inputFocusBorder: primaryColor.helpers.pickScale(8),
26 | inputHoverBg: '$whiteA9',
27 | inputText: tinycolor('black').setAlpha(0.74).toHslString(),
28 | inputPlaceholder: '$blackA10',
29 | inputDisabledText: tinycolor('black').setAlpha(0.35).toHslString(),
30 | inputInvalidBorder: '$red9',
31 | });
32 |
33 | export const getDark: Factory = (primaryColor) => ({
34 | inputBg: '$grayBlue7',
35 | inputBorder: '$grayBlue9',
36 | inputFocusBg: tinycolor('black').setAlpha(0.15).toHslString(),
37 | inputFocusBorder: primaryColor.helpers.pickScale(11),
38 | inputHoverBg: '$whiteA4',
39 | inputText: tinycolor('white').setAlpha(0.8).toHslString(),
40 | inputPlaceholder: '$whiteA10',
41 | inputDisabledText: tinycolor('white').setAlpha(0.35).toHslString(),
42 | inputInvalidBorder: '$red9',
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/components/Input/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Input';
2 |
--------------------------------------------------------------------------------
/components/Label/Label.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import ignoreArgType from '../../utils/ignoreArgType';
6 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
7 | import { Box } from '../Box';
8 | import { Label } from './Label';
9 |
10 | type LabelVariants = VariantProps;
11 | type LabelProps = LabelVariants & NonNullable;
12 |
13 | const BaseLabel = (props: LabelProps): JSX.Element => ;
14 | const LabelForStory = modifyVariantsForStory<
15 | LabelVariants,
16 | LabelProps & React.LabelHTMLAttributes
17 | >(BaseLabel);
18 |
19 | const Component: Meta = {
20 | title: 'Components/Label',
21 | component: LabelForStory,
22 | };
23 |
24 | const Template: StoryFn = ({ id, ...args }) => (
25 |
26 |
29 |
30 |
31 | );
32 |
33 | export const Basic: StoryFn = Template.bind({});
34 |
35 | Basic.args = {
36 | id: 'basic',
37 | };
38 | ignoreArgType('id', Basic);
39 |
40 | export const Capitalized: StoryFn = Template.bind({});
41 |
42 | Capitalized.args = {
43 | id: 'capitalize',
44 | };
45 | ignoreArgType('id', Capitalized);
46 |
47 | export const CapitalizedWords: StoryFn = Template.bind({});
48 |
49 | CapitalizedWords.args = {
50 | id: 'capitalize-words',
51 | transform: 'capitalizeWords',
52 | };
53 | ignoreArgType('id', CapitalizedWords);
54 |
55 | export const Uppercased: StoryFn = Template.bind({});
56 | Uppercased.args = {
57 | id: 'uppercase',
58 | transform: 'uppercase',
59 | };
60 | ignoreArgType('id', Uppercased);
61 |
62 | export const Invalid: StoryFn = Template.bind({});
63 | Invalid.args = {
64 | id: 'invalidvariant',
65 | variant: 'invalid',
66 | };
67 | ignoreArgType('id', Invalid);
68 |
69 | export const Disabled: StoryFn = ({ id, ...args }) => (
70 |
71 |
74 |
75 |
76 | );
77 | Disabled.args = {
78 | id: 'subtledisabled',
79 | };
80 | ignoreArgType('id', Disabled);
81 |
82 | export const FocusContrast: StoryFn = ({ id, ...args }) => {
83 | const [hasFocus, setHasFocus] = React.useState(false);
84 |
85 | const onFocus = React.useCallback(() => {
86 | setHasFocus(true);
87 | }, [setHasFocus]);
88 |
89 | const onBlur = React.useCallback(() => {
90 | setHasFocus(false);
91 | }, [setHasFocus]);
92 |
93 | return (
94 |
95 |
98 |
99 |
100 | );
101 | };
102 | FocusContrast.args = {
103 | id: 'focuscontrastvariants',
104 | };
105 | ignoreArgType('id', FocusContrast);
106 |
107 | export default Component;
108 |
--------------------------------------------------------------------------------
/components/Label/Label.tsx:
--------------------------------------------------------------------------------
1 | import * as LabelPrimitive from '@radix-ui/react-label';
2 |
3 | import { styled } from '../../stitches.config';
4 | import { Text } from '../Text';
5 |
6 | export const Label = styled(LabelPrimitive.Root, Text, {
7 | display: 'inline-block',
8 | verticalAlign: 'middle',
9 | cursor: 'default',
10 | fontWeight: '$medium',
11 | lineHeight: 2.18,
12 |
13 | defaultVariants: {
14 | size: '0',
15 | variant: 'subtle',
16 | transform: 'capitalize',
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/components/Label/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Label';
2 |
--------------------------------------------------------------------------------
/components/Link/Link.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React, { LinkHTMLAttributes } from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Link } from './Link';
7 |
8 | type LinkVariants = VariantProps;
9 | type LinkProps = LinkVariants & NonNullable;
10 |
11 | const BaseLink = (props: LinkProps): JSX.Element => ;
12 | const LinkForStory = modifyVariantsForStory>(
13 | BaseLink
14 | );
15 |
16 | const Component: Meta = {
17 | title: 'Components/Link',
18 | component: LinkForStory,
19 | };
20 |
21 | const Template: StoryFn = (args) => (
22 |
23 | https://traefik.io
24 |
25 | );
26 |
27 | export const Basic: StoryFn = Template.bind({});
28 |
29 | Basic.args = {};
30 |
31 | export const Variant: StoryFn = Template.bind({});
32 |
33 | Variant.args = {
34 | variant: 'primary',
35 | };
36 | Variant.argTypes = {
37 | variant: {
38 | options: ['blue', 'primary', 'subtle', 'contrast'],
39 | control: 'inline-radio',
40 | },
41 | };
42 |
43 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
44 | const Customize: StoryFn = (args) => (
45 |
46 | https://traefik.io
47 |
48 | );
49 |
50 | export default Component;
51 |
--------------------------------------------------------------------------------
/components/Link/Link.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 | export namespace Theme {
6 | type Colors = {
7 | linkSubtle: Property.Color;
8 | linkBlueTextDecoration: Property.Color;
9 | linkBlueHoverTextDecoration: Property.Color;
10 | linkBlueFocusOutline: Property.Color;
11 | linkContrast: Property.Color;
12 | linkContrastTextDecoration: Property.Color;
13 | linkContrastHoverTextDecoration: Property.Color;
14 | linkContrastFocusOutline: Property.Color;
15 | linkSubtleTextDecoration: Property.Color;
16 | linkSubtleHoverTextDecoration: Property.Color;
17 | linkSubtleFocusOutline: Property.Color;
18 | linkPrimary: Property.Color;
19 | linkPrimaryTextDecoration: Property.Color;
20 | linkPrimaryHoverTextDecoration: Property.Color;
21 | linkPrimaryFocusOutline: Property.Color;
22 | };
23 |
24 | type Factory = (primaryColor: ColorInfo) => Colors;
25 |
26 | export const getLight: Factory = (primaryColor) => ({
27 | linkBlue: '$blue11',
28 | linkBlueTextDecoration: '$blue7',
29 | linkBlueHoverTextDecoration: '$blue11',
30 | linkBlueFocusOutline: '$blue8',
31 | linkContrast: '$hiContrast',
32 | linkContrastTextDecoration: '$slate8',
33 | linkContrastHoverTextDecoration: '$hiContrast',
34 | linkContrastFocusOutline: '$slate8',
35 | linkSubtle: '$deepBlue7',
36 | linkSubtleTextDecoration: '$deepBlue5',
37 | linkSubtleHoverTextDecoration: '$deepBlue7',
38 | linkSubtleFocusOutline: '$deepBlue4',
39 | linkPrimary: tinycolor(primaryColor.value).darken(26).toHslString(),
40 | linkPrimaryTextDecoration: tinycolor(primaryColor.value).darken(16).toHslString(),
41 | linkPrimaryHoverTextDecoration: tinycolor(primaryColor.value).darken(26).toHslString(),
42 | linkPrimaryFocusOutline: tinycolor(primaryColor.value).darken(16).toHslString(),
43 | });
44 |
45 | export const getDark: Factory = (primaryColor) => ({
46 | linkBlue: '$blue11',
47 | linkBlueTextDecoration: '$blue8',
48 | linkBlueHoverTextDecoration: '$blue11',
49 | linkBlueFocusOutline: '$blue8',
50 | linkContrast: '$hiContrast',
51 | linkContrastTextDecoration: '$slate8',
52 | linkContrastHoverTextDecoration: '$hiContrast',
53 | linkContrastFocusOutline: '$slate8',
54 | linkSubtle: '$deepBlue6',
55 | linkSubtleTextDecoration: '$deepBlue4',
56 | linkSubtleHoverTextDecoration: '$deepBlue5',
57 | linkSubtleFocusOutline: '$deepBlue3',
58 | linkPrimary: '$primary',
59 | linkPrimaryTextDecoration: tinycolor(primaryColor.value).lighten(16).toHslString(),
60 | linkPrimaryHoverTextDecoration: tinycolor(primaryColor.value).darken(20).toHslString(),
61 | linkPrimaryFocusOutline: tinycolor(primaryColor.value).lighten(16).toHslString(),
62 | });
63 | }
64 |
--------------------------------------------------------------------------------
/components/Link/Link.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '../../stitches.config';
2 | import { Text } from '../Text';
3 |
4 | export const UnstyledLink = styled('a', {
5 | color: 'inherit',
6 | textDecoration: 'inherit',
7 | fontWeight: 'inherit',
8 | });
9 |
10 | export const Link = styled('a', {
11 | alignItems: 'center',
12 | gap: '$1',
13 | flexShrink: 0,
14 | outline: 'none',
15 |
16 | textDecoration: 'underline',
17 | textUnderlineOffset: '3px',
18 | textDecorationColor: '$slate4',
19 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
20 | lineHeight: 'inherit',
21 | fontFamily: '$rubik',
22 |
23 | '@hover': {
24 | '&:hover': {
25 | textDecorationLine: 'underline',
26 | },
27 | },
28 | '&:focus': {
29 | outlineWidth: '2px',
30 | outlineStyle: 'solid',
31 | outlineOffset: '2px',
32 | textDecorationLine: 'none',
33 | },
34 | [`& ${Text}`]: {
35 | color: 'inherit',
36 | },
37 | variants: {
38 | variant: {
39 | blue: {
40 | color: '$linkBlue',
41 | textDecoration: 'none',
42 | textDecorationColor: '$linkBlueTextDecoration',
43 | '&:focus': {
44 | outlineColor: '$linkBlueFocusOutline',
45 | },
46 | },
47 | primary: {
48 | color: '$linkPrimary',
49 | textDecorationColor: '$linkPrimaryTextDecoration',
50 | '@hover': {
51 | '&:hover': {
52 | textDecorationColor: '$linkPrimaryHoverTextDecoration',
53 | },
54 | },
55 | '&:focus': {
56 | outlineColor: '$linkPrimaryFocusOutline',
57 | },
58 | },
59 | subtle: {
60 | color: '$linkSubtle',
61 | textDecorationColor: '$linkSubtleTextDecoration',
62 | '@hover': {
63 | '&:hover': {
64 | textDecorationColor: '$linkSubtleHoverTextDecoration',
65 | },
66 | },
67 | '&:focus': {
68 | outlineColor: '$linkSubtleFocusOutline',
69 | },
70 | },
71 | contrast: {
72 | color: '$linkContrast',
73 | textDecorationColor: '$linkContrastTextDecoration',
74 | '@hover': {
75 | '&:hover': {
76 | textDecorationColor: '$linkContrastHoverTextDecoration',
77 | },
78 | },
79 | '&:focus': {
80 | outlineColor: '$linkContrastFocusOutline',
81 | },
82 | },
83 | },
84 | },
85 | defaultVariants: {
86 | variant: 'contrast',
87 | },
88 | });
89 |
--------------------------------------------------------------------------------
/components/Link/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Link';
2 |
--------------------------------------------------------------------------------
/components/List/List.stories.tsx:
--------------------------------------------------------------------------------
1 | import { InfoCircledIcon } from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { Avatar } from '../Avatar';
6 | import { Button } from '../Button';
7 | import { Checkbox } from '../Checkbox';
8 | import { Flex } from '../Flex';
9 | import { Text } from '../Text';
10 | import { Li, Ol, Ul } from './List';
11 |
12 | const Component: Meta = {
13 | title: 'Components/List',
14 | component: Ul,
15 | };
16 |
17 | const Template: StoryFn = (args) => (
18 |
19 | - Dashboard
20 | - Profile
21 | - Settings
22 | - Help
23 |
24 | );
25 |
26 | export const Basic: StoryFn = Template.bind({});
27 |
28 | export const Checkboxes: StoryFn = (args) => (
29 |
47 | );
48 |
49 | export const Ordered: StoryFn = (args) => (
50 |
51 | - Dashboard
52 | - Profile
53 | - Settings
54 | - Help
55 |
56 | );
57 |
58 | export const Interactive: StoryFn = Template.bind({});
59 | Interactive.args = {
60 | interactive: true,
61 | };
62 |
63 | export const Users: StoryFn = (args) => (
64 |
65 | -
66 |
67 |
68 | John Doe
69 | john.doe@john.doe
70 |
71 |
72 | -
73 |
74 |
75 | Jane Doe
76 | jane.doe@jane.doe
77 |
78 |
79 | -
80 |
81 |
82 | Doe Jane
83 | doe.jane@doe.jane
84 |
85 |
86 |
87 | );
88 | Users.args = {
89 | interactive: true,
90 | };
91 |
92 | export const Controls: StoryFn = (args) => (
93 |
94 | }>
95 |
96 |
97 | John Doe
98 | john.doe@john.doe
99 |
100 |
101 | - Delete}>
102 |
103 |
104 | Jane Doe
105 | jane.doe@jane.doe
106 |
107 |
108 | }>
109 |
110 |
111 | Doe Jane
112 | doe.jane@doe.jane
113 |
114 |
115 |
116 | );
117 | Controls.args = {
118 | interactive: true,
119 | };
120 |
121 | export default Component;
122 |
--------------------------------------------------------------------------------
/components/List/List.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | export type Colors = {
7 | listItemHoverBg: Property.Color;
8 | listItemActiveBg: Property.Color;
9 | listItemFocusBorder: Property.Color;
10 | };
11 |
12 | type Factory = (primaryColor: ColorInfo) => Colors;
13 |
14 | export const getLight: Factory = (primaryColor) => ({
15 | listItemHoverBg: '$whiteA9',
16 | listItemActiveBg: '$whiteA8',
17 | listItemFocusBorder: primaryColor.helpers.pickScale(8),
18 | });
19 |
20 | export const getDark: Factory = (primaryColor) => ({
21 | listItemHoverBg: '$whiteA4',
22 | listItemActiveBg: '$whiteA3',
23 | listItemFocusBorder: primaryColor.helpers.pickScale(11),
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/components/List/index.ts:
--------------------------------------------------------------------------------
1 | export * from './List';
2 |
--------------------------------------------------------------------------------
/components/Navigation/Navigation.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 | export namespace Theme {
6 | type Colors = {
7 | navButtonBg: Property.Color;
8 | navButtonHoverBg: Property.Color;
9 | navButtonHoverBg2: Property.Color;
10 | navButtonFocusBg: Property.Color;
11 | navButtonFocusBg2: Property.Color;
12 | navButtonFocusBorder: Property.Color;
13 | navButtonText: Property.Color;
14 | navButtonHoverText: Property.Color;
15 | navButtonActiveText: Property.Color;
16 | navButtonFocusText: Property.Color;
17 | };
18 |
19 | type Factory = (primaryColor: ColorInfo) => Colors;
20 |
21 | export const getLight: Factory = (primaryColor) => ({
22 | navButtonBg: 'transparent',
23 | navButtonHoverBg: tinycolor('black').setAlpha(0.04).toHslString(),
24 | navButtonHoverBg2: 'transparent',
25 | navButtonFocusBg: tinycolor('white').setAlpha(0.15).toHslString(),
26 | navButtonFocusBg2: '$primary',
27 | navButtonFocusBorder: primaryColor.helpers.pickScale(8),
28 | navButtonActiveBg: tinycolor('black').setAlpha(0.04).toHslString(),
29 | navButtonActiveBg2: 'transparent',
30 | navButtonText: tinycolor('black').setAlpha(0.54).toHslString(),
31 | navButtonHoverText: 'black',
32 | navButtonFocusText: 'black',
33 | navButtonActiveText: '$primary',
34 | });
35 |
36 | export const getDark: Factory = (primaryColor) => ({
37 | navButtonBg: 'transparent',
38 | navButtonHoverBg: tinycolor('white').setAlpha(0.05).toHslString(),
39 | navButtonHoverBg2: '$primary',
40 | navButtonFocusBg: tinycolor('white').setAlpha(0.15).toHslString(),
41 | navButtonFocusBg2: '$primary',
42 | navButtonFocusBorder: primaryColor.helpers.pickScale(11),
43 | navButtonActiveBg: tinycolor('white').setAlpha(0.05).toHslString(),
44 | navButtonActiveBg2: '$primary',
45 | navButtonText: 'white',
46 | navButtonHoverText: 'white',
47 | navButtonFocusText: 'white',
48 | navButtonActiveText: '$primary',
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/components/Navigation/NavigationItem.stories.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DashboardIcon,
3 | GearIcon,
4 | PersonIcon,
5 | QuestionMarkCircledIcon,
6 | } from '@radix-ui/react-icons';
7 | import { Meta, StoryFn } from '@storybook/react';
8 | import React from 'react';
9 |
10 | import { Badge } from '../Badge';
11 | import { NavigationDrawer, NavigationItem } from './Navigation';
12 |
13 | const Component: Meta = {
14 | title: 'Components/NavigationItem',
15 | // @ts-expect-error
16 | component: NavigationItem,
17 | argTypes: {
18 | // @ts-expect-error
19 | as: {
20 | options: ['a', 'button'],
21 | },
22 | href: {
23 | control: 'text',
24 | },
25 | startAdornment: {
26 | options: ['Dashboard', 'Gear', 'Person', 'Question'],
27 | mapping: {
28 | Dashboard: ,
29 | Gear: ,
30 | Person: ,
31 | Question: ,
32 | },
33 | },
34 | endAdornment: {
35 | control: 'number',
36 | },
37 | active: {
38 | control: 'boolean',
39 | },
40 | },
41 | };
42 |
43 | const Template: StoryFn = (args) => (
44 |
45 |
58 | {args.endAdornment}
59 |
60 | )
61 | }
62 | >
63 | Navigation Item
64 |
65 |
66 | );
67 |
68 | export const Basic: StoryFn = Template.bind({});
69 |
70 | Basic.args = {
71 | as: 'a',
72 | href: '#',
73 | startAdornment: 'Gear',
74 | endAdornment: 1,
75 | active: false,
76 | };
77 |
78 | export const ButtonProps: StoryFn = (args) => {
79 | const noop = () => undefined;
80 | return (
81 |
82 |
83 | Navigation Item
84 |
85 |
86 | );
87 | };
88 |
89 | export const LinkProps: StoryFn = () => (
90 |
91 |
92 | Navigation Item
93 |
94 |
95 | );
96 |
97 | export default Component;
98 |
--------------------------------------------------------------------------------
/components/Navigation/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Navigation';
2 |
--------------------------------------------------------------------------------
/components/NavigationMenu/NavigationMenu.stories.tsx:
--------------------------------------------------------------------------------
1 | import { GearIcon, PersonIcon } from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Container } from '../Container';
7 | import { Link } from '../Link';
8 | import { NavigationItem } from '../Navigation';
9 | import { Text } from '../Text';
10 | import {
11 | NavigationMenu,
12 | NavigationMenuContent,
13 | NavigationMenuItem,
14 | NavigationMenuLink,
15 | NavigationMenuList,
16 | NavigationMenuProps,
17 | NavigationMenuTrigger,
18 | NavigationMenuVariants,
19 | } from './NavigationMenu';
20 |
21 | const BaseNavigationMenu = (props: NavigationMenuProps): JSX.Element => (
22 |
23 | );
24 |
25 | const NavigationMenuForStory = modifyVariantsForStory(
26 | BaseNavigationMenu,
27 | );
28 |
29 | const Component: Meta = {
30 | title: 'Components/NavigationMenu',
31 | component: NavigationMenuForStory,
32 | };
33 |
34 | const Template: StoryFn = (args) => (
35 |
36 |
37 |
38 |
39 |
40 | Links
41 |
42 |
43 |
44 | A link
45 |
46 |
47 | Another link
48 |
49 |
50 |
51 |
52 |
53 |
54 | Navigation items
55 |
56 |
57 |
58 | }>Profile
59 |
60 |
61 | }>Settings
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 |
70 | export const Basic: StoryFn = Template.bind({});
71 |
72 | export default Component;
73 |
--------------------------------------------------------------------------------
/components/NavigationMenu/NavigationMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
2 | import React from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 | import { elevationVariants } from '../Elevation';
6 | import { panelStyles } from '../Panel';
7 |
8 | // #region Root
9 |
10 | const StyledRoot = styled(NavigationMenuPrimitive.Root, {
11 | ul: {
12 | m: 0,
13 | p: 0,
14 | listStyleType: 'none',
15 | li: {
16 | position: 'relative',
17 | display: 'inline',
18 | },
19 | },
20 | });
21 |
22 | export type NavigationMenuProps = React.ComponentProps & {
23 | children: React.ReactNode;
24 | css?: CSS;
25 | };
26 |
27 | export type NavigationMenuVariants = VariantProps;
28 |
29 | export const NavigationMenu = React.forwardRef<
30 | React.ElementRef,
31 | NavigationMenuProps
32 | >(({ children, ...props }, fowardedRef) => (
33 |
34 | {children}
35 |
36 | ));
37 |
38 | // #endregion Root
39 |
40 | // #region Content
41 |
42 | const StyledContent = styled(NavigationMenuPrimitive.Content, panelStyles, {
43 | position: 'absolute',
44 | left: 0,
45 | zIndex: 1,
46 | p: '$2',
47 | color: '$hiContrast',
48 | '&:focus': {
49 | outline: 'none',
50 | },
51 | variants: {
52 | elevation: elevationVariants,
53 | },
54 | defaultVariants: {
55 | elevation: 2,
56 | },
57 | });
58 |
59 | type NavigationMenuPrimitiveContentProps = Omit<
60 | React.ComponentProps,
61 | 'as'
62 | >;
63 |
64 | export type NavigationMenuContentProps = NavigationMenuPrimitiveContentProps &
65 | VariantProps & {
66 | css?: CSS;
67 | };
68 |
69 | export const NavigationMenuContent = React.forwardRef<
70 | React.ElementRef,
71 | NavigationMenuContentProps
72 | >(({ children, elevation, ...props }, fowardedRef) => (
73 |
74 | {children}
75 |
76 | ));
77 |
78 | // #endregion Content
79 |
80 | // #region Trigger
81 |
82 | const StyledTrigger = styled(NavigationMenuPrimitive.Trigger, {
83 | backgroundColor: 'transparent',
84 | border: 'none',
85 | });
86 |
87 | export type NavigationMenuTriggerProps = React.ComponentProps<
88 | typeof NavigationMenuPrimitive.Trigger
89 | > &
90 | VariantProps & {
91 | css?: CSS;
92 | };
93 |
94 | export const NavigationMenuTrigger = ({ children, ...props }: NavigationMenuTriggerProps) => (
95 | {children}
96 | );
97 |
98 | // #endregion Trigger
99 |
100 | export const NavigationMenuIndicator = NavigationMenuPrimitive.Indicator;
101 | export const NavigationMenuItem = NavigationMenuPrimitive.Item;
102 | export const NavigationMenuLink = NavigationMenuPrimitive.Link;
103 | export const NavigationMenuList = NavigationMenuPrimitive.List;
104 | export const NavigationMenuViewport = NavigationMenuPrimitive.Viewport;
105 |
--------------------------------------------------------------------------------
/components/NavigationMenu/index.ts:
--------------------------------------------------------------------------------
1 | export * from './NavigationMenu';
2 |
--------------------------------------------------------------------------------
/components/NavigationTree/NavigationTreeContainer.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons';
2 | import React from 'react';
3 |
4 | import { CSS } from '../../stitches.config';
5 | import { NavigationContainer, NavigationContainerProps } from '../Navigation';
6 |
7 | export interface NavigationTreeProps {
8 | children: React.ReactNode;
9 | defaultExpandIcon?: React.ReactNode;
10 | defaultCollapseIcon?: React.ReactNode;
11 | css?: CSS;
12 | fullWidth?: boolean;
13 | }
14 |
15 | export const NavigationTreeContainer = ({
16 | children,
17 | defaultCollapseIcon = ,
18 | defaultExpandIcon = ,
19 | fullWidth = false,
20 | ...props
21 | }: NavigationTreeProps & NavigationContainerProps) => {
22 | const renderChildren = React.Children.map(children, (child) => {
23 | return React.cloneElement(child as React.ReactElement, {
24 | defaultCollapseIcon,
25 | defaultExpandIcon,
26 | fullWidth,
27 | });
28 | });
29 |
30 | return {renderChildren};
31 | };
32 |
--------------------------------------------------------------------------------
/components/NavigationTree/NavigationTreeDrawer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CSS } from '../../stitches.config';
4 | import { NavigationDrawer, NavigationDrawerProps } from '../Navigation';
5 |
6 | export interface NavigationTreeDrawerProps {
7 | children: React.ReactNode;
8 | css?: CSS;
9 | fullWidth?: boolean;
10 | }
11 |
12 | export const NavigationTreeDrawer = ({
13 | children,
14 | fullWidth = false,
15 | ...props
16 | }: NavigationTreeDrawerProps & NavigationDrawerProps) => {
17 | const renderChildren = React.Children.map(children, (child) => {
18 | return React.cloneElement(child as React.ReactElement, {
19 | fullWidth,
20 | });
21 | });
22 |
23 | return (
24 |
25 | {renderChildren}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/components/NavigationTree/NavigationTreeItem.stories.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ArchiveIcon,
3 | DashboardIcon,
4 | EnvelopeClosedIcon,
5 | EnvelopeOpenIcon,
6 | EyeClosedIcon,
7 | EyeOpenIcon,
8 | GearIcon,
9 | PersonIcon,
10 | QuestionMarkCircledIcon,
11 | } from '@radix-ui/react-icons';
12 | import { Meta, StoryFn } from '@storybook/react';
13 | import React, { useState } from 'react';
14 |
15 | import { NavigationDrawer } from '../Navigation';
16 | import { NavigationTreeContainer } from './NavigationTreeContainer';
17 | import { NavigationTreeItem } from './NavigationTreeItem';
18 |
19 | const Component: Meta = {
20 | title: 'Components/NavigationTree',
21 | component: NavigationTreeItem,
22 | argTypes: {
23 | customExpandIcon: {
24 | options: ['Default', 'Eye', 'Envelope'],
25 | mapping: {
26 | Default: undefined,
27 | Eye: ,
28 | Envelope: ,
29 | },
30 | },
31 | customCollapseIcon: {
32 | options: ['Default', 'Eye', 'Envelope'],
33 | mapping: {
34 | Default: undefined,
35 | Eye: ,
36 | Envelope: ,
37 | },
38 | },
39 | defaultCollapseIcon: {
40 | options: ['Default', 'Eye', 'Envelope'],
41 | mapping: {
42 | Default: undefined,
43 | Eye: ,
44 | Envelope: ,
45 | },
46 | },
47 | defaultExpandIcon: {
48 | options: ['Default', 'Eye', 'Envelope'],
49 | mapping: {
50 | Default: undefined,
51 | Eye: ,
52 | Envelope: ,
53 | },
54 | },
55 | label: {
56 | control: 'text',
57 | },
58 | startAdornment: {
59 | options: ['None', 'Dashboard', 'Gear', 'Person', 'Question'],
60 | mapping: {
61 | None: null,
62 | Dashboard: ,
63 | Gear: ,
64 | Person: ,
65 | Question: ,
66 | },
67 | },
68 | endAdornment: {
69 | options: ['None', 'Dashboard', 'Gear', 'Person', 'Question'],
70 | mapping: {
71 | None: null,
72 | Dashboard: ,
73 | Gear: ,
74 | Person: ,
75 | Question: ,
76 | },
77 | },
78 | active: {
79 | control: 'boolean',
80 | },
81 | },
82 | };
83 |
84 | const Template: StoryFn = (args) => {
85 | const [currentRoute, setCurrentRoute] = useState('/');
86 |
87 | const navigationHandlerProps = (route: string) => ({
88 | active: route === currentRoute,
89 | onClick: () => setCurrentRoute(route),
90 | });
91 |
92 | return (
93 |
94 |
98 |
99 |
100 | }
103 | label="One.One.One"
104 | />
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
112 | export const TreeItem: StoryFn = Template.bind({});
113 | TreeItem.args = {
114 | label: 'One.One',
115 | };
116 |
117 | export default Component;
118 |
--------------------------------------------------------------------------------
/components/NavigationTree/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './NavigationTreeContainer';
2 | export * from './NavigationTreeDrawer';
3 | export * from './NavigationTreeItem';
4 |
--------------------------------------------------------------------------------
/components/Overlay.tsx:
--------------------------------------------------------------------------------
1 | import { CSS, styled } from '../stitches.config';
2 |
3 | export const overlayStyles: CSS = {
4 | backgroundColor: 'hsla(0, 0%, 0%, 0.7)',
5 | };
6 |
7 | export const Overlay = styled('div', overlayStyles);
8 |
--------------------------------------------------------------------------------
/components/Panel.tsx:
--------------------------------------------------------------------------------
1 | import { CSS, styled } from '../stitches.config';
2 |
3 | export const panelStyles: CSS = {
4 | backgroundColor: '$deepBlue3',
5 | borderRadius: '$3',
6 | boxShadow: '$colors$shadowLight 0px 10px 38px -10px, $colors$deepBlue3 0px 10px 20px -15px',
7 | };
8 |
9 | export const Panel = styled('div', panelStyles);
10 |
--------------------------------------------------------------------------------
/components/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import merge from 'lodash.merge';
2 | import React from 'react';
3 |
4 | import { CSS, VariantProps } from '../stitches.config';
5 | import { Text } from './Text';
6 |
7 | const DEFAULT_TAG = 'p';
8 |
9 | type TextSizeVariants = Pick, 'size'>;
10 |
11 | type ParagraphSizeVariants = '1' | '2';
12 | type ParagraphVariants = { size?: ParagraphSizeVariants } & Omit, 'size'>;
13 | type ParagraphProps = React.ComponentProps &
14 | ParagraphVariants & { css?: CSS; as?: any };
15 |
16 | export const Paragraph = React.forwardRef, ParagraphProps>(
17 | (props, forwardedRef) => {
18 | // '2' here is the default Paragraph size variant
19 | const { size = '1', ...textProps } = props;
20 |
21 | // This is the mapping of Paragraph Variants to Text variants
22 | const textSize: Record = {
23 | 1: { '@initial': '3', '@bp2': '4' },
24 | 2: { '@initial': '5', '@bp2': '6' },
25 | };
26 |
27 | // This is the mapping of Paragraph Variants to Text css
28 | const textCss: Record = {
29 | 1: { lineHeight: '25px', '@bp2': { lineHeight: '27px' } },
30 | 2: { color: '$slate11', lineHeight: '27px', '@bp2': { lineHeight: '30px' } },
31 | };
32 | return (
33 |
42 | );
43 | }
44 | );
45 |
--------------------------------------------------------------------------------
/components/Popover/Popover.tsx:
--------------------------------------------------------------------------------
1 | import * as PopoverPrimitive from '@radix-ui/react-popover';
2 | import React from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 | import { elevationVariants } from '../Elevation';
6 | import { panelStyles } from '../Panel';
7 |
8 | export type PopoverProps = React.ComponentProps & {
9 | children: React.ReactNode;
10 | };
11 | export type PopoverVariants = VariantProps;
12 |
13 | export function Popover({ children, ...props }: PopoverProps) {
14 | return {children};
15 | }
16 |
17 | const StyledContent = styled(PopoverPrimitive.Content, panelStyles, {
18 | minWidth: 200,
19 | minHeight: '$6',
20 | maxWidth: 265,
21 | color: '$hiContrast',
22 | '&:focus': {
23 | outline: 'none',
24 | },
25 | variants: {
26 | elevation: elevationVariants,
27 | },
28 | defaultVariants: {
29 | elevation: 2,
30 | },
31 | });
32 |
33 | const fillColorVariants: Record = {
34 | 0: {
35 | fill: '$00dp',
36 | },
37 | 1: {
38 | fill: '$01dp',
39 | },
40 | 2: {
41 | fill: '$02dp',
42 | },
43 | 3: {
44 | fill: '$03dp',
45 | },
46 | 4: {
47 | fill: '$04dp',
48 | },
49 | 5: {
50 | fill: '$05dp',
51 | },
52 | };
53 |
54 | const StyledArrow = styled(PopoverPrimitive.Arrow, {
55 | fill: 'currentColor',
56 | variants: {
57 | elevation: fillColorVariants,
58 | },
59 | defaultVariants: {
60 | elevation: 2,
61 | },
62 | });
63 |
64 | type PopoverContentPrimitiveProps = Omit<
65 | React.ComponentProps,
66 | 'as'
67 | >;
68 | export type PopoverContentProps = PopoverContentPrimitiveProps &
69 | VariantProps & {
70 | css?: CSS;
71 | hideArrow?: boolean;
72 | arrowCss?: CSS;
73 | };
74 |
75 | export const PopoverContent = React.forwardRef<
76 | React.ElementRef,
77 | PopoverContentProps
78 | >(({ children, hideArrow, arrowCss, elevation, ...props }, fowardedRef) => (
79 |
80 | {children}
81 | {!hideArrow && (
82 |
83 | )}
84 |
85 | ));
86 |
87 | export const PopoverTrigger = PopoverPrimitive.Trigger;
88 | export const PopoverClose = PopoverPrimitive.Close;
89 | export const PopoverAnchor = PopoverPrimitive.Anchor;
90 | export const PopoverPortal = PopoverPrimitive.Portal;
91 |
--------------------------------------------------------------------------------
/components/Popover/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Popover';
2 |
--------------------------------------------------------------------------------
/components/Radio/Radio.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { VariantProps } from '../../stitches.config';
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Radio, RadioGroup, RadioProps, RadioVariants } from './Radio';
7 |
8 | const BaseRadio = (props: RadioProps): JSX.Element => ;
9 | const RadioForStory = modifyVariantsForStory(BaseRadio);
10 |
11 | type RadioGroupVariants = VariantProps;
12 | type RadioGroupProps = RadioGroupVariants & NonNullable;
13 |
14 | const BaseRadioGroup = (props: RadioGroupProps): JSX.Element => ;
15 | const RadioGroupForStory = modifyVariantsForStory<
16 | RadioGroupVariants,
17 | RadioGroupProps & React.InputHTMLAttributes
18 | >(BaseRadioGroup);
19 |
20 | const Component: Meta = {
21 | title: 'Components/Radio',
22 | component: RadioForStory,
23 | };
24 |
25 | const Template: StoryFn = ({ value, ...rest }) => (
26 |
27 |
28 |
29 |
30 | );
31 |
32 | export const Basic: StoryFn = Template.bind({});
33 |
34 | Basic.args = {};
35 |
36 | export const Size: StoryFn = Template.bind({});
37 |
38 | Size.args = {
39 | size: '2',
40 | };
41 |
42 | export const Disabled: StoryFn = Template.bind({});
43 |
44 | Disabled.args = {
45 | disabled: true,
46 | size: 2,
47 | };
48 |
49 | export default Component;
50 |
--------------------------------------------------------------------------------
/components/Radio/Radio.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 | export namespace Theme {
6 | type Colors = {
7 | radioBorder: Property.Color;
8 | radioHoverBg: Property.Color;
9 | radioHoverBorder: Property.Color;
10 | radioFocusBorder: Property.Color;
11 | radioDisabledBg: Property.Color;
12 | radioDisabledBorder: Property.Color;
13 | radioIndicatorDisabledBg: Property.Color;
14 | };
15 |
16 | type Factory = (primaryColor: ColorInfo) => Colors;
17 |
18 | export const getLight: Factory = (primaryColor) => ({
19 | radioIndicator: '$primary',
20 | radioBorder: '$grayBlue9',
21 | radioHoverBg: 'transparent',
22 | radioHoverBorder: '$primary',
23 | radioFocusBorder: '$primary',
24 | radioDisabledBg: '$deepBlue3',
25 | radioDisabledBorder: '$deepBlue5',
26 | radioIndicatorDisabledBg: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
27 | });
28 |
29 | export const getDark: Factory = (primaryColor) => ({
30 | radioIndicator: '$primary',
31 | radioBorder: '$grayBlue9',
32 | radioHoverBg: '$deepBlue3',
33 | radioHoverBorder: '$primary',
34 | radioFocusBorder: '$primary',
35 | radioDisabledBg: '$deepBlue3',
36 | radioDisabledBorder: '$deepBlue4',
37 | radioIndicatorDisabledBg: tinycolor(primaryColor.value).setAlpha(0.6).toHslString(),
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/components/Radio/Radio.tsx:
--------------------------------------------------------------------------------
1 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
2 | import React from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 |
6 | export const RadioGroup = styled(RadioGroupPrimitive.Root, {
7 | display: 'flex',
8 | });
9 |
10 | export const INDICATOR_BASE_STYLES = {
11 | alignItems: 'center',
12 | display: 'flex',
13 | height: '100%',
14 | justifyContent: 'center',
15 | width: '100%',
16 | position: 'relative',
17 | '&::after': {
18 | content: '""',
19 | display: 'block',
20 | width: '7px',
21 | height: '7px',
22 | borderRadius: '$round',
23 | backgroundColor: '$contentBg',
24 | },
25 | };
26 |
27 | const StyledIndicator = styled(RadioGroupPrimitive.Indicator, INDICATOR_BASE_STYLES);
28 |
29 | export const RADIO_BASE_STYLES = {
30 | all: 'unset',
31 | boxSizing: 'border-box',
32 | userSelect: 'none',
33 | '&::before': {
34 | boxSizing: 'border-box',
35 | },
36 | '&::after': {
37 | boxSizing: 'border-box',
38 | },
39 | alignItems: 'center',
40 | appearance: 'none',
41 | display: 'inline-flex',
42 | justifyContent: 'center',
43 | lineHeight: '1',
44 | margin: '0',
45 | outline: 'none',
46 | padding: '0',
47 | textDecoration: 'none',
48 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
49 |
50 | borderRadius: '50%',
51 | color: '$hiContrast',
52 | boxShadow: 'inset 0 0 0 1px $colors$radioBorder',
53 | overflow: 'hidden',
54 | '&[data-state=checked]': {
55 | backgroundColor: '$radioIndicator',
56 | },
57 | };
58 | const StyledRadio = styled(RadioGroupPrimitive.Item, RADIO_BASE_STYLES, {
59 | '@hover': {
60 | '&:hover': {
61 | cursor: 'pointer',
62 | boxShadow: 'inset 0 0 0 1px $colors$radioHoverBorder',
63 | },
64 | },
65 | '&:focus-visible': {
66 | outline: 'none',
67 | boxShadow: 'inset 0 0 0 1px $colors$radioFocusBorder, 0 0 0 1px $colors$radioFocusBorder',
68 | },
69 |
70 | '&:disabled': {
71 | pointerEvents: 'none',
72 | '&::placeholder': {
73 | color: '$radioDisabledText',
74 | },
75 | '&[data-state=checked]': {
76 | backgroundColor: '$radioIndicatorDisabledBg',
77 | },
78 |
79 | [`& ${StyledIndicator}`]: {
80 | '&::after': {
81 | backgroundColor: '$radioDisabledBg',
82 | },
83 | },
84 | },
85 |
86 | variants: {
87 | size: {
88 | '1': {
89 | width: '$3',
90 | height: '$3',
91 | },
92 | '2': {
93 | width: '$5',
94 | height: '$5',
95 |
96 | [`& ${StyledIndicator}`]: {
97 | '&::after': {
98 | width: '$3',
99 | height: '$3',
100 | },
101 | },
102 | },
103 | },
104 | },
105 | defaultVariants: {
106 | size: '1',
107 | },
108 | });
109 |
110 | export type RadioVariants = VariantProps;
111 | type RadioGroupItemPrimitiveProps = Omit<
112 | React.ComponentProps,
113 | 'as'
114 | >;
115 | export type RadioProps = RadioGroupItemPrimitiveProps & RadioVariants & { css?: CSS };
116 |
117 | export const Radio = React.forwardRef, RadioProps>(
118 | (props, forwardedRef) => (
119 |
120 |
121 |
122 | )
123 | );
124 |
--------------------------------------------------------------------------------
/components/Radio/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Radio';
2 |
--------------------------------------------------------------------------------
/components/RadioAccordion/RadioAccordion.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { AccordionContent, AccordionItem, AccordionRoot, AccordionTrigger } from '../Accordion';
5 | import { Box } from '../Box';
6 | import { Text } from '../Text';
7 | import {
8 | RadioAccordionContent,
9 | RadioAccordionItem,
10 | RadioAccordionRoot,
11 | RadioAccordionTrigger,
12 | } from './RadioAccordion';
13 |
14 | const Component: Meta = {
15 | title: 'Components/RadioAccordion',
16 | component: RadioAccordionRoot,
17 | };
18 |
19 | export const Basic: StoryFn = (args) => (
20 |
21 |
22 |
23 | Item1 Trigger
24 | Item1 Content
25 |
26 |
27 | Item2 Trigger
28 | Item2 Content
29 |
30 |
31 | Item3 Trigger
32 | Item3 Content
33 |
34 |
35 |
36 | );
37 |
38 | export const UnderAccordion: StoryFn = (args) => (
39 |
40 |
41 |
42 | Open the accordion
43 |
44 |
45 |
46 |
47 | Item1 Trigger
48 | Item1 Content
49 |
50 |
51 | Item2 Trigger
52 | Item2 Content
53 |
54 |
55 | Item3 Trigger
56 | Item3 Content
57 |
58 |
59 |
60 |
61 |
62 | );
63 |
64 | export default Component;
65 |
--------------------------------------------------------------------------------
/components/RadioAccordion/RadioAccordion.tsx:
--------------------------------------------------------------------------------
1 | import type { AccordionSingleProps } from '@radix-ui/react-accordion';
2 | import React, { ComponentProps } from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 | import {
6 | AccordionContent,
7 | AccordionItem,
8 | AccordionRoot,
9 | AccordionTriggerProps,
10 | StyledAccordionHeader,
11 | StyledAccordionTrigger,
12 | } from '../Accordion';
13 | import { INDICATOR_BASE_STYLES, RADIO_BASE_STYLES } from '../Radio';
14 |
15 | export interface RadioAccordionRootProps
16 | extends Omit,
17 | Omit, 'type' | 'collapsible'> {
18 | css?: CSS; // could not skipped
19 | }
20 |
21 | export const RadioAccordionRoot: (props: RadioAccordionRootProps) => JSX.Element = (props) => (
22 |
23 | );
24 |
25 | export interface RadioAccordionItemProps
26 | extends ComponentProps,
27 | VariantProps {}
28 | export const RadioAccordionItem = React.forwardRef<
29 | React.ElementRef,
30 | RadioAccordionItemProps
31 | >(({ value, children, ...props }, forwardedRef) => {
32 | return (
33 |
39 | {children}
40 |
41 | );
42 | });
43 |
44 | const StyledRadio = styled('div', RADIO_BASE_STYLES, {
45 | width: 18,
46 | height: 18,
47 | mr: '$2',
48 | '[data-state=open] > &': {
49 | backgroundColor: '$radioIndicator',
50 | },
51 | });
52 |
53 | const StyledIndicator = styled('div', INDICATOR_BASE_STYLES);
54 |
55 | const StyledRadioAccordionTrigger = styled(StyledAccordionTrigger, {
56 | '@hover': {
57 | '&:hover': {
58 | [`& ${StyledRadio}`]: {
59 | boxShadow: 'inset 0 0 0 1px $colors$radioHoverBorder',
60 | },
61 | },
62 | },
63 | });
64 |
65 | export interface RadioAccordionTriggerProps extends AccordionTriggerProps {}
66 | export const RadioAccordionTrigger = React.forwardRef<
67 | React.ElementRef,
68 | RadioAccordionTriggerProps
69 | >(({ children, ...props }, ref) => {
70 | return (
71 |
72 |
73 |
74 |
75 |
76 | {children}
77 |
78 |
79 | );
80 | });
81 |
82 | export const RadioAccordionContent = AccordionContent;
83 |
--------------------------------------------------------------------------------
/components/RadioAccordion/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './RadioAccordion';
2 |
--------------------------------------------------------------------------------
/components/Select/Select.stories.tsx:
--------------------------------------------------------------------------------
1 | import { Meta, StoryFn } from '@storybook/react';
2 | import React from 'react';
3 |
4 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
5 | import { Select, SelectProps, SelectVariants } from './Select';
6 |
7 | const BaseSelect = (props: SelectProps): JSX.Element => ;
8 | const SelectForStory = modifyVariantsForStory<
9 | SelectVariants,
10 | SelectProps & React.InputHTMLAttributes
11 | >(BaseSelect);
12 |
13 | const Component: Meta = {
14 | title: 'Components/Select',
15 | component: SelectForStory,
16 | };
17 |
18 | const Template: StoryFn = (args) => (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 |
28 | export const Basic: StoryFn = Template.bind({});
29 |
30 | Basic.args = {};
31 |
32 | export const Size: StoryFn = Template.bind({});
33 |
34 | Size.args = { size: 'large', placeholder: 'placeholder' };
35 |
36 | export const Variant: StoryFn = Template.bind({});
37 |
38 | Variant.args = { variant: 'ghost', defaultValue: 'option3' };
39 | Variant.argTypes = {
40 | variant: {
41 | control: 'inline-radio',
42 | options: [undefined, 'ghost'],
43 | },
44 | };
45 |
46 | export const State: StoryFn = Template.bind({});
47 |
48 | State.args = { state: 'invalid' };
49 |
50 | export const Disabled: StoryFn = Template.bind({});
51 |
52 | Disabled.args = { disabled: true, defaultValue: 'option3' };
53 |
54 | export const Overflow: StoryFn = ({ width, ...args }) => (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 |
64 | Overflow.args = { width: 100, defaultValue: 'option1', size: 'medium' };
65 |
66 | Overflow.argTypes = {
67 | size: {
68 | control: 'inline-radio',
69 | options: ['small', 'medium', 'large'],
70 | },
71 | width: {
72 | control: 'number',
73 | },
74 | };
75 |
76 | export default Component;
77 |
--------------------------------------------------------------------------------
/components/Select/Select.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 | import { Theme as InputTheme } from '../Input/Input.themes';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | selectBg: Property.Color;
9 | selectBorder: Property.Color;
10 | selectFocusBg: Property.Color;
11 | selectFocusBorder: Property.Color;
12 | selectHoverBg: Property.Color;
13 | selectText: Property.Color;
14 | selectPlaceholder: Property.Color;
15 | selectDisabledText: Property.Color;
16 | selectInvalidBorder: Property.Color;
17 | };
18 |
19 | type Factory = (primaryColor: ColorInfo) => Colors;
20 | type FactoryMapper = (colors: InputTheme.Colors) => Colors;
21 |
22 | const remapInputTheme: FactoryMapper = (inputLightOrDark) => ({
23 | selectBg: inputLightOrDark.inputBg,
24 | selectBorder: inputLightOrDark.inputBorder,
25 | selectFocusBg: inputLightOrDark.inputFocusBg,
26 | selectFocusBorder: inputLightOrDark.inputFocusBorder,
27 | selectHoverBg: inputLightOrDark.inputHoverBg,
28 | selectText: inputLightOrDark.inputText,
29 | selectPlaceholder: inputLightOrDark.inputPlaceholder,
30 | selectDisabledText: inputLightOrDark.inputDisabledText,
31 | selectInvalidBorder: inputLightOrDark.inputInvalidBorder,
32 | });
33 |
34 | export const getLight: Factory = (primaryColor) => ({
35 | ...remapInputTheme(InputTheme.getLight(primaryColor)),
36 | });
37 |
38 | export const getDark: Factory = (primaryColor) => ({
39 | ...remapInputTheme(InputTheme.getDark(primaryColor)),
40 | });
41 | }
42 |
--------------------------------------------------------------------------------
/components/Select/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Select';
2 |
--------------------------------------------------------------------------------
/components/SidePanel/SidePanel.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | sidePanelBackground: Property.Color;
8 | };
9 |
10 | type Factory = (primaryColor?: ColorInfo) => Colors;
11 |
12 | export const getLight: Factory = () => ({
13 | sidePanelBackground: '$deepBlue2',
14 | });
15 |
16 | export const getDark: Factory = () => ({
17 | sidePanelBackground: '$deepBlue3',
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/components/SidePanel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SidePanel';
2 |
--------------------------------------------------------------------------------
/components/Skeleton/Skeleton.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | skeletonBackground: Property.Color;
8 | skeletonAnimation: Property.Color;
9 | };
10 |
11 | type Factory = (primaryColor?: ColorInfo) => Colors;
12 |
13 | export const getLight: Factory = () => ({
14 | skeletonBackground: '$slate4',
15 | skeletonAnimation: '$slate6',
16 | });
17 |
18 | export const getDark: Factory = () => ({
19 | skeletonBackground: '$deepBlue3',
20 | skeletonAnimation: '$deepBlue4',
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/components/Skeleton/Skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { keyframes, styled, VariantProps } from '../../stitches.config';
2 |
3 | const pulse = keyframes({
4 | '0%': { opacity: 0 },
5 | '100%': { opacity: '100%' },
6 | });
7 |
8 | export const Skeleton = styled('div', {
9 | backgroundColor: '$skeletonBackground',
10 | position: 'relative',
11 | overflow: 'hidden',
12 | borderRadius: '3px',
13 | height: 'auto',
14 | width: 'auto',
15 | '&:not(:empty)': {
16 | '& > *': {
17 | visibility: 'hidden',
18 | display: 'block',
19 | },
20 | maxWidth: 'fit-content',
21 | },
22 |
23 | '&::after': {
24 | animationName: `${pulse}`,
25 | animationDuration: '500ms',
26 | animationDirection: 'alternate',
27 | animationIterationCount: 'infinite',
28 | animationTimingFunction: 'ease-in-out',
29 | backgroundColor: '$skeletonAnimation',
30 | borderRadius: 'inherit',
31 | bottom: 0,
32 | content: '""',
33 | left: 0,
34 | position: 'absolute',
35 | right: 0,
36 | top: 0,
37 | },
38 |
39 | variants: {
40 | variant: {
41 | square: {
42 | borderRadius: '$2',
43 | },
44 | circle: {
45 | borderRadius: '$round',
46 | },
47 | badge: {
48 | borderRadius: '$pill',
49 | display: 'inline-flex',
50 | },
51 | button: {
52 | borderRadius: '$3',
53 | display: 'inline-flex',
54 | },
55 | text: {
56 | '&:empty:before': {
57 | content: '"\\00a0"', // adds a space character before element
58 | },
59 | },
60 | },
61 | },
62 | defaultVariants: {
63 | variant: 'text',
64 | },
65 | });
66 |
67 | export type SkeletonVariants = VariantProps;
68 | export type SkeletonProps = SkeletonVariants & NonNullable;
69 |
--------------------------------------------------------------------------------
/components/Skeleton/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Skeleton';
2 |
--------------------------------------------------------------------------------
/components/Switch/Switch.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | switchBackground: Property.Color;
9 | switchActiveBackground: Property.Color;
10 | switchFocusBorder: Property.Color;
11 | switchThumb: Property.Color;
12 | };
13 |
14 | type Factory = (primaryColor: ColorInfo) => Colors;
15 |
16 | export const getLight: Factory = (primaryColor) => ({
17 | switchBackground: tinycolor('black').setAlpha(0.2).toHslString(),
18 | switchActiveBackground: '$primary',
19 | switchFocusBorder: primaryColor.helpers.pickScale(8),
20 | switchThumb: tinycolor('white').setAlpha(0.87).toHslString(),
21 | });
22 |
23 | export const getDark: Factory = (primaryColor) => ({
24 | switchBackground: tinycolor('white').setAlpha(0.2).toHslString(),
25 | switchActiveBackground: '$primary',
26 | switchFocusBorder: primaryColor.helpers.pickScale(11),
27 | switchThumb: tinycolor('black').setAlpha(0.87).toHslString(),
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/components/Switch/Switch.tsx:
--------------------------------------------------------------------------------
1 | import * as SwitchPrimitive from '@radix-ui/react-switch';
2 | import React from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 | import { elevationVariants } from '../Elevation';
6 |
7 | // CONSTANTS
8 | const THUMB_DIAMETER = 14; // @FIXME: shouldn't this size be part of theme ?
9 | const LARGE_RATIO = 1.5;
10 | const THUMB_GAP = 2; // Gap between thumb and background
11 | const ROOT_WIDTH = 32;
12 | const ROOT_HEIGHT = 18;
13 |
14 | // COMPONENTS
15 | const StyledThumb = styled(SwitchPrimitive.Thumb, {
16 | position: 'absolute',
17 | left: 0,
18 | width: THUMB_DIAMETER,
19 | height: THUMB_DIAMETER,
20 | backgroundColor: '$switchThumb',
21 | borderRadius: '$round',
22 | boxShadow: 'rgba(0, 0, 0, 0.3) 0px 0px 1px, rgba(0, 0, 0, 0.2) 0px 1px 2px;',
23 | transition: 'transform 100ms cubic-bezier(0.22, 1, 0.36, 1)',
24 | transform: `translateX(${THUMB_GAP}px)`,
25 | willChange: 'transform',
26 |
27 | '&[data-state="checked"]': {
28 | transform: `translateX(${ROOT_WIDTH - THUMB_GAP - THUMB_DIAMETER}px)`,
29 | },
30 | });
31 |
32 | const StyledSwitch = styled(SwitchPrimitive.Root, {
33 | all: 'unset',
34 | boxSizing: 'border-box',
35 | userSelect: 'none',
36 | '&::before': {
37 | boxSizing: 'border-box',
38 | },
39 | '&::after': {
40 | boxSizing: 'border-box',
41 | },
42 |
43 | // Reset
44 | alignItems: 'center',
45 | display: 'inline-flex',
46 | justifyContent: 'center',
47 | lineHeight: '1',
48 | margin: '0',
49 | outline: 'none',
50 | WebkitTapHighlightColor: 'rgba(0,0,0,0)',
51 |
52 | backgroundColor: '$switchBackground',
53 | borderRadius: '$pill',
54 | position: 'relative',
55 | '@hover': {
56 | '&:hover': {
57 | cursor: 'pointer',
58 | [`& ${StyledThumb}`]: {
59 | ...elevationVariants[1],
60 | },
61 | },
62 | },
63 | '&:focus': {
64 | outline: '2px solid $switchFocusBorder',
65 | [`& ${StyledThumb}`]: {
66 | ...elevationVariants[2],
67 | },
68 | },
69 | '&:disabled': {
70 | pointerEvents: 'none',
71 | opacity: 0.38,
72 | },
73 | '&[data-state="checked"]': {
74 | backgroundColor: '$switchActiveBackground',
75 | },
76 |
77 | variants: {
78 | size: {
79 | '1': {
80 | width: ROOT_WIDTH,
81 | height: ROOT_HEIGHT,
82 | m: '$2',
83 | },
84 | '2': {
85 | width: ROOT_WIDTH * LARGE_RATIO,
86 | height: ROOT_HEIGHT * LARGE_RATIO,
87 | [`& ${StyledThumb}`]: {
88 | width: THUMB_DIAMETER * LARGE_RATIO,
89 | height: THUMB_DIAMETER * LARGE_RATIO,
90 | transform: `translateX(${LARGE_RATIO * THUMB_GAP})`,
91 |
92 | '&[data-state="checked"]': {
93 | transform: `translateX(${LARGE_RATIO * (ROOT_WIDTH - THUMB_GAP - THUMB_DIAMETER)}px)`,
94 | },
95 | },
96 | },
97 | },
98 | },
99 | defaultVariants: {
100 | size: '1',
101 | },
102 | });
103 |
104 | export type SwitchVariants = VariantProps;
105 | type SwitchPrimitiveProps = Omit, 'as'>;
106 | export type SwitchProps = SwitchPrimitiveProps & SwitchVariants & { css?: CSS };
107 |
108 | export const Switch = React.forwardRef, SwitchProps>(
109 | (props, forwardedRef) => (
110 |
111 |
112 |
113 | )
114 | );
115 |
--------------------------------------------------------------------------------
/components/Switch/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Switch';
2 |
--------------------------------------------------------------------------------
/components/Table/Table.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | tableText: Property.Color;
9 | tableSubtleText: Property.Color;
10 | tableHoverBackground: Property.BackgroundColor;
11 | tableActiveText: Property.Color;
12 | tableActiveHoverText: Property.Color;
13 | tableHeaderLayerBackground: Property.BackgroundColor;
14 | tableFooterLayerBackground: Property.BackgroundColor;
15 | tableHeaderText: Property.Color;
16 | tableFooterText: Property.Color;
17 | tableHoverText: Property.Color;
18 | tableRowBorder: Property.BorderBottomColor;
19 | };
20 |
21 | type Factory = (primaryColor: ColorInfo) => Colors;
22 |
23 | export const getLight: Factory = (primaryColor) => ({
24 | tableText: tinycolor('black').setAlpha(0.74).toHslString(),
25 | tableSubtleText: tinycolor('black').setAlpha(0.51).toHslString(),
26 | tableHeaderLayerBackground: tinycolor('black').setAlpha(0.03).toHslString(),
27 | tableFooterLayerBackground: tinycolor('black').setAlpha(0.03).toHslString(),
28 | tableHeaderText: tinycolor('black').setAlpha(0.51).toHslString(),
29 | tableFooterText: tinycolor('black').setAlpha(0.51).toHslString(),
30 | tableHoverBackground: tinycolor('black').setAlpha(0.04).toHslString(),
31 | tableHoverText: tinycolor('black').setAlpha(0.74).toHslString(),
32 | tableActiveText: primaryColor.value,
33 | tableActiveHoverText: tinycolor(primaryColor.value).lighten(10).toHslString(),
34 | tableRowBorder: 'hsl(0, 0%, 87%)',
35 | });
36 |
37 | export const getDark: Factory = (primaryColor) => ({
38 | tableText: tinycolor('white').setAlpha(0.74).toHslString(),
39 | tableSubtleText: tinycolor('white').setAlpha(0.51).toHslString(),
40 | tableHeaderLayerBackground: tinycolor('white').setAlpha(0.02).toHslString(),
41 | tableFooterLayerBackground: tinycolor('white').setAlpha(0.02).toHslString(),
42 | tableHeaderText: tinycolor('white').setAlpha(0.51).toHslString(),
43 | tableFooterText: tinycolor('white').setAlpha(0.51).toHslString(),
44 | tableHoverBackground: tinycolor(primaryColor.value).setAlpha(0.04).toHslString(),
45 | tableHoverText: tinycolor('white').setAlpha(0.74).toHslString(),
46 | tableActiveText: '$primary',
47 | tableActiveHoverText: tinycolor(primaryColor.value).lighten(10).toHslString(),
48 | tableRowBorder: 'hsl(209, 21%, 23%)',
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/components/Table/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Table';
2 |
--------------------------------------------------------------------------------
/components/Tabs/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as TabsPrimitive from '@radix-ui/react-tabs';
2 |
3 | import { styled } from '../../stitches.config';
4 |
5 | export const TabsContainer = styled(TabsPrimitive.Root, {
6 | display: 'flex',
7 | flexDirection: 'column',
8 | });
9 |
10 | export const TabsList = styled(TabsPrimitive.List, {
11 | display: 'flex',
12 | borderBottom: `1px solid $textSubtle`,
13 | });
14 |
15 | export const TabsTrigger = styled(TabsPrimitive.Trigger, {
16 | all: 'unset',
17 | color: '$textSubtle',
18 | fontFamily: 'inherit',
19 | flex: 1,
20 | display: 'flex',
21 | alignItems: 'center',
22 | justifyContent: 'center',
23 | py: '$2',
24 | userSelect: 'none',
25 | '@hover': {
26 | '&:hover': {
27 | cursor: 'pointer',
28 | color: '$hiContrast',
29 | },
30 | },
31 | '&[data-state="active"]': {
32 | color: '$primary',
33 | boxShadow: 'inset 0 -1px 0 0 currentColor, 0 1px 0 0 currentColor',
34 | fontWeight: '$semiBold',
35 | },
36 | });
37 |
38 | export const TabsContent = styled(TabsPrimitive.Content, {
39 | width: '100%',
40 | });
41 |
--------------------------------------------------------------------------------
/components/Tabs/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tabs';
2 |
--------------------------------------------------------------------------------
/components/Text/Text.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | type Colors = {
8 | textSubtle: Property.Color;
9 | textDefault: Property.Color;
10 | textContrast: Property.Color;
11 | textInvalid: Property.Color;
12 | textRed: Property.Color;
13 | };
14 |
15 | type Factory = (primaryColor: ColorInfo) => Colors;
16 |
17 | export const getLight: Factory = () => ({
18 | textSubtle: tinycolor('black').setAlpha(0.51).toHslString(),
19 | textDefault: tinycolor('black').setAlpha(0.74).toHslString(),
20 | textContrast: 'black',
21 | textInvalid: '$red9',
22 | textRed: '$red10',
23 | });
24 |
25 | export const getDark: Factory = () => ({
26 | textSubtle: tinycolor('white').setAlpha(0.51).toHslString(),
27 | textDefault: tinycolor('white').setAlpha(0.74).toHslString(),
28 | textContrast: 'white',
29 | textInvalid: '$red9',
30 | textRed: '$red10',
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/components/Text/Text.tsx:
--------------------------------------------------------------------------------
1 | import { CSS, styled, VariantProps } from '../../stitches.config';
2 |
3 | export const Text = styled('span', {
4 | // Reset
5 | lineHeight: '1',
6 | margin: '0',
7 | fontFamily: '$rubik',
8 | fontVariantNumeric: 'tabular-nums',
9 | display: 'inline-block',
10 |
11 | variants: {
12 | size: {
13 | '0': {
14 | fontSize: '$0',
15 | },
16 | '1': {
17 | fontSize: '$1',
18 | },
19 | '2': {
20 | fontSize: '$2',
21 | },
22 | '3': {
23 | fontSize: '$3',
24 | },
25 | '4': {
26 | fontSize: '$4',
27 | },
28 | '5': {
29 | fontSize: '$5',
30 | },
31 | '6': {
32 | fontSize: '$6',
33 | },
34 | '7': {
35 | fontSize: '$7',
36 | },
37 | '8': {
38 | fontSize: '$8',
39 | },
40 | '9': {
41 | fontSize: '$9',
42 | },
43 | '10': {
44 | fontSize: '$10',
45 | },
46 | '11': {
47 | fontSize: '$11',
48 | },
49 | '12': {
50 | fontSize: '$12',
51 | },
52 | inherit: {
53 | fontSize: 'inherit',
54 | },
55 | },
56 | weight: {
57 | light: {
58 | fontWeight: '$light',
59 | },
60 | regular: {
61 | fontWeight: '$regular',
62 | },
63 | medium: {
64 | fontWeight: '$medium',
65 | },
66 | semiBold: {
67 | fontWeight: '$semiBold',
68 | },
69 | bold: {
70 | fontWeight: '$bold',
71 | },
72 | },
73 | variant: {
74 | red: {
75 | color: '$textRed',
76 | },
77 | subtle: {
78 | color: '$textSubtle',
79 | },
80 | default: {
81 | color: '$textDefault',
82 | },
83 | contrast: {
84 | color: '$textContrast',
85 | },
86 | inherit: {
87 | color: 'inherit',
88 | },
89 | invalid: {
90 | color: '$textInvalid',
91 | },
92 | },
93 | gradient: {
94 | true: {
95 | WebkitBackgroundClip: 'text',
96 | WebkitTextFillColor: 'transparent',
97 | },
98 | },
99 | transform: {
100 | uppercase: {
101 | textTransform: 'uppercase',
102 | },
103 | capitalize: {
104 | // WARNING: this will only work with block elements (display block/inline-block)
105 | // @see https://developer.mozilla.org/en-US/docs/Web/CSS/::first-letter
106 | display: 'inline-block',
107 | '&::first-letter': {
108 | textTransform: 'uppercase',
109 | },
110 | },
111 | capitalizeWords: {
112 | textTransform: 'capitalize',
113 | },
114 | },
115 | noWrap: {
116 | true: {
117 | overflow: 'hidden',
118 | textOverflow: 'ellipsis',
119 | whiteSpace: 'nowrap',
120 | },
121 | },
122 | },
123 | defaultVariants: {
124 | size: '3',
125 | variant: 'default',
126 | },
127 | });
128 |
129 | export type TextVariants = VariantProps;
130 | export type TextProps = TextVariants & { css?: CSS };
131 |
--------------------------------------------------------------------------------
/components/Text/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Text';
2 |
--------------------------------------------------------------------------------
/components/TextField/index.ts:
--------------------------------------------------------------------------------
1 | export * from './TextField';
2 |
--------------------------------------------------------------------------------
/components/Textarea/Textarea.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 | import tinycolor from 'tinycolor2';
3 |
4 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
5 |
6 | export namespace Theme {
7 | export type Colors = {
8 | textareaBg: Property.Color;
9 | textareaBorder: Property.Color;
10 | textareaFocusBg: Property.Color;
11 | textareaFocusBorder: Property.Color;
12 | textareaHoverBg: Property.Color;
13 | textareaText: Property.Color;
14 | textareaPlaceholder: Property.Color;
15 | textareaDisabledText: Property.Color;
16 | textareaInvalidBorder: Property.Color;
17 | };
18 |
19 | type Factory = (primaryColor: ColorInfo) => Colors;
20 |
21 | export const getLight: Factory = (primaryColor) => ({
22 | textareaBg: '$deepBlue1',
23 | textareaBorder: '$grayBlue9',
24 | textareaFocusBg: tinycolor('black').setAlpha(0.15).toHslString(),
25 | textareaFocusBorder: primaryColor.helpers.pickScale(8),
26 | textareaHoverBg: '$whiteA9',
27 | textareaText: tinycolor('black').setAlpha(0.74).toHslString(),
28 | textareaPlaceholder: '$blackA10',
29 | textareaDisabledText: tinycolor('black').setAlpha(0.35).toHslString(),
30 | textareaInvalidBorder: '$red9',
31 | });
32 |
33 | export const getDark: Factory = (primaryColor) => ({
34 | textareaBg: '$grayBlue7',
35 | textareaBorder: '$grayBlue9',
36 | textareaFocusBg: tinycolor('black').setAlpha(0.15).toHslString(),
37 | textareaFocusBorder: primaryColor.helpers.pickScale(11),
38 | textareaHoverBg: '$whiteA4',
39 | textareaText: tinycolor('white').setAlpha(0.8).toHslString(),
40 | textareaPlaceholder: '$whiteA10',
41 | textareaDisabledText: tinycolor('white').setAlpha(0.35).toHslString(),
42 | textareaInvalidBorder: '$red9',
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/components/Textarea/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './Textarea';
2 |
--------------------------------------------------------------------------------
/components/Tooltip/Tooltip.stories.tsx:
--------------------------------------------------------------------------------
1 | import { CrossCircledIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { modifyVariantsForStory } from '../../utils/modifyVariantsForStory';
6 | import { Bubble as BubbleComponent } from '../Bubble';
7 | import { Container } from '../Container';
8 | import { Flex } from '../Flex';
9 | import { Text } from '../Text';
10 | import { Tooltip, TooltipProps, TooltipVariants } from './Tooltip';
11 |
12 | const BaseTooltip = (props: TooltipProps): JSX.Element => ;
13 | const TooltipForStory = modifyVariantsForStory(BaseTooltip);
14 |
15 | const Component: Meta = {
16 | title: 'Components/Tooltip',
17 | component: TooltipForStory,
18 | };
19 |
20 | const Template: StoryFn = (args) => (
21 |
22 |
23 | Tooltip label
24 |
25 |
26 | );
27 |
28 | export const Basic: StoryFn = Template.bind({});
29 |
30 | Basic.args = {
31 | content: 'This is some tooltip text',
32 | };
33 |
34 | export const MultiLine: StoryFn = Template.bind({});
35 |
36 | MultiLine.args = {
37 | multiline: true,
38 | content:
39 | 'This is some tooltip text. This box shows the max amount of text to display. If more room is needed, use a modal instead.',
40 | };
41 |
42 | export const NodeContent: StoryFn = Template.bind({});
43 |
44 | const WarningOption = (
45 |
46 |
47 | Warning message
48 |
49 | );
50 | const DisabledOption = (
51 |
52 |
53 | Disabled message
54 |
55 | );
56 | const HeadingOption = (
57 |
58 | Heading
59 | Content
60 |
61 | );
62 |
63 | NodeContent.args = {
64 | content: WarningOption,
65 | };
66 |
67 | NodeContent.argTypes = {
68 | content: {
69 | options: ['Warning', 'Disabled', 'Heading'],
70 | mapping: {
71 | Warning: WarningOption,
72 | Disabled: DisabledOption,
73 | Heading: HeadingOption,
74 | },
75 | },
76 | };
77 |
78 | export const Bubble: StoryFn = (args) => (
79 | This is a green bubble}>
80 |
81 |
82 | );
83 | export default Component;
84 |
--------------------------------------------------------------------------------
/components/Tooltip/Tooltip.themes.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorInfo } from '../../utils/getPrimaryColorInfo';
4 |
5 | export namespace Theme {
6 | type Colors = {
7 | tooltipContentBg: Property.Color;
8 | tooltipText: Property.Color;
9 | };
10 |
11 | type Factory = (primaryColor: ColorInfo) => Colors;
12 |
13 | export const getLight: Factory = () => ({
14 | tooltipContentBg: '$primary',
15 | tooltipText: 'white',
16 | });
17 |
18 | export const getDark: Factory = () => ({
19 | tooltipContentBg: '$primary',
20 | tooltipText: '$deepBlue2',
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/components/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
2 | import React from 'react';
3 |
4 | import { CSS, styled, VariantProps } from '../../stitches.config';
5 | import { Box } from '../Box';
6 | import { Text } from '../Text';
7 |
8 | export type TooltipProps = React.ComponentProps &
9 | Omit, 'content'> & {
10 | children: React.ReactElement | React.ReactNode;
11 | content: React.ReactElement | React.ReactNode;
12 | multiline?: boolean;
13 | css?: CSS;
14 | };
15 |
16 | const Content = styled(TooltipPrimitive.Content, {
17 | color: '$tooltipText',
18 | backgroundColor: '$tooltipContentBg',
19 | borderRadius: '$3',
20 | padding: '$2',
21 |
22 | variants: {
23 | multiline: {
24 | true: {
25 | maxWidth: 250,
26 | pb: 7,
27 | },
28 | },
29 | },
30 | });
31 |
32 | const ArrowBox = styled(Box, {
33 | color: '$tooltipContentBg',
34 | });
35 |
36 | export const TooltipContent = React.forwardRef<
37 | React.ElementRef,
38 | Partial
39 | >(({ css, multiline, children, ...props }, forwardedRef) => {
40 | const isContentString = React.useMemo(() => typeof children === 'string', [children]);
41 | return (
42 | )}
48 | multiline={multiline}
49 | ref={forwardedRef}
50 | >
51 | {isContentString ? (
52 |
60 | {children}
61 |
62 | ) : (
63 | children
64 | )}
65 |
66 |
74 |
75 |
76 | );
77 | });
78 |
79 | export const Tooltip = React.forwardRef, TooltipProps>(
80 | (
81 | { children, content, open, defaultOpen, onOpenChange, multiline, css, ...props },
82 | forwardedRef,
83 | ) => (
84 |
85 | {children}
86 |
87 |
88 | {content}
89 |
90 |
91 |
92 | ),
93 | );
94 |
95 | export const TooltipRoot = TooltipPrimitive.Root;
96 | export const TooltipTrigger = TooltipPrimitive.Trigger;
97 | export const TooltipPortal = TooltipPrimitive.Portal;
98 |
99 | export type TooltipVariants = VariantProps;
100 |
--------------------------------------------------------------------------------
/components/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tooltip';
2 |
--------------------------------------------------------------------------------
/components/VisuallyHidden/VisuallyHidden.stories.tsx:
--------------------------------------------------------------------------------
1 | import { GearIcon } from '@radix-ui/react-icons';
2 | import { Meta, StoryFn } from '@storybook/react';
3 | import React from 'react';
4 |
5 | import { styled } from '../../stitches.config';
6 | import { Card } from '../Card';
7 | import { Caption, Table } from '../Table';
8 | import { VisuallyHidden } from './VisuallyHidden';
9 |
10 | const FlexButton = styled('button', {
11 | m: 0,
12 | p: 0,
13 | color: '$hiContrast',
14 | border: '1px dashed $hiContrast',
15 | display: 'flex',
16 | background: 'none',
17 | });
18 |
19 | const ContrastDiv = styled('div', {
20 | color: '$hiContrast',
21 | });
22 |
23 | const Component: Meta = {
24 | title: 'Components/VisuallyHidden',
25 | component: VisuallyHidden,
26 | };
27 |
28 | export const Basic: StoryFn = (args) => (
29 |
30 | Hidden visually
31 |
32 | );
33 |
34 | export const HiddenButtonText: StoryFn = (args) => (
35 |
36 |
37 | Settings
38 |
39 | );
40 |
41 | export const AsChild: StoryFn = (args) => (
42 |
43 |
44 |
45 |
46 | Hidden visually
47 |
48 |
49 |
50 |
51 |
52 | Not hidden visually
53 |
54 |
55 |
56 | );
57 |
58 | AsChild.args = {
59 | asChild: true,
60 | };
61 |
62 | export default Component;
63 |
--------------------------------------------------------------------------------
/components/VisuallyHidden/VisuallyHidden.tsx:
--------------------------------------------------------------------------------
1 | import * as VisuallyHiddenPrimitive from '@radix-ui/react-visually-hidden';
2 |
3 | export const VisuallyHidden = VisuallyHiddenPrimitive.Root;
4 |
--------------------------------------------------------------------------------
/components/VisuallyHidden/index.tsx:
--------------------------------------------------------------------------------
1 | export * from './VisuallyHidden';
2 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import { fixupPluginRules } from '@eslint/compat';
2 | import pluginJs from '@eslint/js';
3 | import eslintPluginJsxA11y from 'eslint-plugin-jsx-a11y';
4 | import eslintPluginPrettier from 'eslint-plugin-prettier/recommended';
5 | import eslintPluginReact from 'eslint-plugin-react/configs/recommended.js';
6 | import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
7 | import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
8 | import globals from 'globals';
9 | import tseslint from 'typescript-eslint';
10 |
11 | export default [
12 | pluginJs.configs.recommended,
13 | ...tseslint.configs.recommended,
14 | { files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] },
15 | { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
16 | { languageOptions: { globals: globals.browser } },
17 | {
18 | plugins: {
19 | 'jsx-a11y': eslintPluginJsxA11y,
20 | prettier: eslintPluginPrettier,
21 | react: eslintPluginReact,
22 | 'react-hooks': fixupPluginRules(eslintPluginReactHooks),
23 | 'simple-import-sort': eslintPluginSimpleImportSort,
24 | },
25 | },
26 | {
27 | rules: {
28 | ...eslintPluginReactHooks.configs.recommended.rules,
29 | '@typescript-eslint/ban-ts-comment': 'off',
30 | '@typescript-eslint/no-empty-object-type': 'off',
31 | '@typescript-eslint/no-explicit-any': 'off',
32 | '@typescript-eslint/no-namespace': 'off',
33 | 'simple-import-sort/imports': 'error',
34 | 'simple-import-sort/exports': 'error',
35 | },
36 | },
37 | ];
38 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2 | module.exports = {
3 | testEnvironment: 'jest-environment-jsdom',
4 | moduleDirectories: ['node_modules'],
5 | setupFilesAfterEnv: ['/jest.setup.js'],
6 | testPathIgnorePatterns: ['/node_modules/', '/__tests__/utils'],
7 | };
8 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | // optional: configure or set up a testing framework before each test
2 | // if you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
3 |
4 | // used for __tests__/testing-library.js
5 | // learn more: https://github.com/testing-library/jest-dom
6 | require('@testing-library/jest-dom/extend-expect');
7 |
8 | const { toHaveNoViolations } = require('jest-axe');
9 |
10 | expect.extend(toHaveNoViolations);
11 |
12 | window.ResizeObserver = class ResizeObserver {
13 | constructor(cb) {
14 | this.cb = cb;
15 | }
16 | observe() {
17 | this.cb([{ borderBoxSize: { inlineSize: 0, blockSize: 0 } }]);
18 | }
19 | unobserve() {
20 | // do nothing
21 | }
22 | };
23 |
24 | window.DOMRect = {
25 | fromRect: () => ({
26 | top: 0,
27 | left: 0,
28 | bottom: 0,
29 | right: 0,
30 | width: 0,
31 | height: 0,
32 | }),
33 | };
34 |
--------------------------------------------------------------------------------
/patches/@stitches+react+1.2.8.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@stitches/react/types/css-util.d.ts b/node_modules/@stitches/react/types/css-util.d.ts
2 | index 1668fc2ab56c421894c547b2e5a988166cd90e5f..cfdad3f208ef7f57c89de7fb94cdaecbd7378c63 100644
3 | --- a/node_modules/@stitches/react/types/css-util.d.ts
4 | +++ b/node_modules/@stitches/react/types/css-util.d.ts
5 | @@ -117,3 +117,11 @@ export type $$ScaleValue = typeof $$ScaleValue
6 | export declare const $$ThemeValue: unique symbol
7 |
8 | export type $$ThemeValue = typeof $$ThemeValue
9 | +
10 | +// https://github.com/microsoft/TypeScript/issues/37888#issuecomment-846638356
11 | +export type WithPropertyValue = {
12 | + readonly [K in $$PropertyValue]: T
13 | +}
14 | +export type WithScaleValue = {
15 | + readonly [K in $$ScaleValue]: T;
16 | +}
17 | \ No newline at end of file
18 | diff --git a/node_modules/@stitches/react/types/index.d.ts b/node_modules/@stitches/react/types/index.d.ts
19 | index 8dbcc9cad3f6c556a3f370065dd95300a02dd973..dafadd22e8b6285aadee2630936a7918c9c5b02b 100644
20 | --- a/node_modules/@stitches/react/types/index.d.ts
21 | +++ b/node_modules/@stitches/react/types/index.d.ts
22 | @@ -35,7 +35,7 @@ export type ComponentProps = Component extends ((...args: any[]) => a
23 | /** Returns a type that expects a value to be a kind of CSS property value. */
24 | export type PropertyValue = (
25 | Config extends null
26 | - ? { readonly [K in CSSUtil.$$PropertyValue]: Property }
27 | + ? CSSUtil.WithPropertyValue
28 | : Config extends { [K: string]: any }
29 | ? CSSUtil.CSS<
30 | Config['media'],
31 | @@ -49,7 +49,7 @@ export type PropertyValue = (
34 | Config extends null
35 | - ? { readonly [K in CSSUtil.$$ScaleValue]: Scale }
36 | + ? CSSUtil.WithScaleValue
37 | : Config extends { [K: string]: any }
38 | ? Scale extends keyof Config['theme']
39 | ? `$${string & keyof Config['theme'][Scale]}`
40 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import babel from '@rollup/plugin-babel';
3 |
4 | import pkg from './package.json';
5 |
6 | export default {
7 | input: './index.ts',
8 | output: [
9 | {
10 | file: pkg.exports['.'].require,
11 | format: 'cjs',
12 | },
13 | {
14 | preserveModules: true,
15 | dir: pkg.modulesDir,
16 | format: 'es',
17 | },
18 | ],
19 | external: [
20 | 'react/jsx-runtime',
21 | ...Object.keys(pkg.dependencies || {}),
22 | ...Object.keys(pkg.peerDependencies || {}),
23 | ],
24 | plugins: [
25 | typescript({
26 | clean: true,
27 | tsconfig: 'tsconfig-rollup.json',
28 | typescript: require('typescript'),
29 | }),
30 | babel({
31 | extensions: ['.ts', '.tsx', '.js', '.jsx'],
32 | babelHelpers: 'bundled',
33 | }),
34 | ],
35 | };
36 |
--------------------------------------------------------------------------------
/stories/Introduction.mdx:
--------------------------------------------------------------------------------
1 | import { Meta } from '@storybook/addon-docs';
2 |
3 |
4 |
5 | # Welcome to Faency Design System
6 |
--------------------------------------------------------------------------------
/stories/examples/form.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from '../../components/Link';
4 | import { Button, Card, Checkbox, Flex, H1, Label, TextField } from '../../index';
5 |
6 | export const Form = () => (
7 |
8 | Form Example
9 |
10 |
49 |
50 |
51 | );
52 |
53 | export default {
54 | title: 'Examples/Form',
55 | component: Form,
56 | };
57 |
--------------------------------------------------------------------------------
/tsconfig-rollup.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "jsx": "preserve",
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "outDir": "dist",
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true,
14 | "sourceMap": true,
15 | "strict": true,
16 | "target": "es5",
17 | "noImplicitAny": false,
18 | "incremental": true,
19 | "lib": ["dom", "dom.iterable", "esnext"],
20 | "allowJs": true,
21 | "isolatedModules": true,
22 | "paths": {
23 | "@emotion/core": ["./types/index.d.ts"],
24 | "@storybook/theming": ["./types/index.d.ts"],
25 | "react": ["./node_modules/@types/react"]
26 | }
27 | },
28 | "exclude": ["node_modules", "pages", "**/*.test.tsx"],
29 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "jsx": "react"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "jsx": "preserve",
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "outDir": "dist",
12 | "resolveJsonModule": true,
13 | "skipLibCheck": true,
14 | "sourceMap": true,
15 | "strict": true,
16 | "target": "es5",
17 | "noImplicitAny": false,
18 | "incremental": true,
19 | "lib": ["dom", "dom.iterable", "esnext"],
20 | "allowJs": true,
21 | "isolatedModules": true,
22 | "paths": {
23 | "@emotion/core": ["./types/index.d.ts"],
24 | "@storybook/theming": ["./types/index.d.ts"],
25 | "react": ["./node_modules/@types/react"]
26 | }
27 | },
28 | "exclude": ["node_modules", "pages", "*.test.ts"],
29 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
30 | }
31 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | // must exist even if empty => https://github.com/modulz/stitches/issues/897
2 |
--------------------------------------------------------------------------------
/utils/getPrimaryColorInfo.ts:
--------------------------------------------------------------------------------
1 | import { Property } from '@stitches/react/types/css';
2 |
3 | import { ColorMap } from '../colors';
4 | import { PrimaryColor } from '../stitches.config';
5 |
6 | type ColorScale = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
7 |
8 | type PickScale = (scale: ColorScale, options?: { alpha: boolean }) => Property.Color;
9 |
10 | export type ColorInfo = {
11 | name: PrimaryColor;
12 | token: Property.Color;
13 | value: string;
14 | helpers: {
15 | pickScale: PickScale;
16 | };
17 | };
18 |
19 | const getPrimaryColorInfo = (primaryColor: PrimaryColor, colors: ColorMap): ColorInfo => ({
20 | name: primaryColor,
21 | value: colors[`${primaryColor}9`],
22 | token: `$${primaryColor}9`,
23 | helpers: {
24 | pickScale: (scale, options = { alpha: false }) =>
25 | `$${primaryColor}${options.alpha ? 'A' : ''}${scale}`,
26 | },
27 | });
28 |
29 | export default getPrimaryColorInfo;
30 |
--------------------------------------------------------------------------------
/utils/ignoreArgType.ts:
--------------------------------------------------------------------------------
1 | const ignoreArgType = (key, Story) => {
2 | Story.argTypes = {
3 | ...(Story?.argTypes || {}),
4 | [key]: {
5 | table: {
6 | disable: true,
7 | },
8 | },
9 | };
10 | return Story;
11 | };
12 |
13 | export default ignoreArgType;
14 |
--------------------------------------------------------------------------------
/utils/modifyVariantsForStory.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * This utility is meant to solve this issue: https://github.com/modulz/stitches/issues/239. Namely,
3 | * storybook uses type annotations to auto-generate control options. This is extremely convenient
4 | * for maintaining the storybook; however, it doesn't work well with stitches. This utility should be
5 | * used to modify a stitches component for storybook when that component uses a stitches component's
6 | * variants as props.
7 | *
8 | * In the below example, use `Button` in the app and `ButtonForStory` in storybook.
9 | *
10 | * Example:
11 | *
12 | * import type { StitchesVariants } from "@stitches/react";
13 | * import { styled } from "./stitches.config";
14 | *
15 | * const BaseButton = styled("button", {...});
16 | *
17 | * type ButtonVariants = StitchesVariants;
18 | * export interface ButtonProps extends ButtonVariants {...};
19 | *
20 | * export const Button = ({...}: ButtonProps): JSX.Element => {...};
21 | * export const ButtonForStory = modifyVariantsForStory(Button);
22 | */
23 | export interface StitchesMedia {
24 | [x: string]: any;
25 | initial?: any;
26 | }
27 |
28 | // We exclude these type properties from the `ComponentVariants` type so that storybook can more
29 | // easily understand the type arguments. We exclude `"true"` and `"false"` strings as well since
30 | // stitches also adds these, and they aren't necessary for storybook controls.
31 | type StitchesPropsToExclude = 'true' | 'false' | StitchesMedia;
32 |
33 | export function modifyVariantsForStory(
34 | component: (props: ComponentProps) => JSX.Element
35 | ) {
36 | type StoryFnVariants = {
37 | [Property in keyof ComponentVariants]: Exclude<
38 | ComponentVariants[Property],
39 | StitchesPropsToExclude
40 | >;
41 | };
42 |
43 | type StoryFnProps = Omit & StoryFnVariants;
44 |
45 | return component as unknown as (props: StoryFnProps) => JSX.Element;
46 | }
47 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | optimizeDeps: {
6 | exclude: ['storybook'],
7 | },
8 | plugins: [react()],
9 | });
10 |
--------------------------------------------------------------------------------