├── .babelrc
├── .env
├── .eslintrc.js
├── .github
├── stale.yml
└── workflows
│ ├── ci-tests.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .idea
└── prettier.xml
├── .npmignore
├── .prettierrc.js
├── .storybook
├── main.js
├── preview.js
├── stories.js
├── theme.js
└── ui.js
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── docs
├── common-theme-providers.md
├── emotion-logo.png
├── material-logo.svg
├── storybook-logo.png
├── styled-logo.png
└── theme-panel.png
├── nodemon.json
├── package.json
├── preset.js
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── register.js
├── src
├── __test__
│ └── selectors.test.js
├── actions.js
├── config.js
├── helpers
│ ├── createSelector.js
│ ├── sampleTheme.d.ts
│ └── sampleTheme.js
├── index.d.ts
├── index.js
├── manager
│ ├── UI
│ │ ├── Caption.js
│ │ ├── Caption.styled.js
│ │ ├── IconButton.js
│ │ ├── Text.js
│ │ ├── Toolbar.js
│ │ └── Toolbar.styled.js
│ ├── components
│ │ ├── ColorDetails.js
│ │ ├── ColorDetails.styled.js
│ │ ├── SelectTheme.js
│ │ ├── SelectTheme.styled.js
│ │ ├── ThemeBrowser.js
│ │ └── ThemeBrowser.styled.js
│ ├── editors
│ │ ├── ReactJsonEditor.js
│ │ ├── ReactYamlEditor.js
│ │ └── useEditors.js
│ └── register.js
├── preset
│ ├── index.js
│ └── preview.js
├── preview
│ ├── index.d.ts
│ ├── index.js
│ └── onThemeSwitch.js
├── register.js
├── selectors.js
└── utils
│ ├── clipboard.js
│ ├── colors.js
│ ├── default.js
│ └── index.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "current",
8 | "browsers": "defaults"
9 | }
10 | }
11 | ],
12 | "@babel/preset-react"
13 | ],
14 | "plugins": [
15 | "@babel/plugin-proposal-class-properties"
16 | ]
17 | }
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const prettier = require('./.prettierrc.js');
2 | const error = 2;
3 | const warn = 1;
4 | const ignore = 0;
5 |
6 | module.exports = {
7 | root: true,
8 | extends: ['eslint-config-airbnb', 'plugin:jest/recommended', 'prettier'],
9 | plugins: ['prettier', 'jest', 'react', 'json'],
10 | parser: 'babel-eslint',
11 | parserOptions: {
12 | sourceType: 'module',
13 | },
14 | env: {
15 | browser: true,
16 | es6: true,
17 | node: true,
18 | 'jest/globals': true,
19 | },
20 | rules: {
21 | 'react/jsx-props-no-spreading': 'off',
22 | 'arrow-body-style': 'off',
23 | strict: [error, 'never'],
24 | 'prettier/prettier': [warn, prettier],
25 | quotes: [warn, 'single', { avoidEscape: true }],
26 | 'class-methods-use-this': ignore,
27 | 'arrow-parens': [warn, 'as-needed'],
28 | 'space-before-function-paren': ignore,
29 | 'import/no-unresolved': warn,
30 | 'import/extensions': [
31 | // because of highlight.js and fuse.js
32 | warn,
33 | {
34 | js: 'never',
35 | json: 'always',
36 | },
37 | ],
38 | 'import/no-extraneous-dependencies': [
39 | error,
40 | {
41 | devDependencies: [
42 | 'examples/**',
43 | '**/example/**',
44 | '*.js',
45 | '**/*.test.js',
46 | '**/scripts/*.js',
47 | '**/stories/*.js',
48 | '**/__tests__/*.js',
49 | 'src/**',
50 | ],
51 | peerDependencies: true,
52 | },
53 | ],
54 | 'import/prefer-default-export': ignore,
55 | 'react/prop-types': ignore,
56 | 'react/jsx-wrap-multilines': ignore,
57 | 'react/jsx-indent': ignore,
58 | 'react/jsx-indent-props': ignore,
59 | 'react/jsx-closing-bracket-location': ignore,
60 | 'react/jsx-uses-react': error,
61 | 'react/jsx-uses-vars': error,
62 | 'react/react-in-jsx-scope': error,
63 | 'react/jsx-filename-extension': [
64 | warn,
65 | {
66 | extensions: ['.js', '.jsx'],
67 | },
68 | ],
69 | 'jsx-a11y/accessible-emoji': ignore,
70 | 'jsx-a11y/href-no-hash': ignore,
71 | 'jsx-a11y/label-has-for': ignore,
72 | 'jsx-a11y/anchor-is-valid': ['warn', { aspects: ['invalidHref'] }],
73 | 'react/no-unescaped-entities': ignore,
74 | },
75 | };
76 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 120
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 15
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - pinned
8 | - security
9 | - dependencies
10 | - bug
11 | - help wanted
12 | # Label to use when marking an issue as stale
13 | staleLabel: wontfix
14 | # Comment to post when marking an issue as stale. Set to `false` to disable
15 | markComment: >
16 | This issue has been automatically marked as stale because it has not had
17 | recent activity. It will be closed if no further activity occurs. Thank you
18 | for your contributions.
19 | # Comment to post when closing a stale issue. Set to `false` to disable
20 | closeComment: false
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci-tests.yml:
--------------------------------------------------------------------------------
1 | name: ci_tests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | push:
8 | branches-ignore:
9 | - gh-pages
10 |
11 |
12 | jobs:
13 | test:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v1
17 | - uses: actions/setup-node@v1
18 | with:
19 | node-version: 12
20 | - name: Cache node modules
21 | uses: actions/cache@v1
22 | with:
23 | path: node_modules
24 | key: dependencies
25 | - run: yarn
26 | - run: yarn lint
27 | - run: yarn test
28 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '30 13 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'javascript' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v2
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v1
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v1
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v1
68 |
--------------------------------------------------------------------------------
/.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 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /dist
13 |
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 | .idea
22 |
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /src
2 | /public
3 | /.storybook
4 | /node_modules
5 | /docs
6 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tabWidth: 2,
3 | bracketSpacing: true,
4 | trailingComma: 'all',
5 | arrowParens: 'avoid',
6 | singleQuote: true,
7 | };
8 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: ['./stories.js'],
3 | addons: ["../preset.js", '@storybook/addon-actions', '@storybook/addon-links', /* '@storybook/addon-backgrounds', */ 'storybook-dark-mode']
4 | }
5 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | backgrounds: {
3 | default: 'light',
4 | values: [
5 | {
6 | name: 'light',
7 | value: '#fff',
8 | },
9 | {
10 | name: 'dark',
11 | value: '#444',
12 | },
13 | ],
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/.storybook/stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { ThemeProvider } from 'emotion-theming';
4 |
5 | import { storiesOf } from '@storybook/react';
6 | import { withThemes } from '../src/index.js';
7 |
8 | import { ButtonSolid, ButtonRegular, Text, content } from './ui';
9 | import { theme, themeAlt, darkTheme } from './theme';
10 |
11 | const providerFn = ({ theme, children }) => {
12 | return {children} ;
13 | };
14 |
15 | export const onThemeSwitch = context => {
16 | const { theme } = context;
17 | const background = theme.name === 'Dark theme' ? 'pink' : 'red';
18 | const parameters = {
19 | backgrounds: null,/* {
20 | default: background,
21 | }, */
22 | };
23 | return {
24 | parameters,
25 | };
26 | };
27 |
28 | const getCustomValueSnippet = selectedValue => `color: ${selectedValue?.value}`
29 |
30 |
31 | storiesOf('Button', module)
32 | .addDecorator(
33 | withThemes(ThemeProvider, [theme, themeAlt, darkTheme], {
34 | providerFn,
35 | onThemeSwitch,
36 | // getCustomValueSnippet
37 | }
38 | ),
39 | )
40 | .add('Buttons1', () => Hello Button )
41 | .add('Buttons2', () => Hello Button )
42 | .add('Buttons3', () => {content} );
43 |
44 | storiesOf('Non themable', module).add('Component1', () => (
45 | Component without decorator
46 | ));
47 |
--------------------------------------------------------------------------------
/.storybook/theme.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | white: '#ffffff',
3 | whiteTransparent: 'rgba(255, 255, 255, 0.5)',
4 | blackTransparent: '#e8eae8',
5 | accent1: '#d2093b',
6 | accent2: '#252525',
7 | accent3: '#c2c8cb',
8 | accent4: '#044e7c',
9 | accent5: '#ac924d',
10 | accent6: '#e0a8b4',
11 | accent7: '#425550',
12 | accent8: '#8b8b6b',
13 | accent9: '#9c0935',
14 | };
15 |
16 | const media = {
17 | md: '@media (max-width: 767px)',
18 | sm: '@media (max-width: 499px)',
19 | };
20 |
21 | export const theme = {
22 | name: 'Light theme 1',
23 | palette: {
24 | colors,
25 | },
26 | media,
27 | };
28 |
29 | export const themeAlt = {
30 | ...theme,
31 | name: 'Light theme 2',
32 | palette: {
33 | colors: {
34 | white: '#ffffff',
35 | whiteTransparent: 'rgba(255, 255, 255, 0.5)',
36 | blackTransparent: '#fcfcfc',
37 | accent1: '#3bd9d6',
38 | accent2: '#0a8997',
39 | accent3: '#292b2c',
40 | accent4: '#7c0435',
41 | accent5: '#ac924d',
42 | accent6: '#e0a8b4',
43 | accent7: '#6cb09e',
44 | accent8: '#8b8b6b',
45 | accent9: '#1f595f',
46 | textRed: 'orange',
47 | },
48 | },
49 | };
50 |
51 | export const darkTheme = {
52 | ...theme,
53 | name: 'Dark theme',
54 | palette: {
55 | colors: {
56 | white: '#d7d4d4',
57 | whiteTransparent: 'rgba(255, 255, 255, 0.5)',
58 | blackTransparent: '#707270',
59 | accent1: '#46496c',
60 | accent2: '#c7c7cb',
61 | accent3: '#c2c8cb',
62 | accent4: '#044e7c',
63 | accent5: '#ac924d',
64 | accent6: '#e0a8b4',
65 | accent7: '#425550',
66 | accent8: '#8b8b6b',
67 | accent9: '#9c0935',
68 | bgGrey: '#888888',
69 | textRed: '#Fd1500',
70 | },
71 | },
72 | };
73 |
--------------------------------------------------------------------------------
/.storybook/ui.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import styled from '@emotion/styled';
4 |
5 | export const ButtonSolid = styled.button`
6 | background-color: ${({ theme }) => theme.palette.colors.accent1};
7 | border: none;
8 | border-radius: 2px;
9 | padding: 8px 16px;
10 | margin: 8px;
11 | font-size: 16px;
12 | text-transform: uppercase;
13 | color: ${({ theme }) => theme.palette.colors.white};
14 | min-width: 140px;
15 | min-height: 60px;
16 | `;
17 |
18 | export const ButtonRegular = styled.button`
19 | border: 2px solid ${({ theme }) => theme.palette.colors.accent1};
20 | border-radius: 2px;
21 | background-color: ${({ theme }) => theme.palette.colors.blackTransparent};
22 | padding: 8px 16px;
23 | margin: 8px;
24 | font-size: 16px;
25 | text-transform: uppercase;
26 | color: ${({ theme }) => theme.palette.colors.accent2};
27 | min-width: 140px;
28 | min-height: 60px;
29 | `;
30 |
31 | export const Text = styled.div`
32 | margin: auto;
33 | max-width: 600px;
34 | color: ${({ theme }) => theme.palette.colors.accent2};
35 |
36 | b {
37 | color: ${({ theme }) => theme.palette.colors.accent4};
38 | }
39 |
40 | em {
41 | color: ${({ theme }) => theme.palette.colors.accent6};
42 | background-color: ${({ theme }) => theme.palette.colors.accent9};
43 | }
44 |
45 | i {
46 | color: ${({ theme }) => theme.palette.colors.accent3};
47 | background-color: ${({ theme }) => theme.palette.colors.accent7};
48 | }
49 |
50 | a {
51 | color: ${({ theme }) => theme.palette.colors.accent1};
52 | }
53 |
54 | `;
55 |
56 | export const content = <>
57 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquam sem et tortor consequat id porta nibh venenatis. Faucibus pulvinar elementum integer enim neque . Dui faucibus in ornare quam viverra orci sagittis. Cras tincidunt lobortis feugiat vivamus at augue. Posuere ac ut consequat semper viverra nam libero. Tincidunt id aliquet risus feugiat in ante metus dictum at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere ac ut. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit. Risus nullam eget felis eget nunc lobortis mattis. Rutrum quisque non tellus orci ac auctor. At consectetur lorem donec massa sapien faucibus et. Euismod lacinia at quis risus sed vulputate. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Magna fermentum iaculis eu non diam.
58 |
59 | Non quam lacus suspendisse faucibus interdum posuere lorem ipsum. Eget mi proin sed libero enim sed faucibus turpis. Leo integer malesuada nunc vel risus commodo viverra maecenas accumsan. Molestie at elementum eu facilisis. Vulputate sapien nec sagittis aliquam. Neque ornare aenean euismod elementum nisi quis. Ultrices neque ornare aenean euismod elementum. Ut eu sem integer vitae justo eget magna fermentum. Scelerisque eu ultrices vitae auctor eu augue ut. Fermentum dui faucibus in ornare quam viverra. Pharetra massa massa ultricies mi. Lorem ipsum dolor sit amet consectetur adipiscing.
60 |
61 | Felis donec et odio pellentesque diam volutpat commodo sed egestas. Mauris in aliquam sem fringilla ut. Quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Orci sagittis eu volutpat odio facilisis mauris. Nisi vitae suscipit tellus mauris a diam maecenas sed enim. Ornare suspendisse sed nisi lacus sed viverra. Sit amet est placerat in egestas erat imperdiet sed. Vel pretium lectus quam id. Id leo in vitae turpis massa sed elementum. Diam sollicitudin tempor id eu. Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Cum sociis natoque penatibus et magnis dis parturient.
62 | >
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "statusBar.background": "#15add3",
4 | "statusBar.foreground": "#15202b",
5 | "statusBarItem.hoverBackground": "#1087a5",
6 | "titleBar.activeBackground": "#15add3",
7 | "titleBar.activeForeground": "#15202b",
8 | "titleBar.inactiveBackground": "#15add399",
9 | "titleBar.inactiveForeground": "#15202b99",
10 | "sash.hoverBorder": "#15add3",
11 | "statusBar.border": "#15add3",
12 | "statusBar.debuggingBackground": "#d33b15",
13 | "statusBar.debuggingBorder": "#d33b15",
14 | "statusBar.debuggingForeground": "#e7e7e7",
15 | "statusBarItem.remoteBackground": "#15add3",
16 | "statusBarItem.remoteForeground": "#15202b",
17 | "titleBar.border": "#15add3",
18 | "commandCenter.border": "#15202b99"
19 | },
20 | "peacock.color": "#15add3"
21 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | usulpro@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Oleg Proskurin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/%40react-theming%2Fstorybook-addon)
2 | [](https://react-theming.github.io/storybook-addon)
3 |
4 | # Storybook Addon @ React Theming
5 |
6 | Storybook addon for Styled Components, Emotion, Material-UI and any other theming solution. Allows to develop themed components in isolation.
7 |
8 | ```shell
9 | npm i --save-dev @react-theming/storybook-addon
10 | ```
11 | [Demo](https://react-theming.github.io/storybook-addon)
12 |
13 | 
14 |
15 | ## Features :dizzy:
16 |
17 | - Universal - can be used with any styling library
18 | - Switching between themes from addon panel.
19 | - Change a color and see how it affects to your components
20 | - Easily copy-paste paths of nesting theme props into your code
21 | - Auto changes background
22 | - Supports dark Storybook theme
23 | - Keep selected theme on stories updates
24 |
25 |
26 | ## Usage
27 |
28 | specify addon in `.storybook/main.js`
29 |
30 | ```js
31 | // .storybook/main.js
32 |
33 | module.exports = {
34 | stories: ['../src/**/*.stories.js'],
35 | addons: ['@react-theming/storybook-addon'],
36 | };
37 | ```
38 |
39 | or in `.storybook/addons.js` for older versions of Storybook
40 |
41 | ```js
42 | import '@react-theming/storybook-addon/register';
43 |
44 | ```
45 |
46 | Then you'll need to add a decorator with a ThemeProvider of your library. This project is not related to any particular styling solutions, instead, you can use **any of theme providers** you're using in your project.
47 |
48 | ```js
49 | import ThemeProvider from 'library-of-your-choice';
50 | import { withThemes } from '@react-theming/storybook-addon';
51 | import { theme } from '../src/theme';
52 |
53 | // create decorator
54 | const themingDecorator = withThemes(ThemeProvider, [theme]);
55 | ```
56 |
57 | ThemeProvider should accept a theme via `theme` props. This is usually the case for the most common styling libraries like Styled Components, Emotion, Material-UI.
58 |
59 | In case of non standard ThemeProvider you can pass `providerFn` function in options:
60 |
61 | ```js
62 | const providerFn = ({ theme, children }) => {
63 | return {children} ;
64 | };
65 |
66 | const themingDecorator = withThemes(null, [theme], { providerFn });
67 | ```
68 |
69 | ## Use your output of the selected value
70 |
71 | ```js
72 | // .storybook/preview.js
73 |
74 | import { ThemeProvider } from 'styled-components';
75 | import { addDecorator } from '@storybook/react';
76 | import { withThemes } from '@react-theming/storybook-addon';
77 |
78 | import { theme } from '../src/theme';
79 |
80 | ```
81 |
82 | ### Example getCustomFieldSnippet
83 |
84 | ```js
85 | const selectedValue = {
86 | name: "accent5",
87 | namespace: ["palette", "colors"],
88 | type: "color",
89 | value: "#ac924d"
90 | }
91 |
92 |
93 | const getCustomFieldSnippet = selectedValue => {
94 | const { namespace, name } = selectedValue;
95 | const path = namespace.join('.');
96 | const fullPath = `${path}.${name}`;
97 | const themeProp = `\${({ theme }) => theme.${fullPath}}`;
98 | return themeProp;
99 | };
100 |
101 | // The snippet Func function takes the SelectedValue parameter and returns a string
102 | addDecorator(withThemes(ThemeProvider, [theme], { getCustomFieldSnippet }));
103 |
104 | ```
105 |
106 | ### Example getCustomValueSnippet
107 |
108 | By default, the addon outputs colors in HEX format, if you need some kind of add-in, then pass the colorSnippet parameter.
109 |
110 | ```js
111 | const getCustomValueSnippet = ({value, name, type}) => {
112 | // Here is your code
113 | return value
114 | };
115 |
116 | // The colorSnipept function accepts an object consisting of { value : HEX, name: string, type: color}
117 | addDecorator(withThemes(ThemeProvider, [theme], { getCustomValueSnippet }));
118 |
119 | ```
120 |
121 | BACKGROUND COLOR
122 |
123 | This addon has ability to auto change background color when it detect a dark theme. By default it checks if the theme name contains 'dark'.
124 |
125 | You can customize this behavior by passing `onThemeSwitch` function:
126 |
127 | ```js
128 | export const onThemeSwitch = context => {
129 | const { theme } = context;
130 | const background = theme.name === 'Dark theme' ? '#2c2f33' : 'white';
131 | const parameters = {
132 | backgrounds: {
133 | default: background,
134 | },
135 | // Pass backgrounds: null to disable background switching at all
136 | };
137 | return {
138 | parameters,
139 | };
140 | };
141 |
142 | const themingDecorator = withThemes(null, [theme], { onThemeSwitch });
143 | ```
144 |
145 | This way you can have own checks of what the theme is selected and pass what ever color you need.
146 |
147 | !important: The addon change background color on each theme selecting. In some scenarios you might want to disable this behavior e.g. if you already using addon-backgrounds. You can disable background switching by passing `backgrounds: null` in parameters.
148 |
149 |
150 | Below the use cases for most popular styling libraries:
151 |
152 | ## Using with Emotion
153 |
154 | ```js
155 | // .storybook/preview.js
156 |
157 | import { ThemeProvider } from '@emotion/react';
158 | import { addDecorator } from '@storybook/react';
159 | import { withThemes } from '@react-theming/storybook-addon';
160 |
161 | import { theme } from '../src/theme';
162 |
163 | // pass ThemeProvider and array of your themes to decorator
164 | addDecorator(withThemes(ThemeProvider, [theme]));
165 | ```
166 |
167 |
168 | ## 💅 Using with Styled Components
169 |
170 | ```js
171 | // .storybook/preview.js
172 |
173 | import { ThemeProvider } from 'styled-components';
174 | import { addDecorator } from '@storybook/react';
175 | import { withThemes } from '@react-theming/storybook-addon';
176 |
177 | import { theme } from '../src/theme';
178 |
179 | // pass ThemeProvider and array of your themes to decorator
180 | addDecorator(withThemes(ThemeProvider, [theme]));
181 | ```
182 |
183 |
184 | ## Using with Material-UI
185 |
186 | ```js
187 | // theme.js
188 | import { red } from '@material-ui/core/colors';
189 |
190 | // A custom theme for this app
191 | const theme = {
192 | palette: {
193 | primary: {
194 | main: '#556cd6',
195 | },
196 | secondary: {
197 | main: '#19857b',
198 | },
199 | error: {
200 | main: red.A400,
201 | },
202 | background: {
203 | default: '#fff',
204 | },
205 | },
206 | };
207 |
208 | export default theme;
209 | ```
210 |
211 | ```js
212 | // .storybook/preview.js
213 |
214 | import { ThemeProvider } from '@material-ui/core';
215 | import { createMuiTheme } from '@material-ui/core/styles';
216 | import { addDecorator } from '@storybook/react';
217 | import { withThemes } from '@react-theming/storybook-addon';
218 |
219 | import theme from '../src/theme';
220 |
221 | const providerFn = ({ theme, children }) => {
222 | const muTheme = createMuiTheme(theme);
223 | return {children} ;
224 | };
225 |
226 | // pass ThemeProvider and array of your themes to decorator
227 | addDecorator(withThemes(null, [theme], { providerFn }));
228 | ```
229 |
230 | ```js
231 | // index.js
232 |
233 | import React from 'react';
234 | import ReactDOM from 'react-dom';
235 | import { ThemeProvider } from '@material-ui/core/styles';
236 | import { createMuiTheme } from '@material-ui/core/styles';
237 | import App from './App';
238 | import theme from './theme';
239 |
240 | ReactDOM.render(
241 |
242 |
243 | ,
244 | document.querySelector('#root'),
245 | );
246 |
247 | ```
248 |
249 | There is an example app with CRA, Material-UI and Storybook Addon [Demo](https://react-theming.github.io/theming-material-ui/) [Source](https://github.com/react-theming/theming-material-ui)
250 |
251 | ## Credits
252 |
253 |
256 |
--------------------------------------------------------------------------------
/docs/common-theme-providers.md:
--------------------------------------------------------------------------------
1 | # UI Libraries with Theme Providers
2 |
3 | ## 1 Emotion
4 |
5 | https://emotion.sh/docs/theming
6 |
7 | ```js
8 | /** @jsx jsx */
9 | import { jsx } from '@emotion/core';
10 | import { ThemeProvider } from 'emotion-theming';
11 |
12 | const theme = {
13 | colors: {
14 | primary: 'hotpink',
15 | },
16 | };
17 |
18 | render(
19 |
20 | ({ color: theme.colors.primary })}>some other text
21 | ,
22 | );
23 | ```
24 |
25 | ## 2 Styled Components
26 |
27 | https://styled-components.com/docs/advanced#theming
28 |
29 | ```js
30 | import styled, { ThemeProvider } from 'styled-components';
31 |
32 | // Define our button, but with the use of props.theme this time
33 | const Button = styled.button`
34 | font-size: 1em;
35 | margin: 1em;
36 | padding: 0.25em 1em;
37 | border-radius: 3px;
38 |
39 | /* Color the border and text with theme.main */
40 | color: ${props => props.theme.main};
41 | border: 2px solid ${props => props.theme.main};
42 | `;
43 |
44 | // We are passing a default theme for Buttons that arent wrapped in the ThemeProvider
45 | Button.defaultProps = {
46 | theme: {
47 | main: 'palevioletred',
48 | },
49 | };
50 |
51 | // Define what props.theme will look like
52 | const theme = {
53 | main: 'mediumseagreen',
54 | };
55 |
56 | render(
57 |
58 | Normal
59 |
60 |
61 | Themed
62 |
63 |
,
64 | );
65 | ```
66 |
67 | ## 3 Material-UI
68 |
69 | https://material-ui.com/customization/theming/
70 |
71 | ```js
72 |
73 |
74 |
75 | ```
76 |
--------------------------------------------------------------------------------
/docs/emotion-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-theming/storybook-addon/f3ad642a86fd4e3aa782ee2e663097aa184cdcfb/docs/emotion-logo.png
--------------------------------------------------------------------------------
/docs/material-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/storybook-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-theming/storybook-addon/f3ad642a86fd4e3aa782ee2e663097aa184cdcfb/docs/storybook-logo.png
--------------------------------------------------------------------------------
/docs/styled-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-theming/storybook-addon/f3ad642a86fd4e3aa782ee2e663097aa184cdcfb/docs/styled-logo.png
--------------------------------------------------------------------------------
/docs/theme-panel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-theming/storybook-addon/f3ad642a86fd4e3aa782ee2e663097aa184cdcfb/docs/theme-panel.png
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": [
3 | "./src",
4 | "./.storybook/register.js",
5 | "../theme-name/dist"
6 | ],
7 | "ext": "js"
8 |
9 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@react-theming/storybook-addon",
3 | "description": "Develop themes and themable components with Emotion, Styled Components, Material-UI and your custom solution",
4 | "version": "1.1.10",
5 | "private": false,
6 | "main": "dist/index.js",
7 | "homepage": "https://github.com/react-theming/storybook-addon",
8 | "bugs": {
9 | "url": "https://github.com/react-theming/storybook-addon/issues"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/react-theming/storybook-addon.git"
14 | },
15 | "author": "Oleg Proskurin (https://github.com/UsulPro)",
16 | "scripts": {
17 | "start-storybook": "start-storybook -p 9001 -s public --ci --no-manager-cache",
18 | "prepare": "package-prepare",
19 | "prestart": "relative-deps",
20 | "predev": "relative-deps",
21 | "start": "nodemon --exec yarn start-storybook",
22 | "dev": "nodemon --exec yarn prepare",
23 | "build-storybook": "build-storybook -s public",
24 | "test": "react-scripts test",
25 | "lint": "eslint src",
26 | "lint:fix": "eslint src --fix",
27 | "deploy-storybook": "storybook-to-ghpages"
28 | },
29 | "dependencies": {
30 | "@codemirror/theme-one-dark": "0.19.0",
31 | "@focus-reactive/react-yaml": "^1.1.2",
32 | "@react-theming/flatten": "^0.1.1",
33 | "@react-theming/theme-name": "^1.0.3",
34 | "@react-theming/theme-swatch": "^1.0.0",
35 | "@storybook/addon-devkit": "^1.4.2",
36 | "@usulpro/react-json-view": "^2.0.1",
37 | "color-string": "^1.9.1",
38 | "react-color": "^2.18.0"
39 | },
40 | "relativeDependencies": {
41 | "@react-theming/theme-name": "../theme-name",
42 | "@storybook/addon-devkit": "../addon-development-kit"
43 | },
44 | "eslintConfig": {
45 | "extends": "react-app"
46 | },
47 | "browserslist": {
48 | "production": [
49 | ">0.2%",
50 | "not dead",
51 | "not op_mini all"
52 | ],
53 | "development": [
54 | "last 1 chrome version",
55 | "last 1 firefox version",
56 | "last 1 safari version"
57 | ]
58 | },
59 | "devDependencies": {
60 | "@babel/cli": "^7.13.10",
61 | "@babel/core": "^7.13.10",
62 | "@babel/plugin-proposal-class-properties": "^7.13.0",
63 | "@babel/preset-env": "^7.13.10",
64 | "@babel/preset-react": "^7.12.13",
65 | "@storybook/addon-actions": "^6.5.9",
66 | "@storybook/addon-backgrounds": "^6.5.9",
67 | "@storybook/addon-links": "^6.5.9",
68 | "@storybook/addons": "^6.5.9",
69 | "@storybook/react": "^6.5.9",
70 | "@storybook/storybook-deployer": "^2.8.7",
71 | "@storybook/theming": "^6.5.9",
72 | "@usulpro/package-prepare": "^1.3.1",
73 | "babel-eslint": "^10.0.2",
74 | "babel-loader": "^8.2.2",
75 | "eslint": "^7.22.0",
76 | "eslint-config-airbnb": "^18.1.0",
77 | "eslint-config-prettier": "^8.1.0",
78 | "eslint-plugin-import": "^2.7.0",
79 | "eslint-plugin-jest": "^24.3.2",
80 | "eslint-plugin-json": "^2.1.1",
81 | "eslint-plugin-jsx-a11y": "^6.0.2",
82 | "eslint-plugin-prettier": "^3.1.2",
83 | "eslint-plugin-react": "^7.1.0",
84 | "nodemon": "^2.0.7",
85 | "prettier": "^2.0.2",
86 | "react": "^17.0.2",
87 | "react-dom": "^17.0.2",
88 | "react-scripts": "3.0.1",
89 | "relative-deps": "^1.0.7",
90 | "storybook-dark-mode": "^1.0.7"
91 | },
92 | "peerDependencies": {
93 | "@storybook/react": "*",
94 | "@storybook/theming": "*",
95 | "react": "*"
96 | },
97 | "keywords": [
98 | "storybook",
99 | "storybook-addons",
100 | "style",
101 | "react",
102 | "material",
103 | "ui",
104 | "material-ui",
105 | "emotion",
106 | "styled-components",
107 | "jss",
108 | "addon",
109 | "decorator",
110 | "theme",
111 | "theming",
112 | "themable",
113 | "editor",
114 | "switch themes",
115 | "change colors",
116 | "tool",
117 | "develop",
118 | "isolation",
119 | "themable components",
120 | "customization",
121 | "dark theme",
122 | "light theme",
123 | "storybook-addon",
124 | "appearance"
125 | ],
126 | "storybook": {
127 | "displayName": "React Theming"
128 | },
129 | "resolutions": {
130 | "@usulpro/react-json-view": "^2.0.1"
131 | },
132 | "license": "MIT"
133 | }
134 |
--------------------------------------------------------------------------------
/preset.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./dist/preset");
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/react-theming/storybook-addon/f3ad642a86fd4e3aa782ee2e663097aa184cdcfb/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 | You need to enable JavaScript to run this app.
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/register.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | require("./dist/manager/register");
--------------------------------------------------------------------------------
/src/__test__/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { getTheme, getThemeInfoList } from '../selectors';
2 | import { createTheme } from '../helpers/sampleTheme';
3 |
4 | const firstTheme = createTheme({
5 | mainColor: 'red',
6 | name: 'red-theme',
7 | });
8 | const secondTheme = createTheme({
9 | mainColor: 'green',
10 | name: 'green-theme',
11 | });
12 | const thirdTheme = createTheme({
13 | mainColor: 'blue',
14 | name: 'blue-theme',
15 | });
16 |
17 | const store = {
18 | themesList: [firstTheme, secondTheme, thirdTheme],
19 | currentTheme: 1,
20 | };
21 |
22 | it('should select theme', () => {
23 | expect(getTheme(store)).toEqual(secondTheme);
24 | });
25 |
26 | it('should select theme-names', () => {
27 | const names = ['red-theme', 'green-theme', 'blue-theme'];
28 | expect(getThemeInfoList(store).map(({ name }) => name)).toEqual(names);
29 | });
30 |
--------------------------------------------------------------------------------
/src/actions.js:
--------------------------------------------------------------------------------
1 | import { getCurrentInd, getSelectedValue, getTheme } from './selectors';
2 | import { processWord } from './utils';
3 |
4 | export const setCurrent = (store, ind, api) => {
5 | if (api) {
6 | api.setQueryParams({
7 | themeInd: ind,
8 | });
9 | }
10 | return {
11 | ...store,
12 | currentTheme: ind,
13 | selectedWord: null,
14 | };
15 | };
16 |
17 | export const selectValue = (store, value) => ({
18 | ...store,
19 | selectedValue: value,
20 | selectedWord: null,
21 | });
22 |
23 | export const selectWord = (store, value) => ({
24 | ...store,
25 | selectedValue: null,
26 | selectedWord: processWord(value),
27 | });
28 |
29 | export const updateTheme = (store, ind, newTheme) => {
30 | const { themesList } = store;
31 | const newThemesList = [...themesList];
32 | newThemesList[ind] = newTheme;
33 | const newStore = {
34 | ...store,
35 | themesList: newThemesList,
36 | };
37 | return newStore;
38 | };
39 |
40 | const mutateObj = (obj, namespace, key, value) => {
41 | const nestedObj = namespace.reduce((subObj, subKey) => subObj[subKey], obj);
42 | nestedObj[key] = value;
43 | };
44 |
45 | export const changeSelectedColor = (store, color) => {
46 | const selected = getSelectedValue(store);
47 | if (!selected) return store;
48 | const { name, namespace } = selected;
49 | const theme = getTheme(store);
50 | const ind = getCurrentInd(store);
51 | const themeClone = JSON.parse(JSON.stringify(theme));
52 | mutateObj(themeClone, namespace, name, color);
53 | return updateTheme(store, ind, themeClone);
54 | };
55 |
56 | export const changeTheme = (store, newTheme) => {
57 | const ind = getCurrentInd(store);
58 | const { themesList } = store;
59 | const newThemesList = [...themesList];
60 | newThemesList[ind] = newTheme;
61 | return {
62 | ...store,
63 | themesList: newThemesList,
64 | };
65 | };
66 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | import { setConfig } from '@storybook/addon-devkit';
2 |
3 | setConfig({
4 | addonId: 'theming',
5 | panelTitle: 'Theming',
6 | });
7 |
--------------------------------------------------------------------------------
/src/helpers/createSelector.js:
--------------------------------------------------------------------------------
1 | export const createSelector = (...args) => {
2 | const resultFn = args.pop();
3 | return store => {
4 | const selected = args.map(selector => selector(store));
5 | return resultFn(...selected);
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/src/helpers/sampleTheme.d.ts:
--------------------------------------------------------------------------------
1 | export function createTheme(
2 | theme: T,
3 | ): {
4 | main: string;
5 | textColor: string;
6 | backgroundColor: string;
7 | } & T;
8 |
--------------------------------------------------------------------------------
/src/helpers/sampleTheme.js:
--------------------------------------------------------------------------------
1 | const randomColor = () => {
2 | const c = 127 + Math.floor(Math.random() * 128);
3 | return `rgb(${c}, 60, 60)`;
4 | };
5 |
6 | export const createTheme = theme => ({
7 | main: randomColor(),
8 | textColor: 'hsl(0, 0%, 30%)',
9 | backgroundColor: 'white',
10 | ...theme,
11 | });
12 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./preview";
2 | export * from "./helpers/sampleTheme";
3 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './preview';
2 | export * from './helpers/sampleTheme';
3 |
--------------------------------------------------------------------------------
/src/manager/UI/Caption.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import * as styled from './Caption.styled';
3 |
4 | const Caption = ({ children }) => {children} ;
5 |
6 | export default Caption;
7 |
--------------------------------------------------------------------------------
/src/manager/UI/Caption.styled.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@storybook/theming';
2 |
3 | export const Heading = styled.h3`
4 | font-size: 12px;
5 | margin: 0 4px;
6 | font-weight: 600;
7 | text-transform: capitalize;
8 | `;
9 |
--------------------------------------------------------------------------------
/src/manager/UI/IconButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from '@storybook/theming';
3 |
4 | const Button = styled.button`
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | padding: 0;
9 | border: none;
10 | border-radius: 2px;
11 | background-color: unset;
12 | height: 20px;
13 | width: 20px;
14 | background-repeat: no-repeat;
15 | background-size: contain;
16 | svg {
17 | fill: ${({ isDark }) => (isDark ? 'white' : 'black')};
18 | }
19 |
20 | :hover {
21 | background-color: ${({ isDark }) => (isDark ? 'white' : null)};
22 | svg {
23 | stroke: ${({ isDark }) => (isDark ? '#eeeeee' : '#d4cece')};
24 | }
25 | }
26 | `;
27 |
28 | const copyIcon = (
29 |
35 |
36 |
37 | );
38 |
39 | const icons = {
40 | copy: copyIcon,
41 | };
42 |
43 | const IconButton = ({ onClick, title, icon, isDark }) => {
44 | const svg = icons[icon];
45 | return (
46 |
47 | {svg}
48 |
49 | );
50 | };
51 |
52 | export default IconButton;
53 |
--------------------------------------------------------------------------------
/src/manager/UI/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { styled } from '@storybook/theming';
3 |
4 | const Span = styled.span`
5 | margin-left: 10px;
6 | `;
7 |
8 | const Text = ({ children }) => {children} ;
9 |
10 | export default Text;
11 |
--------------------------------------------------------------------------------
/src/manager/UI/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import * as styled from './Toolbar.styled';
4 |
5 | const Toolbar = ({ children, footer }) => (
6 | {children}
7 | );
8 |
9 | export default Toolbar;
10 |
--------------------------------------------------------------------------------
/src/manager/UI/Toolbar.styled.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@storybook/theming';
2 |
3 | export const Container = styled.div`
4 | background-color: ${({ theme, footer }) =>
5 | footer ? theme.background.hoverable : theme.background.hoverable};
6 |
7 | padding: ${({ footer }) => (footer ? '6px 8px' : '4px 8px')};
8 | display: flex;
9 | flex-direction: row;
10 | justify-content: flex-start;
11 | align-items: center;
12 | `;
13 |
--------------------------------------------------------------------------------
/src/manager/components/ColorDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ChromePicker } from 'react-color';
3 |
4 | import * as styled from './ColorDetails.styled';
5 | import Toolbar from '../UI/Toolbar';
6 | import Caption from '../UI/Caption';
7 | import IconButton from '../UI/IconButton';
8 | import Text from '../UI/Text';
9 | import { copyToClipboard } from '../../utils';
10 |
11 | const ColorDetails = ({
12 | selectedValue,
13 | selectedWord,
14 | onChange,
15 | isSbDark,
16 | colorSnippet,
17 | }) => {
18 | const { value, name, type } = selectedValue || selectedWord || {};
19 | const isColor = type === 'color';
20 |
21 | const handleChange = colorInfo => {
22 | const { hex } = colorInfo;
23 | onChange(hex);
24 | };
25 |
26 | return (
27 |
28 |
29 | {name || 'Select color'}
30 |
31 |
32 | {isColor && (
33 |
34 | )}
35 |
36 |
37 |
43 | {value ? colorSnippet(selectedValue) : 'Select color'}
44 |
45 |
46 | );
47 | };
48 |
49 | export default ColorDetails;
50 |
--------------------------------------------------------------------------------
/src/manager/components/ColorDetails.styled.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@storybook/theming';
2 | import { Block } from '@storybook/addon-devkit';
3 |
4 | export const Container = styled(Block)`
5 | display: flex;
6 | flex-direction: column;
7 | height: auto;
8 | overflow: auto;
9 | label: Container;
10 | `;
11 |
12 | export const PickerHolder = styled.div`
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | background-color: hsla(0, 0%, 50%, 0.35);
17 | height: 1px;
18 | flex-grow: 1;
19 | `;
20 |
--------------------------------------------------------------------------------
/src/manager/components/SelectTheme.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { flattenTheme } from '@react-theming/flatten';
3 |
4 | import * as styled from './SelectTheme.styled';
5 | import Toolbar from '../UI/Toolbar';
6 | import Caption from '../UI/Caption';
7 | import IconButton from '../UI/IconButton';
8 |
9 | const materialPreview = ({ palette }) => ({
10 | main: [palette.primary.main, palette.primary.light, palette.primary.dark],
11 | text: [palette.text.secondary],
12 | accent: [
13 | palette.secondary.main,
14 | palette.secondary.light,
15 | palette.secondary.dark,
16 | ],
17 | background: [palette.text.primary],
18 | });
19 |
20 | const SelectTheme = ({ themeInfoList, themeInd, setCurrent }) => {
21 | if (!themeInfoList) return 'No themes info';
22 | const count = themeInfoList.length;
23 | const isMulti = count > 1;
24 | const isSingle = count <= 1;
25 | return (
26 |
27 |
28 | {isMulti ? `${count} themes` : 'Single Theme'}
29 |
30 |
31 |
32 | {themeInfoList.map(({ name, theme }, ind) => {
33 | let colorList;
34 | if (
35 | theme.palette &&
36 | theme.palette.primary &&
37 | theme.palette.primary.main
38 | ) {
39 | colorList = materialPreview(theme);
40 | } else {
41 | const { flattenColors } = flattenTheme(theme);
42 | colorList = flattenColors.map(({ original }) => original);
43 | }
44 | return (
45 |
46 | setCurrent(ind)}
48 | current={ind === themeInd}
49 | single={isSingle}
50 | >
51 |
52 |
53 |
54 |
55 |
56 | {name}
57 |
58 |
59 | );
60 | })}
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | export default SelectTheme;
71 |
--------------------------------------------------------------------------------
/src/manager/components/SelectTheme.styled.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@storybook/theming';
2 | import { Block } from '@storybook/addon-devkit';
3 | import { createSwatch } from '@react-theming/theme-swatch';
4 |
5 | export const Swatch = createSwatch(styled);
6 |
7 | export const Container = styled(Block)`
8 | display: flex;
9 | flex-direction: column;
10 | height: 100%;
11 | background-color: ${({ theme }) => theme.background.app};
12 | color: ${({ theme }) => theme.input.color};
13 |
14 | ul {
15 | list-style: none;
16 | padding: 0;
17 | margin: 0;
18 | }
19 | `;
20 |
21 | export const ListHolder = styled.div`
22 | overflow: auto;
23 | height: 1px;
24 | flex-grow: 1;
25 | padding: ${({ theme }) => theme.layoutMargin}px;
26 | `;
27 |
28 | export const Theme = styled.button`
29 | border: 1px solid ${({ theme }) => theme.input.border};
30 | ${({ current, theme }) =>
31 | current ? `border-color: ${theme.color.secondary} !important;` : null}
32 |
33 | border-radius: ${({ theme }) => theme.appBorderRadius}px;
34 | background-color: ${({ theme }) => theme.background.hoverable};
35 | margin: ${({ theme }) => Math.floor(theme.layoutMargin / 2)}px 0px;
36 | padding: 0px;
37 | width: 100%;
38 | cursor: pointer;
39 | color: ${({ theme }) => theme.input.color};
40 |
41 | :hover {
42 | border: 1px solid ${({ theme }) => theme.appBorderColor};
43 | }
44 | :focus {
45 | outline: 1px solid ${({ theme }) => theme.color.dark};
46 | outline-offset: 2px;
47 | }
48 |
49 | display: flex;
50 | flex-direction: ${({ single }) => (single ? 'column' : 'row')};
51 | justify-content: flex-start;
52 | align-items: center;
53 | `;
54 |
55 | export const AvatarHolder = styled.div`
56 | position: relative;
57 | width: ${({ single }) => (single ? '120px' : '36px')};
58 | height: ${({ single }) => (single ? '120px' : '36px')};
59 | margin: 16px;
60 | `;
61 |
62 | export const ThemeAvatar = styled.div`
63 | width: 100%;
64 | height: 100%;
65 | `;
66 |
67 | export const Title = styled.h4`
68 | margin-left: 6px;
69 | font-size: ${({ single }) => (single ? '32px' : '16px')};
70 | font-weight: ${({ single }) => (single ? 'bold' : 'normal')};
71 | `;
72 |
--------------------------------------------------------------------------------
/src/manager/components/ThemeBrowser.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import * as styled from './ThemeBrowser.styled';
4 | import Toolbar from '../UI/Toolbar';
5 | import Caption from '../UI/Caption';
6 | import IconButton from '../UI/IconButton';
7 | import Text from '../UI/Text';
8 | import { copyToClipboard } from '../../utils/clipboard';
9 | import { initialEditors, useEditors } from '../editors/useEditors';
10 | import { defaultSnippet } from '../../utils/default';
11 |
12 | const showThemePath = (selectedValue, fieldSnippetFn) => {
13 | if (!selectedValue) return 'Select value';
14 | try {
15 | const fn = fieldSnippetFn || defaultSnippet;
16 | return fn(selectedValue);
17 | } catch (err) {
18 | return 'try to select value';
19 | }
20 | };
21 |
22 | const ThemeBrowser = ({
23 | theme,
24 | isSbDark,
25 | selectValue,
26 | selectWord,
27 | selectedValue,
28 | updateTheme,
29 | fieldSnippetFn,
30 | }) => {
31 | const editors = useEditors(initialEditors);
32 |
33 | const footerAction = showThemePath(selectedValue, fieldSnippetFn);
34 |
35 | const handlerChange = value => updateTheme(value.json);
36 |
37 | return (
38 |
39 |
40 | Editor:
41 |
42 | {editors.editorButtons.map(btn => (
43 |
49 | {btn.title}
50 |
51 | ))}
52 |
53 |
54 |
55 | {editors.renderCurrentEditor({
56 | isDark: isSbDark,
57 | theme,
58 | onChange: handlerChange,
59 | selectValue,
60 | selectWord,
61 | })}
62 |
63 | {footerAction ? (
64 |
65 |
71 | {footerAction}
72 |
73 | ) : null}
74 |
75 | );
76 | };
77 |
78 | export default ThemeBrowser;
79 |
--------------------------------------------------------------------------------
/src/manager/components/ThemeBrowser.styled.js:
--------------------------------------------------------------------------------
1 | import { styled } from '@storybook/theming';
2 | import { Block } from '@storybook/addon-devkit';
3 |
4 | export const Container = styled(Block)`
5 | border-left: 1px solid gray;
6 | border-right: 1px solid gray;
7 | display: flex;
8 | flex-direction: column;
9 | height: auto;
10 | label: Container;
11 | `;
12 |
13 | export const ThemeHolder = styled.div`
14 | height: auto;
15 | overflow: auto;
16 | flex-grow: 1;
17 | label: ThemeHolder;
18 | `;
19 |
20 | const copyIcon =
21 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggZD0iTTE2IDFINGMtMS4xIDAtMiAuOS0yIDJ2MTRoMlYzaDEyVjF6bTMgNEg4Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDExYzEuMSAwIDItLjkgMi0yVjdjMC0xLjEtLjktMi0yLTJ6bTAgMTZIOFY3aDExdjE0eiIvPjwvc3ZnPg=';
22 |
23 | export const Copy = styled.button`
24 | background-color: unset;
25 | border: none;
26 | background: url(${copyIcon});
27 | background-repeat: no-repeat;
28 | background-size: contain;
29 | width: 35px;
30 | height: 20px;
31 | cursor: pointer;
32 | opacity: 0.6;
33 | :hover {
34 | opacity: 1;
35 | }
36 | `;
37 |
38 | export const SelectedCard = styled.div`
39 | background-color: #f6f9fc;
40 | padding: 12px;
41 | margin-top: 4px;
42 | font-size: 16px;
43 | `;
44 |
45 | export const ButtonsEditor = styled.div`
46 | display: flex;
47 | button {
48 | position: relative;
49 | background-color: ${({ isDark }) => (isDark ? '#525252' : '#cbcbcb')};
50 | border: none;
51 | border-radius: 2px;
52 | font-size: 11px;
53 | font-weight: 800;
54 | padding: 1px 8px;
55 | margin: 0 0 0 10px;
56 | color: ${({ isDark }) => (isDark ? '#bcbaba' : '#565454')};
57 | cursor: pointer;
58 |
59 | :hover {
60 | opacity: 0.7;
61 | }
62 | }
63 | button.active {
64 | :hover {
65 | opacity: 1;
66 | }
67 | ::after {
68 | content: '🟢';
69 | font-size: 4px;
70 | position: absolute;
71 | top: 1px;
72 | left: 1px;
73 | }
74 | }
75 | `;
76 |
--------------------------------------------------------------------------------
/src/manager/editors/ReactJsonEditor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactJson from '@usulpro/react-json-view';
3 |
4 | const ReactJsonEditor = ({ isDark, theme, selectValue }) => {
5 | const jsTheme = isDark ? 'codeschool' : 'shapeshifter:inverted';
6 | return (
7 |
8 | );
9 | };
10 |
11 | export default ReactJsonEditor;
12 |
--------------------------------------------------------------------------------
/src/manager/editors/ReactYamlEditor.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import YamlEditor from '@focus-reactive/react-yaml';
3 | import { oneDark } from '@codemirror/theme-one-dark';
4 |
5 | const ReactYamlEditor = ({ isDark, theme, onChange, selectWord }) => {
6 | const merge = React.useCallback(({ json }) => ({ json }), []);
7 | const ownTheme = isDark ? oneDark : undefined;
8 |
9 | const handleSelectWord = ({ word }) => {
10 | selectWord(word);
11 | };
12 |
13 | return (
14 |
21 | );
22 | };
23 |
24 | export default ReactYamlEditor;
25 |
--------------------------------------------------------------------------------
/src/manager/editors/useEditors.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactJsonEditor from './ReactJsonEditor';
3 | import ReactYamlEditor from './ReactYamlEditor';
4 |
5 | export const initialEditors = [
6 | {
7 | name: 'json',
8 | title: 'JSON',
9 | renderComponent: props => ,
10 | },
11 | {
12 | name: 'yaml',
13 | title: 'YAML',
14 | renderComponent: props => ,
15 | },
16 | ];
17 |
18 | export const useEditors = registeredEditors => {
19 | const editorNames = [...new Set(registeredEditors.map(({ name }) => name))];
20 | const [currentEditor, setCurrentEditor] = React.useState(editorNames[0]);
21 |
22 | const editorButtons = registeredEditors.map(editor => ({
23 | ...editor,
24 | isSelected: editor.name === currentEditor,
25 | select: () => setCurrentEditor(editor.name),
26 | }));
27 |
28 | const renderCurrentEditor = props => {
29 | const editor = registeredEditors.find(({ name }) => name === currentEditor);
30 | return editor.renderComponent(props);
31 | };
32 |
33 | return {
34 | editorButtons,
35 | renderCurrentEditor,
36 | };
37 | };
38 |
--------------------------------------------------------------------------------
/src/manager/register.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { register, Layout } from '@storybook/addon-devkit';
3 | import { useTheme } from '@storybook/theming';
4 |
5 | import {
6 | getTheme,
7 | getThemeInfoList,
8 | getThemeInfo,
9 | getSelectedValue,
10 | getCurrentInd,
11 | getSnippet,
12 | getSelectedWord,
13 | getColorSnippet,
14 | } from '../selectors';
15 | import SelectTheme from './components/SelectTheme';
16 | import ThemeBrowser from './components/ThemeBrowser';
17 |
18 | import '../config';
19 | import ColorDetails from './components/ColorDetails';
20 | import * as actions from '../actions';
21 |
22 | const AddonThemingPanel = ({
23 | theme,
24 | themeInd,
25 | themeInfoList,
26 | themeInfo,
27 | selectedValue,
28 | selectedWord,
29 | setCurrent,
30 | selectValue,
31 | selectWord,
32 | changeSelectedColor,
33 | isFirstDataReceived,
34 | api,
35 | snippet,
36 | colorSnippet,
37 | updateTheme,
38 | }) => {
39 | React.useEffect(() => {
40 | if (themeInd === null) {
41 | const storedThemeInd = api.getQueryParam('themeInd');
42 | setCurrent(storedThemeInd || 0);
43 | }
44 | }, [themeInd]);
45 |
46 | const sbTheme = useTheme();
47 | const isSbDark = sbTheme.base !== 'light';
48 |
49 | return isFirstDataReceived && themeInd !== null ? (
50 |
51 |
56 |
66 |
73 |
74 | ) : (
75 | Waiting for data
76 | );
77 | };
78 |
79 | register(
80 | {
81 | themeInfoList: getThemeInfoList,
82 | theme: getTheme,
83 | themeInfo: getThemeInfo,
84 | themeInd: getCurrentInd,
85 | selectedValue: getSelectedValue,
86 | selectedWord: getSelectedWord,
87 | snippet: getSnippet,
88 | colorSnippet: getColorSnippet,
89 | },
90 | ({ global }) => ({
91 | setCurrent: global(actions.setCurrent),
92 | selectValue: global(actions.selectValue),
93 | selectWord: global(actions.selectWord),
94 | changeSelectedColor: global(actions.changeSelectedColor),
95 | updateTheme: global(actions.changeTheme),
96 | }),
97 | )(AddonThemingPanel);
98 |
--------------------------------------------------------------------------------
/src/preset/index.js:
--------------------------------------------------------------------------------
1 | export function config(entry = []) {
2 | // return [...entry, require.resolve("./preview")]
3 | return [...entry];
4 | }
5 |
6 | export function managerEntries(entry = []) {
7 | return [...entry, require.resolve('../manager/register')];
8 | }
9 |
--------------------------------------------------------------------------------
/src/preset/preview.js:
--------------------------------------------------------------------------------
1 | import { withThemes } from '../preview';
2 |
3 | export const decorators = [withThemes];
4 |
--------------------------------------------------------------------------------
/src/preview/index.d.ts:
--------------------------------------------------------------------------------
1 | import { StoryFn } from '@storybook/addon-devkit';
2 | import React from 'react';
3 |
4 | type ThemeProviderProps = {
5 | theme: T;
6 | children: React.ReactNode;
7 | } & P;
8 |
9 | type Opts = {
10 | providerFn: React.FC>;
11 | onThemeSwitch?: (context: any) => {};
12 | };
13 |
14 | export function withThemes(
15 | ThemeProvider: React.ComponentType>,
16 | themesList: {}[],
17 | opts: Opts,
18 | ): ReturnType;
19 |
20 | export const toThemes: <
21 | T extends import('@storybook/addon-devkit').AddonParameters
22 | >(
23 | _: T,
24 | ) => { [key: symbol]: T };
25 |
26 | export function useThemes(
27 | ThemeProvider: React.ComponentType>,
28 | themesList: {}[],
29 | opts: Opts,
30 | ): typeof toThemes;
31 |
32 |
--------------------------------------------------------------------------------
/src/preview/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { addDecorator } from '@storybook/react';
3 | import { createDecorator, setParameters } from '@storybook/addon-devkit';
4 | import '../config';
5 | import { handleOnSwitch, onThemeSwitchDefault } from './onThemeSwitch';
6 | import { defaultColorSnippet, defaultSnippet } from '../utils/default';
7 |
8 | const DecoratorUI = ThemeProvider => ({
9 | getStory,
10 | theme,
11 | themeInd,
12 | onThemeSwitch,
13 | }) => {
14 | React.useEffect(() => {
15 | handleOnSwitch({ theme, onThemeSwitch });
16 | }, [themeInd]);
17 | return {getStory()} ;
18 | };
19 |
20 | const withData = (ThemeProvider, { providerFn, onThemeSwitch }) => {
21 | let CurrentThemeProvider = ThemeProvider;
22 | if (providerFn) {
23 | CurrentThemeProvider = ({ theme, children }) => (
24 | <>{providerFn({ theme, children })}>
25 | );
26 | }
27 | return createDecorator({
28 | theme: store => store.themesList[store.currentTheme || 0],
29 | themeInd: store => store.currentTheme,
30 | onThemeSwitch: () => onThemeSwitch,
31 | })(DecoratorUI(CurrentThemeProvider), { isGlobal: true });
32 | };
33 |
34 | export const withThemes = (
35 | ThemeProvider,
36 | themesList,
37 | {
38 | providerFn,
39 | onThemeSwitch = onThemeSwitchDefault,
40 | getCustomFieldSnippet = defaultSnippet,
41 | getCustomValueSnippet = defaultColorSnippet,
42 | } = {},
43 | ) =>
44 | withData(ThemeProvider, { providerFn, onThemeSwitch })({
45 | themesList,
46 | currentTheme: null,
47 | fieldSnippetFn: getCustomFieldSnippet,
48 | colorSnippet: getCustomValueSnippet,
49 | });
50 |
51 | export const toThemes = setParameters();
52 |
53 | export const useThemes = (...args) => {
54 | addDecorator(withThemes(...args));
55 | return toThemes;
56 | };
57 |
--------------------------------------------------------------------------------
/src/preview/onThemeSwitch.js:
--------------------------------------------------------------------------------
1 | const LIGHT_BG = '#fff';
2 | const DARK_BG = '#333';
3 |
4 | const TAG_SELECTOR = 'addon-backgrounds-color';
5 |
6 | const createStyle = color => `
7 | .sb-show-main {
8 | background: ${color} !important;
9 | transition: background-color 0.3s;
10 | }
11 | `;
12 |
13 | export const addBackgroundStyle = color => {
14 | if (!color) {
15 | return;
16 | }
17 | const css = createStyle(color);
18 | const existingStyle = document.getElementById(TAG_SELECTOR);
19 | if (existingStyle) {
20 | if (existingStyle.innerHTML !== css) {
21 | existingStyle.innerHTML = css;
22 | }
23 | } else {
24 | const style = document.createElement('style');
25 | style.setAttribute('id', TAG_SELECTOR);
26 | style.innerHTML = css;
27 |
28 | document.head.appendChild(style);
29 | }
30 | };
31 |
32 | export const handleOnSwitch = ({ theme, onThemeSwitch }) => {
33 | const result = onThemeSwitch({ theme });
34 | const color = result.parameters?.backgrounds?.default;
35 | addBackgroundStyle(color);
36 | };
37 |
38 | export const onThemeSwitchDefault = context => {
39 | const { theme } = context;
40 | const background = /dark/i.test(theme.name) ? DARK_BG : LIGHT_BG;
41 | const parameters = {
42 | backgrounds: {
43 | default: background,
44 | },
45 | };
46 | return {
47 | parameters,
48 | };
49 | };
50 |
--------------------------------------------------------------------------------
/src/register.js:
--------------------------------------------------------------------------------
1 | import './manager/register';
2 |
--------------------------------------------------------------------------------
/src/selectors.js:
--------------------------------------------------------------------------------
1 | import { themeName } from '@react-theming/theme-name';
2 |
3 | export const createSelector = (...args) => {
4 | const resultFn = args.pop();
5 | return store => {
6 | const selected = args.map(selector => selector(store));
7 | return resultFn(...selected, store);
8 | };
9 | };
10 |
11 | export const getCurrentInd = store => store.currentTheme;
12 | export const getThemesList = store => store.themesList;
13 | export const getSnippet = store => store.fieldSnippetFn;
14 | export const getColorSnippet = store => store.colorSnippet;
15 |
16 | export const getTheme = createSelector(
17 | getCurrentInd,
18 | getThemesList,
19 | (ind, themes) => (themes ? themes[ind] : undefined),
20 | );
21 |
22 | export const getThemeInfoList = createSelector(getThemesList, (list = []) =>
23 | list.map((theme, ind) => ({
24 | name: themeName(theme, ind),
25 | theme,
26 | })),
27 | );
28 |
29 | export const getThemeInfo = createSelector(
30 | getCurrentInd,
31 | getThemeInfoList,
32 | (ind, themesInfo) => (themesInfo ? themesInfo[ind] : undefined),
33 | );
34 |
35 | export const getSelectedValue = createSelector(getTheme, (theme, store) => {
36 | const { selectedValue } = store;
37 | if (!selectedValue) return undefined;
38 | const { name, namespace, type } = selectedValue;
39 | const nestedObj = namespace.reduce((subObj, subKey) => subObj[subKey], theme);
40 | const value = nestedObj[name];
41 | return { name, namespace, value, type };
42 | });
43 |
44 | export const getSelectedWord = createSelector(store => {
45 | const { selectedWord } = store;
46 | return selectedWord;
47 | });
48 |
--------------------------------------------------------------------------------
/src/utils/clipboard.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | export const copyToClipboard = str => () => {
3 | const el = window.document.createElement('textarea');
4 | el.value = str;
5 | window.document.body.appendChild(el);
6 | el.select();
7 | window.document.execCommand('copy');
8 | window.document.body.removeChild(el);
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/colors.js:
--------------------------------------------------------------------------------
1 | import colorString from 'color-string';
2 |
3 | export const isColor = str => colorString.get(str);
4 |
5 | export const processWord = str => {
6 | if (!str) {
7 | return null;
8 | }
9 | const value = str.replace(/^['|"|`|(]|['|"|`|)|:]*$/g, '');
10 | if (!value) {
11 | return null;
12 | }
13 | return {
14 | name: '/* yaml */',
15 | type: isColor(value) ? 'color' : '',
16 | value,
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/src/utils/default.js:
--------------------------------------------------------------------------------
1 | export const defaultSnippet = selectedValue => {
2 | const { namespace, name } = selectedValue;
3 | const path = namespace.join('.');
4 | const fullPath = `${path}.${name}`;
5 | const themeProp = `\${({ theme }) => theme.${fullPath}}`;
6 | return themeProp;
7 | };
8 |
9 | export const defaultColorSnippet = selectedValue => selectedValue?.value || '';
10 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export * from './clipboard';
2 | export * from './default';
3 | export * from './colors';
4 |
--------------------------------------------------------------------------------