├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
├── ISSUE_TEMPLATE
│ ├── ---bug-report.md
│ └── ---feature-request.md
└── workflows
│ └── build.yml
├── .gitignore
├── .storybook
├── main.js
└── preview.js
├── @types
└── index.d.ts
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── config.js
├── package-lock.json
├── package.json
├── setupTests.ts
├── src
├── Alert.tsx
├── Avatar.tsx
├── Backdrop.tsx
├── Badge.tsx
├── Button.tsx
├── Card.tsx
├── CardBody.tsx
├── Dropdown.tsx
├── DropdownItem.tsx
├── HelperText.tsx
├── Input.tsx
├── Label.tsx
├── Modal.tsx
├── ModalBody.tsx
├── ModalFooter.tsx
├── ModalHeader.tsx
├── Pagination.tsx
├── Select.tsx
├── Table.tsx
├── TableBody.tsx
├── TableCell.tsx
├── TableContainer.tsx
├── TableFooter.tsx
├── TableHeader.tsx
├── TableRow.tsx
├── Textarea.tsx
├── Transition.tsx
├── Windmill.tsx
├── __tests__
│ ├── Alert.test.tsx
│ ├── Avatar.test.tsx
│ ├── Backdrop.test.tsx
│ ├── Badge.test.tsx
│ ├── Button.test.tsx
│ ├── Card.test.tsx
│ ├── CardBody.test.tsx
│ ├── Dropdown.test.tsx
│ ├── DropdownItem.test.tsx
│ ├── HelperText.test.tsx
│ ├── Input.test.tsx
│ ├── Label.test.tsx
│ ├── Modal.test.tsx
│ ├── ModalBody.test.tsx
│ ├── ModalFooter.test.tsx
│ ├── ModalHeader.test.tsx
│ ├── Pagination.test.tsx
│ ├── Select.test.tsx
│ ├── Table.test.tsx
│ ├── TableBody.test.tsx
│ ├── TableCell.test.tsx
│ ├── TableContainer.test.tsx
│ ├── TableFooter.test.tsx
│ ├── TableHeader.test.tsx
│ ├── TableRow.test.tsx
│ ├── Textarea.test.tsx
│ ├── Transition.test.tsx
│ ├── Windmill.test.tsx
│ ├── context
│ │ └── ThemeContext.test.tsx
│ └── utils
│ │ └── heart.svg
├── context
│ └── ThemeContext.tsx
├── index.ts
├── stories
│ ├── Alert.stories.tsx
│ ├── Avatar.stories.tsx
│ ├── Backdrop.stories.tsx
│ ├── Badge.stories.tsx
│ ├── Button.stories.tsx
│ ├── Card.stories.tsx
│ ├── Dropdown.stories.tsx
│ ├── HelperText.stories.tsx
│ ├── Input.stories.tsx
│ ├── Label.stories.tsx
│ ├── Modal.stories.tsx
│ ├── Pagination.stories.tsx
│ ├── Select.stories.tsx
│ ├── Table.stories.tsx
│ ├── Textarea.stories.tsx
│ └── static
│ │ ├── avatar-1.jpg
│ │ └── heart.svg
├── themes
│ └── default.ts
└── utils
│ ├── mergeDeep.ts
│ ├── useDarkMode.ts
│ └── warning.ts
├── style
├── tailwind.config.js
└── tailwind.css
├── tsconfig.json
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | dev
4 | coverage
5 | lib
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "jest": true
5 | },
6 | "settings": {
7 | "react": {
8 | "version": "detect"
9 | }
10 | },
11 | "parserOptions": {
12 | "ecmaVersion": 2018,
13 | "sourceType": "module"
14 | },
15 | "extends": ["standard", "plugin:react/recommended", "prettier"],
16 | "plugins": ["prettier"],
17 | "rules": {
18 | "no-unused-vars": [
19 | 2,
20 | {
21 | "args": "all",
22 | "argsIgnorePattern": "^_"
23 | }
24 | ],
25 | "no-warning-comments": 0,
26 | "prettier/prettier": [
27 | "error",
28 | {
29 | "semi": false,
30 | "singleQuote": true,
31 | "printWidth": 100,
32 | "tabWidth": 2,
33 | "useTabs": false,
34 | "trailingComma": "es5",
35 | "bracketSpacing": true,
36 | "parser": "flow",
37 | "endOfLine": "auto"
38 | }
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug report"
3 | about: 'Bugs, missing documentation, or unexpected behavior '
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 |
19 |
20 | - `windmill-react-ui` version:
21 |
22 | ### Relevant code or config:
23 |
24 | ```html
25 |
26 | ```
27 |
28 |
32 |
33 | ### What you did:
34 |
35 |
36 |
37 | ### What happened:
38 |
39 |
40 |
41 | ### Reproduction:
42 |
43 |
49 |
50 | ### Problem description:
51 |
52 |
53 |
54 | ### Suggested solution:
55 |
56 |
60 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4A1 Feature request"
3 | about: 'I have a suggestion (and might want to implement myself)! '
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Build
5 |
6 | on:
7 | push:
8 | branches: [master]
9 | pull_request:
10 | branches: [master]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [10.x, 12.x, 14.x]
19 |
20 | steps:
21 | - uses: actions/checkout@v1
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v1
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - run: npm ci
27 | - run: npm run lint
28 | - run: npm run test:coverage
29 | - run: npm run build
30 | - name: Upload coverage to Codecov
31 | uses: codecov/codecov-action@v1
32 | with:
33 | file: ./coverage/coverage-final.json
34 | flags: unittest
35 | name: codecov-1
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | dist
4 | dev
5 | style/output.css
6 | lib
7 | .env
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpackFinal: (config) => {
3 | config.resolve.extensions.push('.svg')
4 |
5 | config.module.rules = config.module.rules.map((data) => {
6 | if (/svg\|/.test(String(data.test)))
7 | data.test = /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/
8 |
9 | return data
10 | })
11 |
12 | config.module.rules.push({
13 | test: /\.svg$/,
14 | use: [
15 | { loader: require.resolve('babel-loader') },
16 | { loader: require.resolve('@svgr/webpack') },
17 | ],
18 | })
19 |
20 | return config
21 | },
22 | stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
23 | addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
24 | }
25 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | import '../style/output.css'
2 |
3 | export const parameters = {
4 | actions: { argTypesRegex: '^on[A-Z].*' },
5 | }
6 |
--------------------------------------------------------------------------------
/@types/index.d.ts:
--------------------------------------------------------------------------------
1 | // used in ThemeContext
2 | type Mode = string | null
3 |
4 | declare module '*.svg' {
5 | const content: any
6 | export default content
7 | }
8 |
9 | declare module '*.jpg' {
10 | const content: any
11 | export default content
12 | }
13 |
14 | declare type ListenerMap = {
15 | [k: string]: any
16 | }
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [0.6.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.6.0-beta.0...0.6.0) (2021-05-17)
2 |
3 | This is the last version before 1.0. It already introduces some breaking changes from 0.5.x, but if you just want some base components on top of Tailwind 2.0, this is what you're looking for.
4 |
5 | If you need pre Tailwind v2 support, go for 0.5.1.
6 |
7 | ### BREAKING CHANGES
8 |
9 | - Upgrade Tailwind CSS to v2
10 | - Remove `tailwindcss-multi-theme` plugin
11 | - This would only be a problem for you if you're using your own solution for theme handling (not using `usePreferences` or `dark`), as we move from `theme-dark` and `theme-light` to Tailwind's own solution: `darkMode: 'class',`, which just adds `dark` to `body` as a class.
12 | - Upgrade forms plugin
13 |
14 | # [0.6.0-beta.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.5.1...0.6.0-beta.0) (2021-05-15)
15 |
16 | ### Bug Fixes
17 |
18 | - **dark theme:** add dark variants to tailwind ([6e0bcd3](https://github.com/estevanmaito/windmill-react-ui/commit/6e0bcd32688924584f0a3edcab6bb7b908c3dda4))
19 | - **dark theme:** fix focus ring for dark theme ([8095735](https://github.com/estevanmaito/windmill-react-ui/commit/8095735ab40b42b79ac0e8e5cbda695720dbdf72))
20 | - **dark theme:** use tailwind default `dark` class to apply theme ([4c42027](https://github.com/estevanmaito/windmill-react-ui/commit/4c420279078c7a75589fed5df3a458dbffbabd03))
21 | - **package.json:** add tailwind build before run storybook ([#37](https://github.com/estevanmaito/windmill-react-ui/issues/37)) ([6536826](https://github.com/estevanmaito/windmill-react-ui/commit/653682637554f175c7d9e0e8f7f81c3f854f2d0f))
22 |
23 | ### Features
24 |
25 | - **dependencies:** upgrade dependencie ([0305963](https://github.com/estevanmaito/windmill-react-ui/commit/0305963609a958561223bb352ffbae856bda571a))
26 |
27 | ### BREAKING CHANGES
28 |
29 | - **dependencies:** upgrade Tailwind to v2; remove multi-theme plugin; upgrade forms plugin;
30 |
31 | ## [0.5.1](https://github.com/estevanmaito/windmill-react-ui/compare/0.5.0...0.5.1) (2021-02-06)
32 |
33 | - **forms**: fix prop extension to allow usage of element attributes eg. name, on `input`, `select` and `textarea`.
34 |
35 | # [0.5.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.4.1...0.5.0) (2020-12-02)
36 |
37 | ### Bug Fixes
38 |
39 | - **components:** add documentation for every property, for every component ([adb7bb8](https://github.com/estevanmaito/windmill-react-ui/commit/adb7bb8c33ed350c8cb6cc4bb9abfa35da631a9f))
40 | - **events:** update event listeners to capture events ([997f8f8](https://github.com/estevanmaito/windmill-react-ui/commit/997f8f8e80d370a96e91567d2f9921767530c8a2)), closes [#20](https://github.com/estevanmaito/windmill-react-ui/issues/20)
41 |
42 | ## [0.4.1](https://github.com/estevanmaito/windmill-react-ui/compare/0.4.0...0.4.1) (2020-11-08)
43 |
44 | ### Bug Fixes
45 |
46 | - **alert:** actually export the Alert ([ccade2c](https://github.com/estevanmaito/windmill-react-ui/commit/ccade2c73fadf6ad620e249a7850b21725aa320c))
47 | - **pagination:** fix pagination update when totalResults or resultsPerPage change ([fbedd18](https://github.com/estevanmaito/windmill-react-ui/commit/fbedd18572d64cbbc5d292d7cb8ffc0636e9ef29))
48 |
49 | # [0.4.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.3.1...0.4.0) (2020-11-08)
50 |
51 | ### Features
52 |
53 | - **alert:** add alert component ([b0855c7](https://github.com/estevanmaito/windmill-react-ui/commit/b0855c7152f71618c548876a64ccc5573e65c509))
54 | - **TypeScript rewrite**
55 |
56 | # [0.4.0-beta.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.3.1...0.4.0-beta.0) (2020-10-26)
57 |
58 | ### Bug Fixes
59 |
60 | - **button:** fix gradient style applied to anchor buttons due to type ([131bd69](https://github.com/estevanmaito/windmill-react-ui/commit/131bd69580622a98e4fddabe2330db812648c1a1)), closes [#7](https://github.com/estevanmaito/windmill-react-ui/issues/7)
61 | - **component:** add event handlers to button props ([90a3d69](https://github.com/estevanmaito/windmill-react-ui/commit/90a3d694dd82e99b8169b17c4f080c8fa994b65a))
62 | - **packages:** update dependencies ([330a535](https://github.com/estevanmaito/windmill-react-ui/commit/330a53547028ee42da16cbb4d02157268df6e6a2))
63 |
64 | ## [0.3.2](https://github.com/estevanmaito/windmill-react-ui/compare/0.3.1...0.3.2) (2020-08-08)
65 |
66 | ### Bug Fixes
67 |
68 | - **button:** fix gradient style applied to anchor buttons due to type being incorrectly inherited from button defaults ([131bd69](https://github.com/estevanmaito/windmill-react-ui/commit/131bd69580622a98e4fddabe2330db812648c1a1)), closes [#7](https://github.com/estevanmaito/windmill-react-ui/issues/7)
69 |
70 | ## [0.3.1](https://github.com/estevanmaito/windmill-react-ui/compare/0.3.0...0.3.1) (2020-07-21)
71 |
72 | ### Bug Fixes
73 |
74 | - **config:** fix purge content and add lib to npm published files ([abf8c24](https://github.com/estevanmaito/windmill-react-ui/commit/abf8c2425e13001700791fe0ed5cbee097dd2267))
75 |
76 | # [0.3.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.2.0...0.3.0) (2020-07-21)
77 |
78 | ### Build System
79 |
80 | - **scope:** use `@windmill` as scope ([e6862da](https://github.com/estevanmaito/windmill-react-ui/commit/e6862dab1744bc94b3a94ce8774bd93a010862af))
81 |
82 | ### BREAKING CHANGES
83 |
84 | - **scope:** Use `@windmill/react-ui` to install the package as `windmill-react-ui` is deprecated.
85 |
86 | # [0.2.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.2...0.2.0) (2020-07-20)
87 |
88 | ### Bug Fixes
89 |
90 | - **form:** fix 1px off input and select ([4a3bc13](https://github.com/estevanmaito/windmill-react-ui/commit/4a3bc133f7b81744f0869b65deb136d2ed5516d1))
91 |
92 | ### BREAKING CHANGES
93 |
94 | - **form:** Regular Inputs and Selects had a height of 39px, which looked terrible when used
95 | side-by-side with a regular button (38px height). Reducing the `line-height` to `leading-5` solved
96 | it, and now regular inputs and selects have a 38px height.
97 |
98 | ## [0.1.2](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.2-beta.0...0.1.2) (2020-07-19)
99 |
100 | ### Bug Fixes
101 |
102 | - **avatar:** add forwarRef to avatar ([3e3c8a6](https://github.com/estevanmaito/windmill-react-ui/commit/3e3c8a615d76ba52b3a2739caf2ca6cee7e2908c))
103 | - **backdrop:** add forwardRef to backdrop ([6657d52](https://github.com/estevanmaito/windmill-react-ui/commit/6657d52a03e84b8805d8ea7ea71af719694a7d45))
104 | - **components:** add forwardRef to all components that were missing ([fb240a2](https://github.com/estevanmaito/windmill-react-ui/commit/fb240a24da991b2a486765850e839cca5d7bbf39))
105 | - **variants:** add default variants ([217a1d8](https://github.com/estevanmaito/windmill-react-ui/commit/217a1d8922ab18a7fe0e010b2e2cc34a4c27f460))
106 |
107 | ### BREAKING CHANGES
108 |
109 | - **components:** All components now support ref forwarding.
110 |
111 | ## [0.1.2-beta.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.1...0.1.2-beta.0) (2020-07-17)
112 |
113 | ### Features
114 |
115 | - **config:** add a global config wrapper ([6a6de16](https://github.com/estevanmaito/windmill-react-ui/commit/6a6de16190479fabc2613077f1844c97716190ad))
116 |
117 | ### BREAKING CHANGES
118 |
119 | - **config:** Instead of adding Windmill files to `purge` or to `plugins`, now you only need to
120 | encapsulate your Tailwind config using this wrapper. It will automatically add what Windmill needs
121 | to work properly and you can use your `tailwind.config.js` file as before, as it will be merged.
122 |
123 | ## [0.1.1](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0...0.1.1) (2020-07-14)
124 |
125 | ### Bug Fixes
126 |
127 | - **defaulttheme:** export an object instead of string ([9a720e5](https://github.com/estevanmaito/windmill-react-ui/commit/9a720e5db588653977107684dfa4ec4bd04fa930))
128 |
129 | # [0.1.0](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.14...0.1.0) (2020-07-13)
130 |
131 | ### Bug Fixes
132 |
133 | - **backdrop:** use z index 40 ([48c4348](https://github.com/estevanmaito/windmill-react-ui/commit/48c4348e6503296ddddabe5a44211f9302d88826))
134 | - **card:** hide card overflow ([7cfa036](https://github.com/estevanmaito/windmill-react-ui/commit/7cfa036f453e33d82b1f8d554278fe7f5b754b83))
135 | - **dropdown:** fix dropdown event handlers dependencies ([8490872](https://github.com/estevanmaito/windmill-react-ui/commit/84908725fbfe47f465b7ab2458ff6f535efb060e))
136 | - **pagination:** add base text styles to pagination ([9258df1](https://github.com/estevanmaito/windmill-react-ui/commit/9258df1f630eecd811220cd4807172fcfd4552b1))
137 |
138 | ### Features
139 |
140 | - **defaulttheme:** add defaultTheme export for purge ([22ac723](https://github.com/estevanmaito/windmill-react-ui/commit/22ac72365c6a565802aab592f5aac1f97c764702))
141 | - **dropdown:** add align prop to dropdown ([7297426](https://github.com/estevanmaito/windmill-react-ui/commit/7297426205f85eaeaf1644085d5a8e0b4562a618))
142 | - **windmill:** add usePreferences to windmill ([a619a28](https://github.com/estevanmaito/windmill-react-ui/commit/a619a283afb8829fd6659c788261fbca8197b37f))
143 |
144 | ### BREAKING CHANGES
145 |
146 | - **pagination:** Pagination now has its own text colors, instead of depending on parent styles or
147 | own classes. It makes it useful outside elements like TableFooter, which would add text styles
148 | before. The breaking change is that it doesn't inherit TableFooter (or any parent) colors anymore.
149 | - **windmill:** Now you need to use the prop `usePreferences` in the root Windmill component if you
150 | want access to theme utilities like the current theme or theme toggler, user preferences and user
151 | theme storage. These features were enabled by default before. You get the old behavior just adding
152 | `usePreferences` to the root Windmill component.
153 | - **dropdown:** Dropdown now accepts an align prop that can be 'right' or 'left'. It now defaults
154 | to 'left' if align is not used, which is the natural align of the DOM, instead of the old right
155 | alignment. Change your dropdowns to align="right" if you were using the old default style.
156 |
157 | # [0.1.0-alpha.14](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.13...0.1.0-alpha.14) (2020-07-10)
158 |
159 | ### Bug Fixes
160 |
161 | - **button:** fix link button active state ([cb0877e](https://github.com/estevanmaito/windmill-react-ui/commit/cb0877e19b2064a1556756da2b3ed6499ff1c8f6))
162 |
163 | ### Features
164 |
165 | - **button:** add icon prop to button ([62453c1](https://github.com/estevanmaito/windmill-react-ui/commit/62453c19b9ba007fe40d634b9c9fa934220ca1bd))
166 | - **dropdown:** add outside click handler ([c316cd4](https://github.com/estevanmaito/windmill-react-ui/commit/c316cd44ceadfc11be021308f977396eb282baba))
167 | - **table:** remove styles from TableRow and apply to TableBody ([f5051c2](https://github.com/estevanmaito/windmill-react-ui/commit/f5051c216a3f776f09df85e397afb661cdaa97f5))
168 |
169 | ### BREAKING CHANGES
170 |
171 | - **dropdown:** You don't need to provide an outside click handler to close the dropdown anymore as
172 | it's now done internally calling the passed onClose prop
173 | - **table:** TableRow could only be used inside TableBody because it would apply styles that
174 | would overwirte TableHead, like text colors. Now these styles live only inside TableBody, leaving
175 | TableRow without styles, even though it's entry is still in defaultTheme for further theme
176 | customization.
177 |
178 | # [0.1.0-alpha.13](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.12...0.1.0-alpha.13) (2020-07-09)
179 |
180 | ### Bug Fixes
181 |
182 | - **form:** fix dark validation styles for input and textarea ([39af5d1](https://github.com/estevanmaito/windmill-react-ui/commit/39af5d1a9fc17c68e730f4ace177a9fcd62fa292))
183 |
184 | ### Features
185 |
186 | - **select:** add disabled attribute ([d429e7a](https://github.com/estevanmaito/windmill-react-ui/commit/d429e7aa6ccbf0476509fe65ed3fb1f24024a613))
187 | - **select:** add validation styles ([71d4dfe](https://github.com/estevanmaito/windmill-react-ui/commit/71d4dfe4f4c453ecb0be53ce5331f3183055c6e0))
188 | - **textarea:** add validation and disabled styles ([6108c06](https://github.com/estevanmaito/windmill-react-ui/commit/6108c06584f4f3a91af587aee4963b3e50021abd))
189 |
190 | # [0.1.0-alpha.12](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.11...0.1.0-alpha.12) (2020-07-08)
191 |
192 | ### Bug Fixes
193 |
194 | - **modal:** do not render modal if it is not on client ([2cbe55a](https://github.com/estevanmaito/windmill-react-ui/commit/2cbe55a61862456c650d1c2bc4f30f8874dabd67))
195 | - **plugin:** use opacity from theme for backgroundOpacity ([1c2931f](https://github.com/estevanmaito/windmill-react-ui/commit/1c2931f018f8f3af4ab0f84220aebfbc70af93bb))
196 |
197 | # [0.1.0-alpha.11](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.10...0.1.0-alpha.11) (2020-07-08)
198 |
199 | ### Bug Fixes
200 |
201 | - **badge:** add flex styles to badge ([bf7c412](https://github.com/estevanmaito/windmill-react-ui/commit/bf7c412c0baf28079f8faeb143251f3efc0f904e))
202 | - **plugin:** fix backgroundOpacity variant overwrite ([d843446](https://github.com/estevanmaito/windmill-react-ui/commit/d84344612bf7f3dec6ed3dbcb6feeb789d787769))
203 |
204 | ### BREAKING CHANGES
205 |
206 | - **badge:** it now behaves as inline-flex instead of default inline span. py-1 was also removed
207 | in favor of leading increase, so it better aligns in the center of the pill.
208 |
209 | # [0.1.0-alpha.10](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.9...0.1.0-alpha.10) (2020-07-06)
210 |
211 | ### Bug Fixes
212 |
213 | - **plugin:** fix call stack on backgroundOpacity ([a9b9038](https://github.com/estevanmaito/windmill-react-ui/commit/a9b90380c8d349417f3c190abd00f2b36985c06c))
214 |
215 | # [0.1.0-alpha.9](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.8...0.1.0-alpha.9) (2020-07-06)
216 |
217 | ### Bug Fixes
218 |
219 | - **avatar:** add inline-block to avatar ([c1d4dc1](https://github.com/estevanmaito/windmill-react-ui/commit/c1d4dc115e6e9aa2a74c376f769eb50e284da524))
220 | - **backdrop:** increase backdrop z-index ([951c489](https://github.com/estevanmaito/windmill-react-ui/commit/951c48946d663d1b9071b3d2073bcfcb8a06882a))
221 | - **button:** fix link style for dark buttons ([1002423](https://github.com/estevanmaito/windmill-react-ui/commit/1002423618d29ef8cab320b629c762f3d2b7d408)), closes [#4](https://github.com/estevanmaito/windmill-react-ui/issues/4)
222 | - **dark mode:** fix window not being available on SSR ([af2d464](https://github.com/estevanmaito/windmill-react-ui/commit/af2d46409ed719a7103f50d8915440082fd02787))
223 |
224 | ### Code Refactoring
225 |
226 | - **avatar:** use src instead of img ([e988424](https://github.com/estevanmaito/windmill-react-ui/commit/e98842497c588b5bd33b4feedd5ad6beb8d9f771))
227 |
228 | ### BREAKING CHANGES
229 |
230 | - **avatar:** Avatar now uses src instead of img to pass the image src
231 | - **avatar:** add inline-block
232 | - **button:** different colors for dark mode
233 | - **backdrop:** z-index increased from 10 to 50
234 |
235 | # [0.1.0-alpha.8](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.7...0.1.0-alpha.8) (2020-06-30)
236 |
237 | ### Bug Fixes
238 |
239 | - **defaulttheme:** export defaultTheme as Common JS and remove deprecated theme toggler ([d60e5c2](https://github.com/estevanmaito/windmill-react-ui/commit/d60e5c24d75206e753770630dce074cf028146dc))
240 | - **table pagination:** remove layout styles from table footer that were interfering with pagination ([f9e7b9d](https://github.com/estevanmaito/windmill-react-ui/commit/f9e7b9d3b53d415cc0c02f9376856f4c6564716a))
241 |
242 | # [0.1.0-alpha.7](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.6...0.1.0-alpha.7) (2020-06-30)
243 |
244 | ### Bug Fixes
245 |
246 | - **windmill:** improve theme handling when user have stored theme ([d71b6df](https://github.com/estevanmaito/windmill-react-ui/commit/d71b6dfcebb2e78d2c72d1956d8d2d0e0bd3786c))
247 |
248 | # [0.1.0-alpha.6](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.5...0.1.0-alpha.6) (2020-06-29)
249 |
250 | ### Features
251 |
252 | - **windmill:** add theme storage and user system preferences getter as default ([04c06ef](https://github.com/estevanmaito/windmill-react-ui/commit/04c06ef1888a5675a5f05b784996b6ea9e8c4133))
253 |
254 | # [0.1.0-alpha.5](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.4...0.1.0-alpha.5) (2020-06-29)
255 |
256 | ### Bug Fixes
257 |
258 | - **backdrop:** reduce backdrop z-index ([732c546](https://github.com/estevanmaito/windmill-react-ui/commit/732c5462fb1b2a8ae890a0d62347c033ae0b39d0))
259 |
260 | ### Features
261 |
262 | - **windmill:** expose current selected theme and a theme toggler ([73505bf](https://github.com/estevanmaito/windmill-react-ui/commit/73505bf8c9f8c55e68c2b4a7300bae91e4f3217c))
263 |
264 | # [0.1.0-alpha.4](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.3...0.1.0-alpha.4) (2020-06-28)
265 |
266 | ### Bug Fixes
267 |
268 | - **dropdown:** export dropdown item ([c177442](https://github.com/estevanmaito/windmill-react-ui/commit/c177442666aa4cb94b4b57c1d668691ed0416c5a))
269 |
270 | # [0.1.0-alpha.3](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.2...0.1.0-alpha.3) (2020-06-27)
271 |
272 | ### Bug Fixes
273 |
274 | - **library:** export TableFooter ([04ac997](https://github.com/estevanmaito/windmill-react-ui/commit/04ac9971c083dcd20ac2c9e0922b5d61878abc87))
275 |
276 | # [0.1.0-alpha.2](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.1...0.1.0-alpha.2) (2020-06-27)
277 |
278 | ### Bug Fixes
279 |
280 | - **webpack:** fix externals namingfix Module not found: Can't resolve 'reactDOM' ([380843b](https://github.com/estevanmaito/windmill-react-ui/commit/380843b5017a5eeeaf2a51e2b0ba620b2101a04b))
281 |
282 | # [0.1.0-alpha.1](https://github.com/estevanmaito/windmill-react-ui/compare/0.1.0-alpha.1...0.1.0-alpha.2) (2020-06-27)
283 |
284 | ### Bug Fixes
285 |
286 | - **proptypes:** fix children proptypes ([43cdf46](https://github.com/estevanmaito/windmill-react-ui/commit/43cdf467c732d3d016fb19823558f97add801b2b))
287 | - **windmill:** export windmill ([9983010](https://github.com/estevanmaito/windmill-react-ui/commit/99830107e66706f583f8552ef1d63a8b09afd337))
288 | - **windmill:** fix dark theme prop ([7155eae](https://github.com/estevanmaito/windmill-react-ui/commit/7155eaea80411e2ad5fb04bd9e35c41c3a8b1cc8))
289 |
290 | ### Features
291 |
292 | - **avatar:** add avatar ([ea7827b](https://github.com/estevanmaito/windmill-react-ui/commit/ea7827b41ec89466f31429f0759b93eb5935c09a))
293 | - **backdrop:** add backdrop ([b367072](https://github.com/estevanmaito/windmill-react-ui/commit/b36707228f918e303e2457fcbc561302e2ec468f))
294 | - **badge:** add badge ([7638177](https://github.com/estevanmaito/windmill-react-ui/commit/76381770e2db0d82dfa27fe657847baacc5a4c91))
295 | - **card:** add card and cardbody ([3c64e85](https://github.com/estevanmaito/windmill-react-ui/commit/3c64e852be893871b346e9d2e2eb9797b0b90b87))
296 | - **dropdown:** add dropdown ([e4eeba9](https://github.com/estevanmaito/windmill-react-ui/commit/e4eeba9715c2af0aa835097440b68171372b9095))
297 | - **helpertext:** add helpertext ([12539d2](https://github.com/estevanmaito/windmill-react-ui/commit/12539d23809a42d77610f507495543e7518ec1ba))
298 | - **input:** add form inputs ([8b4f69e](https://github.com/estevanmaito/windmill-react-ui/commit/8b4f69e1d26350312b0173551968ef88856fc468))
299 | - **label:** add form label ([a74f8a9](https://github.com/estevanmaito/windmill-react-ui/commit/a74f8a978bd347de270bc8e4862febfaf3550b18))
300 | - **modal:** add all modal components ([fc987d5](https://github.com/estevanmaito/windmill-react-ui/commit/fc987d57a0135631af627d332268f6e8b39ac88e))
301 | - **pagination:** add pagination ([a410ca4](https://github.com/estevanmaito/windmill-react-ui/commit/a410ca4d572d5b2b2ed87f03de52ab7aaee554f0))
302 | - **plugin:** add pluginexport a plugin that packs multi-theme, custom-forms and the custom shadow outline plugin, alongwith custom modifications to width, height and colors. ([15c772a](https://github.com/estevanmaito/windmill-react-ui/commit/15c772a1167dd61e94d185255b2f338b70b43672))
303 | - **select:** add select ([da44e48](https://github.com/estevanmaito/windmill-react-ui/commit/da44e4882b5e90c997e6a25908a2697eac253283))
304 | - **table:** add table ([6705f3d](https://github.com/estevanmaito/windmill-react-ui/commit/6705f3d4d9ab5d93833ead9fc9a7a73cbebd2951))
305 | - **table:** add table footer ([19f59a8](https://github.com/estevanmaito/windmill-react-ui/commit/19f59a878d61d45b5ca6e8cd4ea0dfc0bc7e554d))
306 | - **textarea:** add textarea ([6a7a333](https://github.com/estevanmaito/windmill-react-ui/commit/6a7a333db0c39ea58d7ec16bf0b6b47373afc014))
307 | - **theme:** add default theme ([b99470c](https://github.com/estevanmaito/windmill-react-ui/commit/b99470cc690d2ea084cab2703391988c70e45ad7))
308 | - **theme:** add theme context ([d3f13e7](https://github.com/estevanmaito/windmill-react-ui/commit/d3f13e7cc422f062006e3578030b6305e70f678d))
309 | - **themes:** add dark mode and toggle ([371578e](https://github.com/estevanmaito/windmill-react-ui/commit/371578ee1109a8ca78c0fac6c4b8a509f5abdd2c))
310 | - **themes:** expose themes configs ([f388a54](https://github.com/estevanmaito/windmill-react-ui/commit/f388a542ed2645b5edf8613283ab229d287c7ae3))
311 | - **transition:** add transition ([46a7d6b](https://github.com/estevanmaito/windmill-react-ui/commit/46a7d6bee3a26cfe04dc68f51253f429465eb1f1))
312 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to make participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT CONTACT]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Estevan Maito
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 | # Windmill React UI
2 |
3 | The component library for fast and accessible development of gorgeous interfaces.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Projects using it: [Windmill Dashboard React](https://github.com/estevanmaito/windmill-dashboard-react)
13 |
14 | ## Mission
15 |
16 | Be the most accessible it can be out of the box and the fastest way to production.
17 |
18 | [Go to docs to see complete, live examples](https://windmillui.com/react-ui)
19 |
20 | ## 🚀 Usage
21 |
22 | Install
23 |
24 | ```sh
25 | npm i @windmill/react-ui
26 | ```
27 |
28 | Inside `tailwind.config.js`
29 |
30 | ```js
31 | const windmill = require('@windmill/react-ui/config')
32 | module.exports = windmill({
33 | purge: [],
34 | theme: {
35 | extend: {},
36 | },
37 | variants: {},
38 | plugins: [],
39 | })
40 | ```
41 |
42 | Then place `Windmill` at the root of your project (the order doesn't matter, as long as your application is inside).
43 |
44 | ```js
45 | // index.js
46 | import React from 'react'
47 | import ReactDOM from 'react-dom'
48 | import App from './App'
49 | import { Windmill } from '@windmill/react-ui'
50 |
51 | ReactDOM.render(
52 |
53 |
54 | ,
55 | document.getElementById('root')
56 | )
57 | ```
58 |
59 | Use components inside your project
60 |
61 | ```js
62 | import { Button } from '@windmill/react-ui'
63 |
64 | function App() {
65 | return
66 | }
67 |
68 | export default App
69 | ```
70 |
71 | ## 🔌 Contributing
72 |
73 | - Fork
74 | - Clone
75 | - `npm install`
76 | - `npm run storybook`
77 |
78 | It will start a local server at `localhost:6006` with all components rendered.
79 |
80 | ⚠ Use `npm run cz` instead of `git commit`! It will guide you through some short questions and guarantee that you commit message is standardized.
81 |
82 | Commit will also trigger linting and test coverage.
83 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | const deepMerge = require('deepmerge')
2 | const customFormsPlugin = require('@tailwindcss/forms')
3 |
4 | const colors = {
5 | transparent: 'transparent',
6 | white: '#ffffff',
7 | black: '#000000',
8 | gray: {
9 | '50': '#f9fafb',
10 | '100': '#f4f5f7',
11 | '200': '#e5e7eb',
12 | '300': '#d5d6d7',
13 | '400': '#9e9e9e',
14 | '500': '#707275',
15 | '600': '#4c4f52',
16 | '700': '#24262d',
17 | '800': '#1a1c23',
18 | '900': '#121317',
19 | // default values from Tailwind UI palette
20 | // '300': '#d2d6dc',
21 | // '400': '#9fa6b2',
22 | // '500': '#6b7280',
23 | // '600': '#4b5563',
24 | // '700': '#374151',
25 | // '800': '#252f3f',
26 | // '900': '#161e2e',
27 | },
28 | 'cool-gray': {
29 | '50': '#fbfdfe',
30 | '100': '#f1f5f9',
31 | '200': '#e2e8f0',
32 | '300': '#cfd8e3',
33 | '400': '#97a6ba',
34 | '500': '#64748b',
35 | '600': '#475569',
36 | '700': '#364152',
37 | '800': '#27303f',
38 | '900': '#1a202e',
39 | },
40 | red: {
41 | '50': '#fdf2f2',
42 | '100': '#fde8e8',
43 | '200': '#fbd5d5',
44 | '300': '#f8b4b4',
45 | '400': '#f98080',
46 | '500': '#f05252',
47 | '600': '#e02424',
48 | '700': '#c81e1e',
49 | '800': '#9b1c1c',
50 | '900': '#771d1d',
51 | },
52 | orange: {
53 | '50': '#fff8f1',
54 | '100': '#feecdc',
55 | '200': '#fcd9bd',
56 | '300': '#fdba8c',
57 | '400': '#ff8a4c',
58 | '500': '#ff5a1f',
59 | '600': '#d03801',
60 | '700': '#b43403',
61 | '800': '#8a2c0d',
62 | '900': '#771d1d',
63 | },
64 | yellow: {
65 | '50': '#fdfdea',
66 | '100': '#fdf6b2',
67 | '200': '#fce96a',
68 | '300': '#faca15',
69 | '400': '#e3a008',
70 | '500': '#c27803',
71 | '600': '#9f580a',
72 | '700': '#8e4b10',
73 | '800': '#723b13',
74 | '900': '#633112',
75 | },
76 | green: {
77 | '50': '#f3faf7',
78 | '100': '#def7ec',
79 | '200': '#bcf0da',
80 | '300': '#84e1bc',
81 | '400': '#31c48d',
82 | '500': '#0e9f6e',
83 | '600': '#057a55',
84 | '700': '#046c4e',
85 | '800': '#03543f',
86 | '900': '#014737',
87 | },
88 | teal: {
89 | '50': '#edfafa',
90 | '100': '#d5f5f6',
91 | '200': '#afecef',
92 | '300': '#7edce2',
93 | '400': '#16bdca',
94 | '500': '#0694a2',
95 | '600': '#047481',
96 | '700': '#036672',
97 | '800': '#05505c',
98 | '900': '#014451',
99 | },
100 | blue: {
101 | '50': '#ebf5ff',
102 | '100': '#e1effe',
103 | '200': '#c3ddfd',
104 | '300': '#a4cafe',
105 | '400': '#76a9fa',
106 | '500': '#3f83f8',
107 | '600': '#1c64f2',
108 | '700': '#1a56db',
109 | '800': '#1e429f',
110 | '900': '#233876',
111 | },
112 | indigo: {
113 | '50': '#f0f5ff',
114 | '100': '#e5edff',
115 | '200': '#cddbfe',
116 | '300': '#b4c6fc',
117 | '400': '#8da2fb',
118 | '500': '#6875f5',
119 | '600': '#5850ec',
120 | '700': '#5145cd',
121 | '800': '#42389d',
122 | '900': '#362f78',
123 | },
124 | purple: {
125 | '50': '#f6f5ff',
126 | '100': '#edebfe',
127 | '200': '#dcd7fe',
128 | '300': '#cabffd',
129 | '400': '#ac94fa',
130 | '500': '#9061f9',
131 | '600': '#7e3af2',
132 | '700': '#6c2bd9',
133 | '800': '#5521b5',
134 | '900': '#4a1d96',
135 | },
136 | pink: {
137 | '50': '#fdf2f8',
138 | '100': '#fce8f3',
139 | '200': '#fad1e8',
140 | '300': '#f8b4d9',
141 | '400': '#f17eb8',
142 | '500': '#e74694',
143 | '600': '#d61f69',
144 | '700': '#bf125d',
145 | '800': '#99154b',
146 | '900': '#751a3d',
147 | },
148 | }
149 |
150 | const backgroundOpacity = (theme) => ({
151 | '10': '0.1',
152 | ...theme('opacity'),
153 | })
154 |
155 | const maxHeight = (theme) => ({
156 | '0': '0',
157 | xl: '36rem',
158 | ...theme('spacing'),
159 | })
160 |
161 | const windmillConfig = {
162 | darkMode: 'class',
163 | purge: {
164 | content: [
165 | 'node_modules/@windmill/react-ui/lib/defaultTheme.js',
166 | 'node_modules/@windmill/react-ui/dist/index.js',
167 | ],
168 | },
169 | theme: {
170 | colors,
171 | backgroundOpacity,
172 | maxHeight,
173 | },
174 | variants: {
175 | backgroundOpacity: ['responsive', 'hover', 'focus', 'dark'],
176 | backgroundColor: ['responsive', 'hover', 'focus', 'active', 'odd', 'dark'],
177 | display: ['responsive', 'dark'],
178 | textColor: ['responsive', 'focus', 'focus-within', 'hover', 'active', 'dark'],
179 | placeholderColor: ['responsive', 'focus', 'dark'],
180 | borderColor: ['responsive', 'hover', 'focus', 'dark'],
181 | divideColor: ['responsive', 'dark'],
182 | boxShadow: ['responsive', 'hover', 'focus', 'dark'],
183 | margin: ['responsive', 'last'],
184 | },
185 | plugins: [customFormsPlugin],
186 | }
187 |
188 | function arrayMergeFn(destinationArray, sourceArray) {
189 | return destinationArray.concat(sourceArray).reduce((acc, cur) => {
190 | if (acc.includes(cur)) return acc
191 | return [...acc, cur]
192 | }, [])
193 | }
194 |
195 | /**
196 | * Merge Windmill and Tailwind CSS configurations
197 | * @param {object} tailwindConfig - Tailwind config object
198 | * @return {object} new config object
199 | */
200 | function wrapper(tailwindConfig) {
201 | let purge
202 | if (Array.isArray(tailwindConfig.purge)) {
203 | purge = {
204 | content: tailwindConfig.purge,
205 | }
206 | } else {
207 | purge = tailwindConfig.purge
208 | }
209 | return deepMerge({ ...tailwindConfig, purge }, windmillConfig, { arrayMerge: arrayMergeFn })
210 | }
211 |
212 | module.exports = wrapper
213 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@windmill/react-ui",
3 | "version": "0.6.0",
4 | "description": "React UI component library built with Tailwind CSS",
5 | "main": "dist/index.js",
6 | "files": [
7 | "lib",
8 | "dist",
9 | "config.js",
10 | "README.md"
11 | ],
12 | "scripts": {
13 | "prebuild": "rimraf dist lib && npm run build:lib && npm run build:ts",
14 | "build:ts": "tsc --declaration --declarationMap --emitDeclarationOnly",
15 | "build:tailwind": "tailwindcss build style/tailwind.css -o style/output.css -c style/tailwind.config.js",
16 | "build:lib": "babel src/themes/default.ts --out-file lib/defaultTheme.js",
17 | "build": "webpack",
18 | "predev": "npm run build:tailwind",
19 | "cz": "git-cz",
20 | "test": "jest",
21 | "codecov": "codecov",
22 | "test:watch": "jest --watch",
23 | "test:coverage": "jest --coverage",
24 | "lint": "eslint .",
25 | "lint:fix": "eslint . --fix",
26 | "prerelease": "npm run build",
27 | "release": "release-it",
28 | "prestorybook": "npm run build:tailwind",
29 | "storybook": "start-storybook -p 6006",
30 | "build-storybook": "build-storybook"
31 | },
32 | "peerDependencies": {
33 | "react": ">=16.8.0",
34 | "react-dom": ">=16.8.0",
35 | "tailwindcss": ">=2.0.0"
36 | },
37 | "dependencies": {
38 | "@tailwindcss/forms": "^0.3.2",
39 | "classnames": "2.2.6",
40 | "deepmerge": "4.2.2",
41 | "postcss": "^8.2.15",
42 | "react-focus-lock": "2.4.1",
43 | "react-transition-group": "4.4.1"
44 | },
45 | "devDependencies": {
46 | "@babel/cli": "^7.8.4",
47 | "@babel/core": "^7.9.6",
48 | "@babel/preset-env": "^7.9.6",
49 | "@babel/preset-react": "^7.10.1",
50 | "@babel/preset-typescript": "^7.10.4",
51 | "@release-it/conventional-changelog": "^2.0.0",
52 | "@storybook/addon-actions": "^6.2.9",
53 | "@storybook/addon-essentials": "^6.2.9",
54 | "@storybook/addon-links": "^6.2.9",
55 | "@storybook/react": "^6.2.9",
56 | "@svgr/webpack": "^5.5.0",
57 | "@types/classnames": "^2.2.10",
58 | "@types/enzyme": "^3.10.5",
59 | "@types/jest": "^26.0.13",
60 | "@types/react": "^16.9.49",
61 | "@types/react-dom": "^16.9.8",
62 | "@types/react-transition-group": "4.4.0",
63 | "@wojtekmaj/enzyme-adapter-react-17": "0.3.2",
64 | "babel-jest": "^26.1.0",
65 | "babel-loader": "^8.1.0",
66 | "codecov": "^3.7.1",
67 | "commitizen": "^4.2.4",
68 | "css-loader": "^3.6.0",
69 | "cz-conventional-changelog": "^3.2.0",
70 | "enzyme": "^3.11.0",
71 | "eslint": "^7.0.0",
72 | "eslint-config-prettier": "^6.11.0",
73 | "eslint-config-standard": "^14.1.1",
74 | "eslint-plugin-import": "^2.20.2",
75 | "eslint-plugin-node": "^11.1.0",
76 | "eslint-plugin-prettier": "^3.1.3",
77 | "eslint-plugin-promise": "^4.2.1",
78 | "eslint-plugin-react": "^7.20.0",
79 | "eslint-plugin-standard": "^4.0.1",
80 | "html-webpack-plugin": "^5.3.1",
81 | "husky": "^4.2.5",
82 | "jest": "^26.0.1",
83 | "jest-svg-transformer": "^1.0.0",
84 | "prettier": "^2.0.5",
85 | "react": "^17.0.1",
86 | "react-dom": "^17.0.1",
87 | "react-is": "^17.0.1",
88 | "release-it": "^14.6.1",
89 | "rimraf": "^3.0.2",
90 | "style-loader": "^1.2.1",
91 | "tailwindcss": "^2.0.0",
92 | "ts-jest": "^26.3.0",
93 | "ts-loader": "^8.0.3",
94 | "typescript": "^4.0.2",
95 | "webpack": "^5.1.3",
96 | "webpack-bundle-analyzer": "^3.8.0",
97 | "webpack-cli": "^4.1.0"
98 | },
99 | "repository": {
100 | "type": "git",
101 | "url": "https://github.com/estevanmaito/windmill-react-ui.git"
102 | },
103 | "keywords": [],
104 | "author": "Estevan Maito ",
105 | "license": "MIT",
106 | "bugs": {
107 | "url": "https://github.com/estevanmaito/windmill-react-ui/issues"
108 | },
109 | "homepage": "https://github.com/estevanmaito/windmill-react-ui#readme",
110 | "release-it": {
111 | "github": {
112 | "release": true
113 | },
114 | "plugins": {
115 | "@release-it/conventional-changelog": {
116 | "preset": "angular",
117 | "infile": "CHANGELOG.md"
118 | }
119 | }
120 | },
121 | "publishConfig": {
122 | "access": "public"
123 | },
124 | "babel": {
125 | "presets": [
126 | "@babel/preset-env",
127 | "@babel/preset-react"
128 | ]
129 | },
130 | "prettier": {
131 | "printWidth": 100,
132 | "semi": false,
133 | "singleQuote": true,
134 | "trailingComma": "es5",
135 | "endOfLine": "lf"
136 | },
137 | "jest": {
138 | "preset": "ts-jest",
139 | "testPathIgnorePatterns": [
140 | ".*\\.d\\.ts",
141 | "/node_modules/"
142 | ],
143 | "collectCoverageFrom": [
144 | "src/**/*.{ts,tsx}",
145 | "!src/{Transition,index}.{ts,tsx}",
146 | "!src/{stories,utils}/*"
147 | ],
148 | "setupFilesAfterEnv": [
149 | "./setupTests.ts"
150 | ],
151 | "coverageThreshold": {
152 | "global": {
153 | "branches": 100,
154 | "functions": 100,
155 | "lines": 100,
156 | "statements": 100
157 | }
158 | },
159 | "transform": {
160 | "^.+\\.svg$": "jest-svg-transformer",
161 | "^.+\\.(ts|tsx)$": "ts-jest"
162 | }
163 | },
164 | "config": {
165 | "commitizen": {
166 | "path": "./node_modules/cz-conventional-changelog"
167 | }
168 | },
169 | "husky": {
170 | "hooks": {
171 | "pre-commit": "npm run lint && npm run test:coverage"
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { configure } from 'enzyme'
2 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17'
3 |
4 | configure({ adapter: new Adapter() })
5 |
--------------------------------------------------------------------------------
/src/Alert.tsx:
--------------------------------------------------------------------------------
1 | import React, { SVGAttributes, useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | enum AlertEnum {
6 | success,
7 | danger,
8 | warning,
9 | info,
10 | neutral,
11 | }
12 |
13 | export interface AlertProps extends React.HTMLAttributes {
14 | /**
15 | * The type of the alert
16 | */
17 | type?: keyof typeof AlertEnum
18 | /**
19 | * If defined, shows the close icon that calls this function
20 | */
21 | onClose?: () => void
22 | }
23 |
24 | type IconProps = SVGAttributes
25 |
26 | export const InfoIcon: React.FC = (props) => (
27 |
38 | )
39 |
40 | export const WarningIcon: React.FC = (props) => (
41 |
52 | )
53 |
54 | export const DangerIcon: React.FC = (props) => (
55 |
66 | )
67 |
68 | export const SuccessIcon: React.FC = (props) => (
69 |
80 | )
81 |
82 | export const NeutralIcon: React.FC = (props) => (
83 |
94 | )
95 |
96 | const Alert = React.forwardRef(function Alert(props, ref) {
97 | const { className, children, type = 'neutral', onClose, ...other } = props
98 | const {
99 | theme: { alert },
100 | } = useContext(ThemeContext)
101 |
102 | const baseStyle = alert.base
103 | const withCloseStyle = alert.withClose
104 | const typeStyle = alert[type]
105 | const iconBaseStyle = alert.icon.base
106 | const iconTypeStyle = alert.icon[type]
107 |
108 | let Icon
109 | switch (type) {
110 | case 'success':
111 | Icon = SuccessIcon
112 | break
113 | case 'warning':
114 | Icon = WarningIcon
115 | break
116 | case 'danger':
117 | Icon = DangerIcon
118 | break
119 | case 'info':
120 | Icon = InfoIcon
121 | break
122 | case 'neutral':
123 | Icon = NeutralIcon
124 | break
125 | default:
126 | Icon = NeutralIcon
127 | }
128 |
129 | const cls = classNames(baseStyle, typeStyle, onClose && withCloseStyle, className)
130 |
131 | const iconCls = classNames(iconBaseStyle, iconTypeStyle, 'absolute left-0 top-0 ml-4 mt-4')
132 | const closeCls = classNames(iconBaseStyle, iconTypeStyle)
133 |
134 | return (
135 |
136 | {onClose && (
137 |
152 | )}
153 |
154 | {children}
155 |
156 | )
157 | })
158 |
159 | export default Alert
160 |
--------------------------------------------------------------------------------
/src/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface AvatarProps extends React.HTMLAttributes {
6 | /**
7 | * The size of the avatar
8 | */
9 | size?: 'large' | 'regular' | 'small'
10 | /**
11 | * Alternative text for the avatar image
12 | */
13 | alt?: string
14 | /**
15 | * The source for the avatar image
16 | */
17 | src: string
18 | }
19 |
20 | const Avatar = React.forwardRef(function Avatar(props, ref) {
21 | const { size = 'regular', src, alt, className, ...other } = props
22 | const {
23 | theme: { avatar },
24 | } = useContext(ThemeContext)
25 |
26 | const baseStyle = avatar.base
27 | const sizeStyles = {
28 | large: avatar.size.large,
29 | regular: avatar.size.regular,
30 | small: avatar.size.small,
31 | }
32 |
33 | const cls = classNames(baseStyle, sizeStyles[size], className)
34 |
35 | return (
36 |
37 |

38 |
39 |
40 | )
41 | })
42 |
43 | export default Avatar
44 |
--------------------------------------------------------------------------------
/src/Backdrop.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface BackdropProps extends React.HTMLAttributes {}
6 |
7 | const Backdrop = React.forwardRef(function Backdrop(props, ref) {
8 | const { className, ...other } = props
9 | const {
10 | theme: { backdrop },
11 | } = useContext(ThemeContext)
12 |
13 | const baseStyle = backdrop.base
14 |
15 | const cls = classNames(baseStyle, className)
16 | return
17 | })
18 |
19 | export default Backdrop
20 |
--------------------------------------------------------------------------------
/src/Badge.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface BadgeProps extends React.HTMLAttributes {
6 | /**
7 | * The type of the badge
8 | */
9 | type?: 'success' | 'danger' | 'warning' | 'neutral' | 'primary'
10 | }
11 |
12 | const Badge = React.forwardRef(function Badge(props, ref) {
13 | const { className, children, type = 'primary', ...other } = props
14 |
15 | const {
16 | theme: { badge },
17 | } = useContext(ThemeContext)
18 |
19 | const baseStyle = badge.base
20 | const typeStyle = {
21 | success: badge.success,
22 | danger: badge.danger,
23 | warning: badge.warning,
24 | neutral: badge.neutral,
25 | primary: badge.primary,
26 | }
27 |
28 | const cls = classNames(baseStyle, typeStyle[type], className)
29 |
30 | return (
31 |
32 | {children}
33 |
34 | )
35 | })
36 |
37 | export default Badge
38 |
--------------------------------------------------------------------------------
/src/Button.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames'
2 | import React, { ReactNode, useContext } from 'react'
3 | import { ThemeContext } from './context/ThemeContext'
4 | import warn from './utils/warning'
5 |
6 | type IconType =
7 | | string
8 | | React.FunctionComponent<{ className: string; 'aria-hidden': boolean }>
9 | | React.ComponentClass<{ className: string; 'aria-hidden': boolean }>
10 |
11 | export interface Props {
12 | children?: React.ReactNode
13 | /**
14 | * Defines if the button is disabled
15 | */
16 | disabled?: boolean
17 | /**
18 | * The size of the button
19 | */
20 | size?: 'larger' | 'large' | 'regular' | 'small' | 'pagination'
21 | /**
22 | * Shows only one icon inside the button; defaults to left
23 | */
24 | icon?: IconType
25 | /**
26 | * Shows an icon inside the button, left aligned
27 | */
28 | iconLeft?: IconType
29 | /**
30 | * Shows an icon inside the button, right aligned
31 | */
32 | iconRight?: IconType
33 | /**
34 | * The style of the button
35 | */
36 | layout?: 'outline' | 'link' | 'primary' | '__dropdownItem'
37 | /**
38 | * Shows the button as a block (full width)
39 | */
40 | block?: boolean
41 | }
42 |
43 | export interface ButtonAsButtonProps extends Props, React.ButtonHTMLAttributes {
44 | /**
45 | * The element that should be rendered as a button
46 | */
47 | tag?: 'button'
48 | /**
49 | * The native HTML button type
50 | */
51 | type?: 'button' | 'submit' | 'reset'
52 | }
53 |
54 | export interface ButtonAsAnchorProps extends Props, React.AnchorHTMLAttributes {
55 | tag: 'a'
56 | }
57 |
58 | export interface ButtonAsOtherProps extends Props, React.AnchorHTMLAttributes {
59 | tag: string
60 | }
61 |
62 | export type ButtonProps = ButtonAsButtonProps | ButtonAsAnchorProps | ButtonAsOtherProps
63 |
64 | type Ref = ReactNode | HTMLElement | string
65 |
66 | const Button = React.forwardRef[(function Button(props, ref) {
67 | const {
68 | tag = 'button',
69 | // Fix https://github.com/estevanmaito/windmill-react-ui/issues/7
70 | type = tag === 'button' ? 'button' : undefined,
71 | disabled = false,
72 | size = 'regular',
73 | layout = 'primary',
74 | block = false,
75 | icon,
76 | iconLeft,
77 | iconRight,
78 | className,
79 | children,
80 | ...other
81 | } = props
82 | const {
83 | theme: { button },
84 | } = useContext(ThemeContext)
85 |
86 | function hasIcon() {
87 | return !!icon || !!iconLeft || !!iconRight
88 | }
89 |
90 | warn(
91 | hasIcon() && !other['aria-label'] && !children,
92 | 'Button',
93 | 'You are using an icon button, but no "aria-label" attribute was found. Add an "aria-label" attribute to work as a label for screen readers.'
94 | )
95 |
96 | const IconLeft = iconLeft || icon
97 | const IconRight = iconRight
98 |
99 | const baseStyle = button.base
100 | const blockStyle = button.block
101 | const sizeStyles = {
102 | larger: button.size.larger,
103 | large: button.size.large,
104 | regular: button.size.regular,
105 | small: button.size.small,
106 | /**
107 | * Only used in Pagination.
108 | * Not meant for general use.
109 | */
110 | pagination: button.size.pagination,
111 | }
112 | const iconSizeStyles = {
113 | larger: button.size.icon.larger,
114 | large: button.size.icon.large,
115 | regular: button.size.icon.regular,
116 | small: button.size.icon.small,
117 | pagination: button.size.icon.regular,
118 | }
119 | const iconStyle = button.icon[size]
120 | const layoutStyles = {
121 | primary: button.primary.base,
122 | outline: button.outline.base,
123 | link: button.link.base,
124 | }
125 | const activeStyles = {
126 | primary: button.primary.active,
127 | outline: button.outline.active,
128 | link: button.link.active,
129 | }
130 | const disabledStyles = {
131 | primary: button.primary.disabled,
132 | outline: button.outline.disabled,
133 | link: button.link.disabled,
134 | }
135 |
136 | /**
137 | * Only used in DropdownItem.
138 | * Not meant for general use.
139 | */
140 | const dropdownItemStyle = button.dropdownItem.base
141 |
142 | const buttonStyles =
143 | layout === '__dropdownItem'
144 | ? classNames(dropdownItemStyle, className)
145 | : classNames(
146 | baseStyle,
147 | // has icon but no children
148 | hasIcon() && !children && iconSizeStyles[size],
149 | // has icon and children
150 | hasIcon() && children && sizeStyles[size],
151 | // does not have icon
152 | !hasIcon() && sizeStyles[size],
153 | layoutStyles[layout],
154 | disabled ? disabledStyles[layout] : activeStyles[layout],
155 | block ? blockStyle : null,
156 | className
157 | )
158 |
159 | const iconLeftStyles = classNames(iconStyle, children ? button.icon.left : '')
160 | const iconRightStyles = classNames(iconStyle, children ? button.icon.right : '')
161 |
162 | return React.createElement(
163 | tag,
164 | {
165 | className: buttonStyles,
166 | ref,
167 | disabled,
168 | type,
169 | ...other,
170 | },
171 | IconLeft
172 | ? React.createElement(IconLeft, { className: iconLeftStyles, 'aria-hidden': true })
173 | : null,
174 | children,
175 | IconRight
176 | ? React.createElement(IconRight, { className: iconRightStyles, 'aria-hidden': true })
177 | : null
178 | )
179 | })
180 |
181 | export default Button
182 |
--------------------------------------------------------------------------------
/src/Card.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface CardProps extends React.HTMLAttributes {
6 | /**
7 | * Removes default styles (if true) so you can override with your own background styles
8 | */
9 | colored?: boolean
10 | }
11 |
12 | const Card = React.forwardRef(function Card(props, ref) {
13 | const { className, children, colored = false, ...other } = props
14 | const {
15 | theme: { card },
16 | } = useContext(ThemeContext)
17 |
18 | const baseStyle = card.base
19 | const uncoloredStyle = card.default
20 |
21 | const cls = classNames(baseStyle, !colored && uncoloredStyle, className)
22 |
23 | return (
24 | ]
25 | {children}
26 |
27 | )
28 | })
29 |
30 | export default Card
31 |
--------------------------------------------------------------------------------
/src/CardBody.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const CardBody = React.forwardRef(function CardBody(props, ref) {
8 | const { className, children, ...other } = props
9 | const {
10 | theme: { cardBody },
11 | } = useContext(ThemeContext)
12 |
13 | const baseStyle = cardBody.base
14 |
15 | const cls = classNames(baseStyle, className)
16 |
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | })
23 |
24 | export default CardBody
25 |
--------------------------------------------------------------------------------
/src/Dropdown.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext, useRef } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 | import Transition from './Transition'
5 | import FocusLock from 'react-focus-lock'
6 |
7 | export interface DropdownProps extends React.HTMLAttributes {
8 | /**
9 | * Function executed when the dropdown is closed
10 | */
11 | onClose: () => void
12 | /**
13 | * Defines if the dropdown is open
14 | */
15 | isOpen: boolean
16 | /**
17 | * Defines the alignement of the dropdown related to its parent
18 | */
19 | align?: 'left' | 'right'
20 | }
21 |
22 | const Dropdown = React.forwardRef(function Dropdown(props, ref) {
23 | const { children, onClose, isOpen, className, align = 'left', ...other } = props
24 | const {
25 | theme: { dropdown },
26 | } = useContext(ThemeContext)
27 |
28 | const baseStyle = dropdown.base
29 | const alignStyle = dropdown.align[align]
30 |
31 | function handleEsc(e: KeyboardEvent) {
32 | if (e.key === 'Esc' || e.key === 'Escape') {
33 | onClose()
34 | }
35 | }
36 |
37 | const dropdownRef = useRef(null)
38 | function handleClickOutside(e: MouseEvent) {
39 | if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
40 | onClose()
41 | }
42 | }
43 |
44 | useEffect(() => {
45 | document.addEventListener('click', handleClickOutside, { capture: true })
46 | document.addEventListener('keydown', handleEsc, { capture: true })
47 | return () => {
48 | document.removeEventListener('click', handleClickOutside)
49 | document.removeEventListener('keydown', handleEsc)
50 | }
51 | }, [isOpen])
52 |
53 | const cls = classNames(baseStyle, alignStyle, className)
54 |
55 | return (
56 |
62 |
69 |
70 | )
71 | })
72 |
73 | export default Dropdown
74 |
--------------------------------------------------------------------------------
/src/DropdownItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { ButtonProps } from './Button'
3 | import Button from './Button'
4 | import { ThemeContext } from './context/ThemeContext'
5 |
6 | type Ref = typeof Button
7 | const DropdownItem = React.forwardRef[(function DropdownItem(props, ref) {
8 | // Note: className is passed to the inner Button
9 | const { children, ...other } = props
10 |
11 | const {
12 | theme: { dropdownItem },
13 | } = useContext(ThemeContext)
14 |
15 | const baseStyle = dropdownItem.base
16 |
17 | return (
18 |
19 |
22 |
23 | )
24 | })
25 |
26 | export default DropdownItem
27 |
--------------------------------------------------------------------------------
/src/HelperText.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface HelperTextProps extends React.HTMLAttributes {
6 | /**
7 | * Defines the color of the helper text (the same as with Input, Select, etc.)
8 | */
9 | valid?: boolean
10 | }
11 |
12 | const HelperText = React.forwardRef(function HelperText(
13 | props,
14 | ref
15 | ) {
16 | const { children, valid, className, ...other } = props
17 | const {
18 | theme: { helperText },
19 | } = useContext(ThemeContext)
20 |
21 | const baseStyle = helperText.base
22 | const validStyle = helperText.valid
23 | const invalidStyle = helperText.invalid
24 |
25 | const validationStyle = (valid: boolean | undefined): string => {
26 | switch (valid) {
27 | case true:
28 | return validStyle
29 | case false:
30 | return invalidStyle
31 | default:
32 | return ''
33 | }
34 | }
35 |
36 | const cls = classNames(baseStyle, validationStyle(valid), className)
37 |
38 | return (
39 |
40 | {children}
41 |
42 | )
43 | })
44 |
45 | export default HelperText
46 |
--------------------------------------------------------------------------------
/src/Input.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface InputProps extends React.ComponentPropsWithRef<'input'> {
6 | /**
7 | * Defines the color of the input
8 | */
9 | valid?: boolean
10 | /**
11 | * Defines if the input is disabled
12 | */
13 | disabled?: boolean
14 | /**
15 | * Defines the type of the input
16 | */
17 | type?: string
18 | }
19 |
20 | const Input = React.forwardRef(function Input(props, ref) {
21 | const { valid, disabled, className, type = 'text', ...other } = props
22 |
23 | const {
24 | theme: { input },
25 | } = useContext(ThemeContext)
26 |
27 | const baseStyle = input.base
28 | const activeStyle = input.active
29 | const disabledStyle = input.disabled
30 | const validStyle = input.valid
31 | const invalidStyle = input.invalid
32 | const radioStyle = input.radio
33 | const checkStyle = input.checkbox
34 |
35 | function hasValidation(valid: boolean | undefined) {
36 | return valid !== undefined
37 | }
38 |
39 | function validationStyle(valid: boolean | undefined): string {
40 | if (hasValidation(valid)) {
41 | return valid ? validStyle : invalidStyle
42 | }
43 | return ''
44 | }
45 |
46 | function typeStyle(type: string): string {
47 | switch (type) {
48 | case 'radio':
49 | return radioStyle
50 | case 'checkbox':
51 | return checkStyle
52 | default:
53 | return baseStyle
54 | }
55 | }
56 |
57 | const cls = classNames(
58 | typeStyle(type),
59 | // don't apply activeStyle if has valid or disabled
60 | !hasValidation(valid) && !disabled && activeStyle,
61 | // don't apply disabledStyle if has valid
62 | !hasValidation(valid) && disabled && disabledStyle,
63 | validationStyle(valid),
64 | className
65 | )
66 |
67 | return
68 | })
69 |
70 | export default Input
71 |
--------------------------------------------------------------------------------
/src/Label.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface LabelProps extends React.HTMLAttributes {
6 | /**
7 | * Applies specific styles for checkboxes
8 | */
9 | check?: boolean
10 | /**
11 | * Applies specific styles for radios
12 | */
13 | radio?: boolean
14 | /**
15 | * Defines if the label is disabled (you should still disable child elements)
16 | */
17 | disabled?: boolean
18 | }
19 |
20 | const Label = React.forwardRef(function Label(props, ref) {
21 | const { children, check, radio, disabled, className, ...other } = props
22 | const {
23 | theme: { label },
24 | } = useContext(ThemeContext)
25 |
26 | const baseStyle = label.base
27 | const checkStyle = label.check
28 | const disabledStyle = label.disabled
29 |
30 | const cls = classNames(
31 | baseStyle,
32 | // check and radio are interchangeable
33 | (check || radio) && checkStyle,
34 | disabled && disabledStyle,
35 | className
36 | )
37 |
38 | return (
39 |
42 | )
43 | })
44 |
45 | export default Label
46 |
--------------------------------------------------------------------------------
/src/Modal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext, useState } from 'react'
2 | import { createPortal } from 'react-dom'
3 | import Backdrop from './Backdrop'
4 | import Transition from './Transition'
5 | import FocusLock from 'react-focus-lock'
6 |
7 | import { ThemeContext } from './context/ThemeContext'
8 |
9 | export interface ModalProps extends React.HTMLAttributes {
10 | /**
11 | * Function executed when the dropdown is closed
12 | */
13 | onClose: () => void
14 | /**
15 | * Defines if the modal is open
16 | */
17 | isOpen: boolean
18 | }
19 |
20 | const Modal = React.forwardRef(function Modal(props, ref) {
21 | const { children, onClose, isOpen, ...other } = props
22 |
23 | const {
24 | theme: { modal },
25 | } = useContext(ThemeContext)
26 |
27 | const baseStyle = modal.base
28 |
29 | function handleEsc(e: KeyboardEvent) {
30 | if (e.key === 'Esc' || e.key === 'Escape') {
31 | onClose()
32 | }
33 | }
34 |
35 | useEffect(() => {
36 | document.addEventListener('keydown', handleEsc, { capture: true })
37 | return () => {
38 | document.removeEventListener('keydown', handleEsc)
39 | }
40 | })
41 |
42 | const [mounted, setMounted] = useState(false)
43 |
44 | useEffect(() => {
45 | setMounted(true)
46 | }, [])
47 |
48 | const modalComponent = (
49 |
50 | <>
51 |
59 |
60 |
68 | ] e.stopPropagation()}
72 | ref={ref}
73 | {...other}
74 | >
75 |
76 |
77 |
96 |
97 | {children}
98 |
99 |
100 |
101 |
102 |
103 | >
104 |
105 | )
106 |
107 | return mounted ? createPortal(modalComponent, document.body) : null
108 | })
109 |
110 | export default Modal
111 |
--------------------------------------------------------------------------------
/src/ModalBody.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const ModalBody = React.forwardRef(function ModalBody(props, ref) {
8 | const { children, className, ...other } = props
9 | const {
10 | theme: { modalBody },
11 | } = useContext(ThemeContext)
12 |
13 | const baseStyle = modalBody.base
14 |
15 | const cls = classNames(baseStyle, className)
16 |
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | })
23 |
24 | export default ModalBody
25 |
--------------------------------------------------------------------------------
/src/ModalFooter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const ModalFooter = React.forwardRef(function ModalFooter(props, ref) {
8 | const { children, className, ...other } = props
9 | const {
10 | theme: { modalFooter },
11 | } = useContext(ThemeContext)
12 |
13 | const baseStyle = modalFooter.base
14 |
15 | const cls = classNames(baseStyle, className)
16 |
17 | return (
18 |
21 | )
22 | })
23 |
24 | export default ModalFooter
25 |
--------------------------------------------------------------------------------
/src/ModalHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const ModalHeader = React.forwardRef(function ModalHeader(props, ref) {
8 | const { children, className, ...other } = props
9 | const {
10 | theme: { modalHeader },
11 | } = useContext(ThemeContext)
12 |
13 | const baseStyle = modalHeader.base
14 |
15 | const cls = classNames(baseStyle, className)
16 |
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | })
23 |
24 | export default ModalHeader
25 |
--------------------------------------------------------------------------------
/src/Pagination.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react'
2 | import Button, { ButtonAsButtonProps } from './Button'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | const PrevIcon: React.FC = function PrevIcon(props) {
6 | return (
7 |
14 | )
15 | }
16 |
17 | const NextIcon: React.FC = function NextIcon(props) {
18 | return (
19 |
26 | )
27 | }
28 |
29 | interface NavigationButtonProps extends ButtonAsButtonProps {
30 | directionIcon: 'prev' | 'next'
31 | }
32 |
33 | export const NavigationButton: React.FC = function NavigationButton({
34 | onClick,
35 | disabled,
36 | directionIcon,
37 | }) {
38 | const ariaLabel = directionIcon === 'prev' ? 'Previous' : 'Next'
39 |
40 | const icon = directionIcon === 'prev' ? PrevIcon : NextIcon
41 |
42 | return (
43 |
51 | )
52 | }
53 |
54 | interface PageButtonProps extends ButtonAsButtonProps {
55 | /**
56 | * The page the button represents
57 | */
58 | page: string | number
59 | /**
60 | * Defines if the button is active
61 | */
62 | isActive?: boolean
63 | }
64 |
65 | export const PageButton: React.FC = function PageButton({
66 | page,
67 | isActive,
68 | onClick,
69 | }) {
70 | return (
71 |
74 | )
75 | }
76 |
77 | export const EmptyPageButton = () => ...
78 |
79 | export interface PaginationProps {
80 | /**
81 | * The total number of results
82 | */
83 | totalResults: number
84 | /**
85 | * The number of results shown per page
86 | */
87 | resultsPerPage?: number
88 | /**
89 | * The accessible name of the pagination (what does it refer to?)
90 | */
91 | label: string
92 | /**
93 | * The function executed on page change
94 | */
95 | onChange: (activePage: number) => void
96 | }
97 |
98 | type Ref = HTMLDivElement
99 |
100 | const Pagination = React.forwardRef[(function Pagination(props, ref) {
101 | const { totalResults, resultsPerPage = 10, label, onChange, ...other } = props
102 | const [pages, setPages] = useState<(number | string)[]>([])
103 | const [activePage, setActivePage] = useState(1)
104 |
105 | const TOTAL_PAGES = Math.ceil(totalResults / resultsPerPage)
106 | const FIRST_PAGE = 1
107 | const LAST_PAGE = TOTAL_PAGES
108 | const MAX_VISIBLE_PAGES = 7
109 |
110 | function handlePreviousClick() {
111 | setActivePage(activePage - 1)
112 | }
113 |
114 | function handleNextClick() {
115 | setActivePage(activePage + 1)
116 | }
117 |
118 | useEffect(() => {
119 | // [1], 2, 3, 4, 5, ..., 12 case #1
120 | // 1, [2], 3, 4, 5, ..., 12
121 | // 1, 2, [3], 4, 5, ..., 12
122 | // 1, 2, 3, [4], 5, ..., 12
123 | // 1, ..., 4, [5], 6, ..., 12 case #2
124 | // 1, ..., 5, [6], 7, ..., 12
125 | // 1, ..., 6, [7], 8, ..., 12
126 | // 1, ..., 7, [8], 9, ..., 12
127 | // 1, ..., 8, [9], 10, 11, 12 case #3
128 | // 1, ..., 8, 9, [10], 11, 12
129 | // 1, ..., 8, 9, 10, [11], 12
130 | // 1, ..., 8, 9, 10, 11, [12]
131 | // [1], 2, 3, 4, 5, ..., 8
132 | // always show first and last
133 | // max of 7 pages shown (incl. [...])
134 | if (TOTAL_PAGES <= MAX_VISIBLE_PAGES) {
135 | setPages(Array.from({ length: TOTAL_PAGES }).map((_, i) => i + 1))
136 | } else if (activePage < 5) {
137 | // #1 active page < 5 -> show first 5
138 | setPages([1, 2, 3, 4, 5, '...', TOTAL_PAGES])
139 | } else if (activePage >= 5 && activePage < TOTAL_PAGES - 3) {
140 | // #2 active page >= 5 && < TOTAL_PAGES - 3
141 | setPages([1, '...', activePage - 1, activePage, activePage + 1, '...', TOTAL_PAGES])
142 | } else {
143 | // #3 active page >= TOTAL_PAGES - 3 -> show last
144 | setPages([
145 | 1,
146 | '...',
147 | TOTAL_PAGES - 4,
148 | TOTAL_PAGES - 3,
149 | TOTAL_PAGES - 2,
150 | TOTAL_PAGES - 1,
151 | TOTAL_PAGES,
152 | ])
153 | }
154 | }, [activePage, TOTAL_PAGES])
155 |
156 | useEffect(() => {
157 | onChange(activePage)
158 | }, [activePage])
159 |
160 | const {
161 | theme: { pagination },
162 | } = useContext(ThemeContext)
163 |
164 | const baseStyle = pagination.base
165 |
166 | return (
167 | ]
168 | {/*
169 | * This (label) should probably be an option, and not the default
170 | */}
171 |
172 | Showing {activePage * resultsPerPage - resultsPerPage + 1}-
173 | {Math.min(activePage * resultsPerPage, totalResults)} of {totalResults}
174 |
175 |
176 |
177 |
208 |
209 |
210 | )
211 | })
212 |
213 | export default Pagination
214 |
--------------------------------------------------------------------------------
/src/Select.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface SelectProps extends React.ComponentPropsWithRef<'select'> {
6 | /**
7 | * Defines the color of the select
8 | */
9 | valid?: boolean
10 | }
11 |
12 | const Select = React.forwardRef(function Select(props, ref) {
13 | const { valid, children, className, multiple, disabled, ...other } = props
14 |
15 | const {
16 | theme: { select },
17 | } = useContext(ThemeContext)
18 |
19 | const baseStyle = select.base
20 | const activeStyle = select.active
21 | const validStyle = select.valid
22 | const invalidStyle = select.invalid
23 | const disabledStyle = select.disabled
24 | const selectStyle = select.select
25 |
26 | function hasValidation(valid: boolean | undefined) {
27 | return valid !== undefined
28 | }
29 |
30 | function validationStyle(valid: boolean | undefined): string {
31 | if (hasValidation(valid)) {
32 | return valid ? validStyle : invalidStyle
33 | }
34 | return ''
35 | }
36 |
37 | const cls = classNames(
38 | baseStyle,
39 | // don't apply activeStyle if has valid or disabled
40 | !hasValidation(valid) && !disabled && activeStyle,
41 | // don't apply disabledStyle if has valid
42 | !hasValidation(valid) && disabled && disabledStyle,
43 | validationStyle(valid),
44 | !multiple && selectStyle,
45 | className
46 | )
47 |
48 | return (
49 |
52 | )
53 | })
54 |
55 | export default Select
56 |
--------------------------------------------------------------------------------
/src/Table.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export interface TableProps extends React.TableHTMLAttributes {}
4 |
5 | const Table = React.forwardRef(function Table(props, ref) {
6 | const { children, ...other } = props
7 | return (
8 |
13 | )
14 | })
15 |
16 | export default Table
17 |
--------------------------------------------------------------------------------
/src/TableBody.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const TableBody = React.forwardRef(function TableBody(props, ref) {
8 | const { className, children, ...other } = props
9 |
10 | const {
11 | theme: { tableBody },
12 | } = useContext(ThemeContext)
13 |
14 | const baseStyle = tableBody.base
15 |
16 | const cls = classNames(baseStyle, className)
17 |
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | })
24 |
25 | export default TableBody
26 |
--------------------------------------------------------------------------------
/src/TableCell.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.TdHTMLAttributes {}
6 |
7 | const TableCell = React.forwardRef(function TableCell(props, ref) {
8 | const { className, children, ...other } = props
9 |
10 | const {
11 | theme: { tableCell },
12 | } = useContext(ThemeContext)
13 |
14 | const baseStyle = tableCell.base
15 |
16 | const cls = classNames(baseStyle, className)
17 |
18 | return (
19 |
20 | {children}
21 | |
22 | )
23 | })
24 |
25 | export default TableCell
26 |
--------------------------------------------------------------------------------
/src/TableContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const TableContainer = React.forwardRef(function TableContainer(props, ref) {
8 | const { className, children, ...other } = props
9 |
10 | const {
11 | theme: { tableContainer },
12 | } = useContext(ThemeContext)
13 |
14 | const baseStyle = tableContainer.base
15 |
16 | const cls = classNames(baseStyle, className)
17 |
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | })
24 |
25 | export default TableContainer
26 |
--------------------------------------------------------------------------------
/src/TableFooter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const TableFooter = React.forwardRef(function TableFooter(props, ref) {
8 | const { className, children, ...other } = props
9 |
10 | const {
11 | theme: { tableFooter },
12 | } = useContext(ThemeContext)
13 |
14 | const baseStyle = tableFooter.base
15 |
16 | const cls = classNames(baseStyle, className)
17 |
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | })
24 |
25 | export default TableFooter
26 |
--------------------------------------------------------------------------------
/src/TableHeader.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const TableHeader = React.forwardRef(function TableHeader(
8 | props,
9 | ref
10 | ) {
11 | const { className, children, ...other } = props
12 |
13 | const {
14 | theme: { tableHeader },
15 | } = useContext(ThemeContext)
16 |
17 | const baseStyle = tableHeader.base
18 |
19 | const cls = classNames(baseStyle, className)
20 |
21 | return (
22 |
23 | {children}
24 |
25 | )
26 | })
27 |
28 | export default TableHeader
29 |
--------------------------------------------------------------------------------
/src/TableRow.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | interface Props extends React.HTMLAttributes {}
6 |
7 | const TableRow = React.forwardRef(function TableRow(props, ref) {
8 | const { className, children, ...other } = props
9 |
10 | const {
11 | theme: { tableRow },
12 | } = useContext(ThemeContext)
13 |
14 | const baseStyle = tableRow.base
15 |
16 | const cls = classNames(baseStyle, className)
17 |
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | })
24 |
25 | export default TableRow
26 |
--------------------------------------------------------------------------------
/src/Textarea.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import classNames from 'classnames'
3 | import { ThemeContext } from './context/ThemeContext'
4 |
5 | export interface TextareaProps extends React.ComponentPropsWithRef<'textarea'> {
6 | /**
7 | * Defines the color of the textarea
8 | */
9 | valid?: boolean
10 | }
11 |
12 | const Textarea = React.forwardRef(function Textarea(
13 | props,
14 | ref
15 | ) {
16 | const { valid, disabled, className, children, ...other } = props
17 |
18 | const {
19 | theme: { textarea },
20 | } = useContext(ThemeContext)
21 |
22 | const baseStyle = textarea.base
23 | const activeStyle = textarea.active
24 | const disabledStyle = textarea.disabled
25 | const validStyle = textarea.valid
26 | const invalidStyle = textarea.invalid
27 |
28 | function hasValidation(valid: boolean | undefined) {
29 | return valid !== undefined
30 | }
31 |
32 | function validationStyle(valid: boolean | undefined): string {
33 | if (hasValidation(valid)) {
34 | return valid ? validStyle : invalidStyle
35 | }
36 | return ''
37 | }
38 |
39 | const cls = classNames(
40 | baseStyle,
41 | // don't apply activeStyle if has valid or disabled
42 | !hasValidation(valid) && !disabled && activeStyle,
43 | // don't apply disabledStyle if has valid
44 | !hasValidation(valid) && disabled && disabledStyle,
45 | validationStyle(valid),
46 | className
47 | )
48 |
49 | return (
50 |
53 | )
54 | })
55 |
56 | export default Textarea
57 |
--------------------------------------------------------------------------------
/src/Transition.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * https://gist.github.com/adamwathan/e0a791aa0419098a7ece70028b2e641e
3 | */
4 | import React, { useContext, useEffect, useRef } from 'react'
5 | import { CSSTransition as ReactCSSTransition } from 'react-transition-group'
6 |
7 | interface TransitionContext {
8 | parent: {
9 | appear?: string
10 | show?: boolean
11 | isInitialRender?: boolean
12 | }
13 | }
14 | const transitionContext = React.createContext({
15 | parent: {},
16 | })
17 |
18 | function useIsInitialRender() {
19 | const isInitialRender = useRef(true)
20 | useEffect(() => {
21 | isInitialRender.current = false
22 | }, [])
23 | return isInitialRender.current
24 | }
25 |
26 | interface TransitionProps {
27 | children?: React.ReactNode
28 | show?: boolean
29 | enter?: string
30 | enterFrom?: string
31 | enterTo?: string
32 | leave?: string
33 | leaveFrom?: string
34 | leaveTo?: string
35 | appear?: string
36 | }
37 |
38 | const CSSTransition: React.FC = function CSSTransition({
39 | show,
40 | enter = '',
41 | enterFrom = '',
42 | enterTo = '',
43 | leave = '',
44 | leaveFrom = '',
45 | leaveTo = '',
46 | appear,
47 | children,
48 | }) {
49 | const enterClasses = enter.split(' ').filter((s) => s.length)
50 | const enterFromClasses = enterFrom.split(' ').filter((s) => s.length)
51 | const enterToClasses = enterTo.split(' ').filter((s) => s.length)
52 | const leaveClasses = leave.split(' ').filter((s) => s.length)
53 | const leaveFromClasses = leaveFrom.split(' ').filter((s) => s.length)
54 | const leaveToClasses = leaveTo.split(' ').filter((s) => s.length)
55 |
56 | function addClasses(node: HTMLElement, classes: string[]) {
57 | classes.length && node.classList.add(...classes)
58 | }
59 |
60 | function removeClasses(node: HTMLElement, classes: string[]) {
61 | classes.length && node.classList.remove(...classes)
62 | }
63 |
64 | return (
65 | {
70 | node.addEventListener('transitionend', done, false)
71 | }}
72 | onEnter={(node: HTMLElement) => {
73 | addClasses(node, [...enterClasses, ...enterFromClasses])
74 | }}
75 | onEntering={(node: HTMLElement) => {
76 | removeClasses(node, enterFromClasses)
77 | addClasses(node, enterToClasses)
78 | }}
79 | onEntered={(node: HTMLElement) => {
80 | removeClasses(node, [...enterToClasses, ...enterClasses])
81 | }}
82 | onExit={(node: HTMLElement) => {
83 | addClasses(node, [...leaveClasses, ...leaveFromClasses])
84 | }}
85 | onExiting={(node: HTMLElement) => {
86 | removeClasses(node, leaveFromClasses)
87 | addClasses(node, leaveToClasses)
88 | }}
89 | onExited={(node: HTMLElement) => {
90 | removeClasses(node, [...leaveToClasses, ...leaveClasses])
91 | }}
92 | >
93 | {children}
94 |
95 | )
96 | }
97 |
98 | const Transition: React.FC = function Transition({ show, appear, ...rest }) {
99 | const { parent } = useContext(transitionContext)
100 | const isInitialRender = useIsInitialRender()
101 | const isChild = show === undefined
102 |
103 | if (isChild) {
104 | return
105 | } else
106 | return (
107 |
116 |
117 |
118 | )
119 | }
120 |
121 | export default Transition
122 |
--------------------------------------------------------------------------------
/src/Windmill.tsx:
--------------------------------------------------------------------------------
1 | import React, { useLayoutEffect, useMemo } from 'react'
2 | import { ThemeContext } from './context/ThemeContext'
3 | import defaultTheme from './themes/default'
4 | import { mergeDeep } from './utils/mergeDeep'
5 | import useDarkMode from './utils/useDarkMode'
6 |
7 | interface Props extends React.HTMLAttributes {
8 | children: React.ReactNode
9 | /**
10 | * Defines the styles used throughout the library
11 | */
12 | theme?: object
13 | /**
14 | * Defines dark mode as the default theme
15 | */
16 | dark?: boolean
17 | /**
18 | * Allows the change of theme, reading user's preferences and saving them
19 | */
20 | usePreferences?: boolean
21 | }
22 |
23 | const Windmill: React.FC = ({
24 | children,
25 | theme: customTheme,
26 | dark,
27 | usePreferences = false,
28 | }) => {
29 | const mergedTheme = mergeDeep(defaultTheme, customTheme)
30 | const [mode, setMode, toggleMode] = useDarkMode(usePreferences)
31 |
32 | useLayoutEffect(() => {
33 | if (dark) {
34 | if (setMode != null) {
35 | setMode('dark')
36 | }
37 | document.documentElement.classList.add(`dark`)
38 | }
39 | }, [dark])
40 |
41 | const value = useMemo(
42 | () => ({
43 | theme: mergedTheme,
44 | mode,
45 | toggleMode,
46 | }),
47 | [mode]
48 | )
49 |
50 | return {children}
51 | }
52 |
53 | export default Windmill
54 |
--------------------------------------------------------------------------------
/src/__tests__/Alert.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import Alert, { SuccessIcon, DangerIcon, WarningIcon, InfoIcon, NeutralIcon } from '../Alert'
4 |
5 | describe('Avatar', () => {
6 | it('should render without crashing', () => {
7 | mount()
8 | })
9 |
10 | it('should not contain a close button', () => {
11 | const wrapper = mount()
12 |
13 | expect(wrapper.find('button')).toHaveLength(0)
14 | })
15 |
16 | it('should contain a close button', () => {
17 | const wrapper = mount( {}} />)
18 |
19 | expect(wrapper.find('button')).toHaveLength(1)
20 | })
21 |
22 | it('should call a function when close button is clicked', () => {
23 | const onClose = jest.fn()
24 | const wrapper = mount()
25 |
26 | wrapper.find('button[aria-label="close"]').simulate('click')
27 |
28 | expect(onClose).toHaveBeenCalled()
29 | })
30 |
31 | it('should render a success icon', () => {
32 | const wrapper = mount()
33 | const Icon = SuccessIcon
34 |
35 | expect(wrapper.find(Icon)).toHaveLength(1)
36 | })
37 |
38 | it('should render a danger icon', () => {
39 | const wrapper = mount()
40 | const Icon = DangerIcon
41 |
42 | expect(wrapper.find(Icon)).toHaveLength(1)
43 | })
44 |
45 | it('should render an info icon', () => {
46 | const wrapper = mount()
47 | const Icon = InfoIcon
48 |
49 | expect(wrapper.find(Icon)).toHaveLength(1)
50 | })
51 |
52 | it('should render a warning icon', () => {
53 | const wrapper = mount()
54 | const Icon = WarningIcon
55 |
56 | expect(wrapper.find(Icon)).toHaveLength(1)
57 | })
58 |
59 | it('should render a neutral icon', () => {
60 | const wrapper = mount()
61 | const Icon = NeutralIcon
62 |
63 | expect(wrapper.find(Icon)).toHaveLength(1)
64 | })
65 |
66 | it('should render a neutral icon for an invalid type', () => {
67 | // @ts-expect-error
68 | const wrapper = mount()
69 | const Icon = NeutralIcon
70 |
71 | expect(wrapper.find(Icon)).toHaveLength(1)
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/src/__tests__/Avatar.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import Avatar from '../Avatar'
4 |
5 | describe('Avatar', () => {
6 | it('should render without crashing', () => {
7 | mount()
8 | })
9 |
10 | it('should render with base styles', () => {
11 | const expected = 'relative rounded-full inline-block'
12 | const wrapper = mount()
13 |
14 | expect(wrapper.find(Avatar).getDOMNode().getAttribute('class')).toContain(expected)
15 | })
16 |
17 | it('should render with large styles', () => {
18 | const expected = 'w-10 h-10'
19 | const wrapper = mount()
20 |
21 | expect(wrapper.find(Avatar).getDOMNode().getAttribute('class')).toContain(expected)
22 | })
23 |
24 | it('should render with regular styles using prop', () => {
25 | const expected = 'w-8 h-8'
26 | const wrapper = mount()
27 |
28 | expect(wrapper.find(Avatar).getDOMNode().getAttribute('class')).toContain(expected)
29 | })
30 |
31 | it('should render with regular styles by default', () => {
32 | const expected = 'w-8 h-8'
33 | const wrapper = mount()
34 |
35 | expect(wrapper.find(Avatar).getDOMNode().getAttribute('class')).toContain(expected)
36 | })
37 |
38 | it('should render with small styles', () => {
39 | const expected = 'w-6 h-6'
40 | const wrapper = mount()
41 |
42 | expect(wrapper.find(Avatar).getDOMNode().getAttribute('class')).toContain(expected)
43 | })
44 |
45 | it('should contain an image with alt text', () => {
46 | const expected = 'Lorem'
47 | const wrapper = mount()
48 |
49 | expect(wrapper.find('img').getDOMNode().getAttribute('alt')).toContain(expected)
50 | })
51 |
52 | it('should contain an image with the correct src', () => {
53 | const expected = 'test'
54 | const wrapper = mount()
55 |
56 | expect(wrapper.find('img').getDOMNode().getAttribute('src')).toContain(expected)
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/__tests__/Backdrop.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import Backdrop from '../Backdrop'
4 |
5 | describe('Backdrop', () => {
6 | it('should render without crashing', () => {
7 | mount()
8 | })
9 |
10 | it('should render with base styles', () => {
11 | const expected =
12 | 'fixed inset-0 z-40 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center'
13 | const wrapper = mount()
14 |
15 | expect(wrapper.find(Backdrop).getDOMNode().getAttribute('class')).toContain(expected)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/src/__tests__/Badge.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import Badge from '../Badge'
4 |
5 | describe('Badge', () => {
6 | it('should render without crashing', () => {
7 | mount()
8 | })
9 |
10 | it('should render with base styles', () => {
11 | const expected = 'inline-flex px-2 text-xs font-medium leading-5 rounded-full'
12 | const wrapper = mount()
13 |
14 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
15 | })
16 |
17 | it('should render with success styles', () => {
18 | const expected = 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100'
19 | const wrapper = mount()
20 |
21 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
22 | })
23 |
24 | it('should render with danger styles', () => {
25 | const expected = 'text-red-700 bg-red-100 dark:text-red-100 dark:bg-red-700'
26 | const wrapper = mount()
27 |
28 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
29 | })
30 |
31 | it('should render with warning styles', () => {
32 | const expected = 'text-orange-700 bg-orange-100 dark:text-white dark:bg-orange-600'
33 | const wrapper = mount()
34 |
35 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
36 | })
37 |
38 | it('should render with neutral styles', () => {
39 | const expected = 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700'
40 | const wrapper = mount()
41 |
42 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
43 | })
44 |
45 | it('should render with primary styles', () => {
46 | const expected = 'text-purple-700 bg-purple-100 dark:text-white dark:bg-purple-600'
47 | const wrapper = mount()
48 |
49 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
50 | })
51 |
52 | it('should render with primary styles when no type is used', () => {
53 | const expected = 'text-purple-700 bg-purple-100 dark:text-white dark:bg-purple-600'
54 | const wrapper = mount()
55 |
56 | expect(wrapper.find('span').getDOMNode().getAttribute('class')).toContain(expected)
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/src/__tests__/Button.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { mount } from 'enzyme'
3 | import Button from '../Button'
4 | import HeartIcon from './utils/heart.svg'
5 | import theme from '../themes/default'
6 |
7 | describe('Base Button', () => {
8 | it('should render without crashing', () => {
9 | mount()
10 | })
11 |
12 | it('should render a button element', () => {
13 | const wrapper = mount()
14 |
15 | expect(wrapper.find('button')).toHaveLength(1)
16 | })
17 |
18 | it('should render a button with type button', () => {
19 | const wrapper = mount()
20 |
21 | expect(wrapper.find('button[type="button"]')).toHaveLength(1)
22 | })
23 |
24 | it('should render a button with type submit', () => {
25 | const wrapper = mount()
26 |
27 | expect(wrapper.find('button[type="submit"]')).toHaveLength(1)
28 | })
29 |
30 | it('should render a button with type reset', () => {
31 | const wrapper = mount()
32 |
33 | expect(wrapper.find('button[type="reset"]')).toHaveLength(1)
34 | })
35 |
36 | it('should render an anchor element', () => {
37 | const wrapper = mount()
38 |
39 | expect(wrapper.find('a')).toHaveLength(1)
40 | })
41 |
42 | it('should not contain type for anchor element', () => {
43 | const wrapper = mount()
44 |
45 | expect(wrapper.find('a').getDOMNode().getAttribute('type')).toBeNull()
46 | })
47 |
48 | it('should render an arbitrary element', () => {
49 | const wrapper = mount()
50 |
51 | expect(wrapper.find('div')).toHaveLength(1)
52 | })
53 |
54 | it('should render its children', () => {
55 | const wrapper = mount()
56 |
57 | expect(wrapper.find('button').text()).toBe('Hi')
58 | })
59 |
60 | it('should contain base classes', () => {
61 | const expected =
62 | 'inline-flex items-center justify-center cursor-pointer leading-5 transition-colors duration-150 font-medium focus:outline-none'
63 | const wrapper = mount()
64 |
65 | expect(wrapper.find('button').getDOMNode().getAttribute('class')).toContain(expected)
66 | })
67 |
68 | it('should call onClick callback', (done) => {
69 | mount(