mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
chore: move UI into main repo
This commit is contained in:
1 parent
0f78f6d87f
commit
d1b55da7d2
818 files changed
+207681
No files matched your search
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Supabase
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,313 @@
|
||||
# Supabase UI
|
||||
|
||||
Supabase UI is a React UI library.
|
||||
|
||||
🚧
|
||||
Note: Supabase UI is still a work-in-progress until a major release is published.
|
||||
|
||||
[](https://www.producthunt.com/posts/supabase-ui)
|
||||
|
||||
[View docs](https://ui.supabase.com)
|
||||
|
||||
## Install Supabase UI
|
||||
|
||||
### Install TailwindCSS
|
||||
|
||||
You will need to have tailwind already installed.
|
||||
Follow the instructions for [installing tailwind on their docs](https://tailwindcss.com/docs/installation/framework-guides).
|
||||
|
||||
You will need to use at least tailwind v3.
|
||||
|
||||
### Install Supabase UI package
|
||||
|
||||
After that, you can go ahead and install Supabase UI.
|
||||
|
||||
```cli
|
||||
npm install @supabase/ui
|
||||
```
|
||||
|
||||
### Modify the tailwind config
|
||||
|
||||
You will need to update tailwind.config.js in the root of your project.
|
||||
|
||||
#### Default tailwind.config.js tailwind setup
|
||||
|
||||
This is what a regular tailwind config file looks like
|
||||
|
||||
```js
|
||||
// tailwind.config.js
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
```
|
||||
|
||||
#### Update tailwind.config.js to the following
|
||||
|
||||
You will need to wrap the SupabaseUI config around your own tailwind config like below.
|
||||
Also add the new purge rules
|
||||
|
||||
```js
|
||||
// using Supabase UI
|
||||
|
||||
// tailwind.config.js
|
||||
const ui = require('@supabase/ui/dist/config/ui.config.js')
|
||||
|
||||
module.exports = ui({
|
||||
purge: [
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./node_modules/@supabase/ui/dist/config/default-theme.js', // add this
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
})
|
||||
```
|
||||
|
||||
## Using Supabase UI
|
||||
|
||||
### Colors
|
||||
|
||||
Supabase UI uses Radix Colors for everything, and all the colors are already set up and ready to use once the installation above is complete.
|
||||
|
||||
#### Scale
|
||||
|
||||
In addition to accessing all colors from Radix, we have also assigned a new color pallete called `scale`. This is the designated grayscale color we use throughout all the component.
|
||||
|
||||
`scale`, as a default, is actually just the radix `gray` in dark mode, and radix `slate` in light mode. The colors are stored as CSS variables, so they can be swapped out for other color scales. These serve as the backbone of the components theme. It is advised you use `scale` wherever possible so that, if the scale theme is changed, everything will follow suite.
|
||||
|
||||
To ahieve this, the `scale` colors just reference other CSS variables.
|
||||
|
||||
As an example, and for aneyone interested, the CSS variables look a bit like this:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--colors-scale1: var(--colors-gray1) `;
|
||||
--colors-scale2: var(--colors-gray2) `;
|
||||
/* and so on */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--colors-scale1: var(--colors-slate1) `;
|
||||
--colors-scale2: var(--colors-slate2) `;
|
||||
/* and so on */
|
||||
}
|
||||
```
|
||||
|
||||
We have then populated tailwind with the relevant colors based on CSS variables, rather than the usual hex values.
|
||||
|
||||
##### Caveats
|
||||
|
||||
A drawback to this is that [bg opacity classes eg: (bg-scale-100/25)](https://tailwindcss.com/docs/background-color#changing-the-opacity) don't work, although maybe a workaround can be figured out in the future.
|
||||
|
||||
Another, and possibly larger drawback, is that by relying on a 'color scale' as a theme, we are effectively making it quite hard to have element level theming. For example, custom themeing by a user to, perhaps, pick the 'app background color', or maybe the 'border color'. This is rather hard with this current solution but it's also a bit of a drawback with Tailwind its self as we declare the color and index of the color directly in jsx usually.
|
||||
|
||||
A possible workaround could be to create tailwind classes for various things, and then they reference a tailwind class themselves. Although that could be convoluted as we then have a custom tailwind class, referencing another tailwind class, which in turn references a CSS variable (pheww!).
|
||||
|
||||
#### Dark mode
|
||||
|
||||
All colors now do not need a dark mode equivalent.
|
||||
|
||||
For example, `bg-scale-100` will show the dark mode version of the color in dark mode (which is actually `bg-gray-100`), and then automatically show the light mode version when in light mode (which is `bg-slate-100`).
|
||||
|
||||
We have already seperated all the radix colors for light and dark with CSS variables, so the app will render the correct light/dark shade depending on wether or not the app has `.dark` class added into the `<body>`.
|
||||
|
||||
You may however sometimes want to a different color shade in darkmode vs lightmode. This makes sense, a lot of subtle background and borders need to be different between the two so you will need to add some variation in sometimes.
|
||||
|
||||
For example, you may want to have a app background the same shade across light and dark mode but the panel is a white background for light mode, and in dark mode you want the panel in a ligher shade.
|
||||
|
||||
You can just use tailwind dark mode classes as normal to achieve that:
|
||||
|
||||
```jsx
|
||||
const index = () => (
|
||||
<body className="bg-scale-200">
|
||||
<div className="bg-scale-100 dark:bg-scale-300">I am content</div>
|
||||
</body>
|
||||
)
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
Use tailwind classes as normal for typography, but use `text-scale-*` classes for the text color.
|
||||
|
||||
You can also use the class `text-code` to style text for inline code blocks.
|
||||
|
||||
```jsx
|
||||
|
||||
const index = () => {
|
||||
<body>
|
||||
<h1 className="text-xl text-scale-1200">Hello world, this is a header, using scale-1200</h1>
|
||||
<p className="text-sm text-scale-1100">This is a paragraph that is using scale-1100</h1>
|
||||
<p className="text-xs text-scale-900">This is a paragraph that is small and more gray, for things like captions</p>
|
||||
<p className="text-xs text-code">{"$ npm install @supabase/ui"}</p>
|
||||
</body>
|
||||
}
|
||||
```
|
||||
|
||||
Example of importing a component
|
||||
|
||||
```js
|
||||
import { Button } from '@supabase/ui'
|
||||
|
||||
//...
|
||||
|
||||
return <Button>I am a Supabase UI button</Button>
|
||||
```
|
||||
|
||||
It is probably advisable to use [Normalize](https://github.com/sindresorhus/modern-normalize) with Supabase UI for the timebeing.
|
||||
|
||||
### Using Icons
|
||||
|
||||
We use [Feather icon library](https://feathericons.com/) in Supabase UI
|
||||
|
||||
You can use any Icon from the library as a component by prepending `Icon` to any Icon name, like, `<IconMail>`
|
||||
|
||||
```js
|
||||
import { IconMail } from '@supabase/ui'
|
||||
|
||||
//...
|
||||
|
||||
return <IconMail size="small" />
|
||||
```
|
||||
|
||||
## Using Supabase UI Auth
|
||||
|
||||
You can use our Auth widget straight out the box with Supabase auth including social logins.
|
||||
|
||||
<img width="380" alt="Screenshot 2021-02-05 at 19 25 01" src="https://user-images.githubusercontent.com/8291514/107029572-32f72d00-67ea-11eb-982e-e737f052eea1.png">
|
||||
|
||||
The Auth component also includes a context component which detects whether a user is logged in or not.
|
||||
|
||||
Make sure to also install `@supabase/supabase-js`
|
||||
|
||||
```cli
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
You can then easily import `Auth` from the ui library and pass the `createClient` to the `Auth` component.
|
||||
|
||||
```js
|
||||
import { Auth, Typography, Button } from '@supabase/ui'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
const supabase = createClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key'
|
||||
)
|
||||
|
||||
const Container = (props) => {
|
||||
const { user } = Auth.useUser()
|
||||
if (user)
|
||||
return (
|
||||
<>
|
||||
<Text>Signed in: {user.email}</Text>
|
||||
<Button block onClick={() => props.supabaseClient.auth.signOut()}>
|
||||
Sign out
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
return props.children
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<Auth.UserContextProvider supabaseClient={supabase}>
|
||||
<Container supabaseClient={supabase}>
|
||||
<Auth providers={['facebook', 'github']} supabaseClient={supabase} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
Some of these are a work in progress - we invite anyone to submit a [feature request](https://github.com/supabase/ui/issues/new?labels=enhancement&template=2.Feature_request.md) if there is something you would like to see.
|
||||
|
||||
_General_
|
||||
|
||||
- [x] Button
|
||||
- [x] Typography
|
||||
- [x] Icon
|
||||
- [x] Image (work in progress)
|
||||
|
||||
_Data Input_
|
||||
|
||||
- [x] Input
|
||||
- [x] InputNumber
|
||||
- [x] Select (custom select wip)
|
||||
- [x] Checkbox
|
||||
- [x] Radio
|
||||
- [x] Toggle
|
||||
- [ ] Upload
|
||||
- [ ] Slider
|
||||
- [ ] Date picker
|
||||
- [ ] Time picker
|
||||
- [ ] Form
|
||||
|
||||
_Layout_
|
||||
|
||||
- [ ] ~~Layout~~
|
||||
- [ ] ~~Grid (Flex)~~
|
||||
- [x] Divider
|
||||
- [x] Space (Flex)
|
||||
|
||||
_Display_
|
||||
|
||||
- [x] Card
|
||||
- [ ] Avatar
|
||||
- [x] Accordion
|
||||
- [x] Alert
|
||||
- [x] Badge
|
||||
- [x] Menu
|
||||
- [ ] Tooltips
|
||||
- [ ] Tables
|
||||
- [ ] Code block
|
||||
|
||||
_Navigation_
|
||||
|
||||
- [x] Tabs
|
||||
- [x] Breadcrumb
|
||||
- [x] Dropdown
|
||||
- [x] Menu
|
||||
- [ ] Page Header
|
||||
- [ ] Sidebar
|
||||
- [ ] Flyout menu
|
||||
- [ ] Steps
|
||||
|
||||
_Overlay_
|
||||
|
||||
- [x] Modal
|
||||
- [x] Context Menu
|
||||
- [x] Drawer / SidePanel
|
||||
- [ ] Toast messages / Notification
|
||||
- [ ] Progress
|
||||
- [ ] Feeds / Timeline
|
||||
|
||||
_Utility_
|
||||
|
||||
- [x] Loading
|
||||
- [x] Transition (work in progress)
|
||||
|
||||
_Misc_
|
||||
|
||||
- [x] Storybook docs
|
||||
- [ ] Theming (in progress)
|
||||
- [x] Supabase Auth Elements
|
||||
- [x] Documentation website
|
||||
|
||||
We would be keen to hear any feedback on this project.
|
||||
|
||||
Feel free to [submit a question or idea here](https://github.com/supabase/supabase/discussions/category_choices)
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/env',
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
targets: {
|
||||
browsers: ['>2%'],
|
||||
},
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: ['@babel/plugin-transform-runtime', 'add-react-displayname'],
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
const deepMerge = require('deepmerge')
|
||||
const customFormsPlugin = require('@tailwindcss/forms')
|
||||
|
||||
const colors = {
|
||||
transparent: 'transparent',
|
||||
white: '#ffffff',
|
||||
black: '#000000',
|
||||
gray: {
|
||||
'50': '#f9fafb',
|
||||
'100': '#f4f5f7',
|
||||
'200': '#e5e7eb',
|
||||
'300': '#d5d6d7',
|
||||
'400': '#9e9e9e',
|
||||
'500': '#707275',
|
||||
'600': '#4c4f52',
|
||||
'700': '#24262d',
|
||||
'800': '#1a1c23',
|
||||
'900': '#121317',
|
||||
// default values from Tailwind UI palette
|
||||
// '300': '#d2d6dc',
|
||||
// '400': '#9fa6b2',
|
||||
// '500': '#6b7280',
|
||||
// '600': '#4b5563',
|
||||
// '700': '#374151',
|
||||
// '800': '#252f3f',
|
||||
// '900': '#161e2e',
|
||||
},
|
||||
'cool-gray': {
|
||||
'50': '#fbfdfe',
|
||||
'100': '#f1f5f9',
|
||||
'200': '#e2e8f0',
|
||||
'300': '#cfd8e3',
|
||||
'400': '#97a6ba',
|
||||
'500': '#64748b',
|
||||
'600': '#475569',
|
||||
'700': '#364152',
|
||||
'800': '#27303f',
|
||||
'900': '#1a202e',
|
||||
},
|
||||
red: {
|
||||
'50': '#fdf2f2',
|
||||
'100': '#fde8e8',
|
||||
'200': '#fbd5d5',
|
||||
'300': '#f8b4b4',
|
||||
'400': '#f98080',
|
||||
'500': '#f05252',
|
||||
'600': '#e02424',
|
||||
'700': '#c81e1e',
|
||||
'800': '#9b1c1c',
|
||||
'900': '#771d1d',
|
||||
},
|
||||
orange: {
|
||||
'50': '#fff8f1',
|
||||
'100': '#feecdc',
|
||||
'200': '#fcd9bd',
|
||||
'300': '#fdba8c',
|
||||
'400': '#ff8a4c',
|
||||
'500': '#ff5a1f',
|
||||
'600': '#d03801',
|
||||
'700': '#b43403',
|
||||
'800': '#8a2c0d',
|
||||
'900': '#771d1d',
|
||||
},
|
||||
yellow: {
|
||||
'50': '#fdfdea',
|
||||
'100': '#fdf6b2',
|
||||
'200': '#fce96a',
|
||||
'300': '#faca15',
|
||||
'400': '#e3a008',
|
||||
'500': '#c27803',
|
||||
'600': '#9f580a',
|
||||
'700': '#8e4b10',
|
||||
'800': '#723b13',
|
||||
'900': '#633112',
|
||||
},
|
||||
green: {
|
||||
'50': '#f3faf7',
|
||||
'100': '#def7ec',
|
||||
'200': '#bcf0da',
|
||||
'300': '#84e1bc',
|
||||
'400': '#31c48d',
|
||||
'500': '#0e9f6e',
|
||||
'600': '#057a55',
|
||||
'700': '#046c4e',
|
||||
'800': '#03543f',
|
||||
'900': '#014737',
|
||||
},
|
||||
teal: {
|
||||
'50': '#edfafa',
|
||||
'100': '#d5f5f6',
|
||||
'200': '#afecef',
|
||||
'300': '#7edce2',
|
||||
'400': '#16bdca',
|
||||
'500': '#0694a2',
|
||||
'600': '#047481',
|
||||
'700': '#036672',
|
||||
'800': '#05505c',
|
||||
'900': '#014451',
|
||||
},
|
||||
blue: {
|
||||
'50': '#ebf5ff',
|
||||
'100': '#e1effe',
|
||||
'200': '#c3ddfd',
|
||||
'300': '#a4cafe',
|
||||
'400': '#76a9fa',
|
||||
'500': '#3f83f8',
|
||||
'600': '#1c64f2',
|
||||
'700': '#1a56db',
|
||||
'800': '#1e429f',
|
||||
'900': '#233876',
|
||||
},
|
||||
indigo: {
|
||||
'50': '#f0f5ff',
|
||||
'100': '#e5edff',
|
||||
'200': '#cddbfe',
|
||||
'300': '#b4c6fc',
|
||||
'400': '#8da2fb',
|
||||
'500': '#6875f5',
|
||||
'600': '#5850ec',
|
||||
'700': '#5145cd',
|
||||
'800': '#42389d',
|
||||
'900': '#362f78',
|
||||
},
|
||||
purple: {
|
||||
'50': '#f6f5ff',
|
||||
'100': '#edebfe',
|
||||
'200': '#dcd7fe',
|
||||
'300': '#cabffd',
|
||||
'400': '#ac94fa',
|
||||
'500': '#9061f9',
|
||||
'600': '#7e3af2',
|
||||
'700': '#6c2bd9',
|
||||
'800': '#5521b5',
|
||||
'900': '#4a1d96',
|
||||
},
|
||||
pink: {
|
||||
'50': '#fdf2f8',
|
||||
'100': '#fce8f3',
|
||||
'200': '#fad1e8',
|
||||
'300': '#f8b4d9',
|
||||
'400': '#f17eb8',
|
||||
'500': '#e74694',
|
||||
'600': '#d61f69',
|
||||
'700': '#bf125d',
|
||||
'800': '#99154b',
|
||||
'900': '#751a3d',
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundOpacity = (theme) => ({
|
||||
'10': '0.1',
|
||||
...theme('opacity'),
|
||||
})
|
||||
|
||||
const maxHeight = (theme) => ({
|
||||
'0': '0',
|
||||
xl: '36rem',
|
||||
...theme('spacing'),
|
||||
})
|
||||
|
||||
const windmillConfig = {
|
||||
darkMode: 'class',
|
||||
purge: {
|
||||
content: [
|
||||
'node_modules/@supabase/ui/theme/defaultTheme.js',
|
||||
// 'node_modules/@supabase/ui/dist/index.js',
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
colors,
|
||||
backgroundOpacity,
|
||||
maxHeight,
|
||||
},
|
||||
variants: {
|
||||
backgroundOpacity: ['responsive', 'hover', 'focus', 'dark'],
|
||||
backgroundColor: ['responsive', 'hover', 'focus', 'active', 'odd', 'dark'],
|
||||
display: ['responsive', 'dark'],
|
||||
textColor: [
|
||||
'responsive',
|
||||
'focus',
|
||||
'focus-within',
|
||||
'hover',
|
||||
'active',
|
||||
'dark',
|
||||
],
|
||||
placeholderColor: ['responsive', 'focus', 'dark'],
|
||||
borderColor: ['responsive', 'hover', 'focus', 'dark'],
|
||||
divideColor: ['responsive', 'dark'],
|
||||
boxShadow: ['responsive', 'hover', 'focus', 'dark'],
|
||||
margin: ['responsive', 'last'],
|
||||
},
|
||||
plugins: [customFormsPlugin],
|
||||
}
|
||||
|
||||
function arrayMergeFn(destinationArray, sourceArray) {
|
||||
return destinationArray.concat(sourceArray).reduce((acc, cur) => {
|
||||
if (acc.includes(cur)) return acc
|
||||
return [...acc, cur]
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Windmill and Tailwind CSS configurations
|
||||
* @param {object} tailwindConfig - Tailwind config object
|
||||
* @return {object} new config object
|
||||
*/
|
||||
export function wrapper(tailwindConfig) {
|
||||
let purge
|
||||
if (Array.isArray(tailwindConfig.purge)) {
|
||||
purge = {
|
||||
content: tailwindConfig.purge,
|
||||
}
|
||||
} else {
|
||||
purge = tailwindConfig.purge
|
||||
}
|
||||
return deepMerge({ ...tailwindConfig, purge }, windmillConfig, {
|
||||
arrayMerge: arrayMergeFn,
|
||||
})
|
||||
}
|
||||
|
||||
// module.exports = wrapper
|
||||
@@ -0,0 +1,90 @@
|
||||
module.exports = {
|
||||
brand: {
|
||||
brand1: 'hsla(153, 80%, 99%, 1)',
|
||||
brand2: 'hsla(148, 88%, 97%, 1)',
|
||||
brand3: 'hsla(148, 76%, 95%, 1)',
|
||||
brand4: 'hsla(148, 68%, 92%, 1)',
|
||||
brand5: 'hsla(148, 59%, 88%, 1)',
|
||||
brand6: 'hsla(149, 43%, 82%, 1)',
|
||||
brand7: 'hsla(149, 43%, 69%, 1)',
|
||||
brand8: 'hsla(154, 55%, 45%, 1)',
|
||||
brand9: 'hsla(153, 60%, 53%, 1)',
|
||||
brand10: 'hsla(153, 50%, 50%, 1)',
|
||||
brand11: 'hsla(153, 50%, 34%, 1)',
|
||||
brand12: 'hsla(153, 40%, 12%, 1)',
|
||||
},
|
||||
brandDark: {
|
||||
brand1: 'hsla(153, 75%, 6%, 1)',
|
||||
brand2: 'hsla(153, 73%, 7%, 1)',
|
||||
brand3: 'hsla(154, 69%, 9%, 1)',
|
||||
brand4: 'hsla(154, 67%, 11%, 1)',
|
||||
brand5: 'hsla(154, 66%, 13%, 1)',
|
||||
brand6: 'hsla(154, 64%, 17%, 1)',
|
||||
brand7: 'hsla(154, 62%, 22%, 1)',
|
||||
brand8: 'hsla(153, 60%, 28%, 1)',
|
||||
brand9: 'hsla(153, 60%, 53%, 1)',
|
||||
brand10: 'hsla(153, 60%, 70%, 1)',
|
||||
brand11: 'hsla(153, 60%, 50%, 1)',
|
||||
brand12: 'hsla(153, 60%, 95%, 1)',
|
||||
},
|
||||
contrast: {
|
||||
'brand-hiContrast': 'var(--colors-brand-9)',
|
||||
'brand-loContrast': 'var(--colors-brand-4)',
|
||||
},
|
||||
scale: {
|
||||
scale1: 'var(--colors-slate1)',
|
||||
scale2: 'var(--colors-slate2)',
|
||||
scale3: 'var(--colors-slate3)',
|
||||
scale4: 'var(--colors-slate4)',
|
||||
scale5: 'var(--colors-slate5)',
|
||||
scale6: 'var(--colors-slate6)',
|
||||
scale7: 'var(--colors-slate7)',
|
||||
scale8: 'var(--colors-slate8)',
|
||||
scale9: 'var(--colors-slate9)',
|
||||
scale10: 'var(--colors-slate10)',
|
||||
scale11: 'var(--colors-slate11)',
|
||||
scale12: 'var(--colors-slate12)',
|
||||
},
|
||||
scaleA: {
|
||||
scaleA1: 'var(--colors-slatea1)',
|
||||
scaleA2: 'var(--colors-slatea2)',
|
||||
scaleA3: 'var(--colors-slatea3)',
|
||||
scaleA4: 'var(--colors-slatea4)',
|
||||
scaleA5: 'var(--colors-slatea5)',
|
||||
scaleA6: 'var(--colors-slatea6)',
|
||||
scaleA7: 'var(--colors-slatea7)',
|
||||
scaleA8: 'var(--colors-slatea8)',
|
||||
scaleA9: 'var(--colors-slatea9)',
|
||||
scaleA10: 'var(--colors-slatea10)',
|
||||
scaleA11: 'var(--colors-slatea11)',
|
||||
scaleA12: 'var(--colors-slatea12)',
|
||||
},
|
||||
scaleDark: {
|
||||
scale1: 'var(--colors-gray1)',
|
||||
scale2: 'var(--colors-gray2)',
|
||||
scale3: 'var(--colors-gray3)',
|
||||
scale4: 'var(--colors-gray4)',
|
||||
scale5: 'var(--colors-gray5)',
|
||||
scale6: 'var(--colors-gray6)',
|
||||
scale7: 'var(--colors-gray7)',
|
||||
scale8: 'var(--colors-gray8)',
|
||||
scale9: 'var(--colors-gray9)',
|
||||
scale10: 'var(--colors-gray10)',
|
||||
scale11: 'var(--colors-gray11)',
|
||||
scale12: 'var(--colors-gray12)',
|
||||
},
|
||||
scaleADark: {
|
||||
scaleA1: 'var(--colors-graya1)',
|
||||
scaleA2: 'var(--colors-graya2)',
|
||||
scaleA3: 'var(--colors-graya3)',
|
||||
scaleA4: 'var(--colors-graya4)',
|
||||
scaleA5: 'var(--colors-graya5)',
|
||||
scaleA6: 'var(--colors-graya6)',
|
||||
scaleA7: 'var(--colors-graya7)',
|
||||
scaleA8: 'var(--colors-graya8)',
|
||||
scaleA9: 'var(--colors-graya9)',
|
||||
scaleA10: 'var(--colors-graya10)',
|
||||
scaleA11: 'var(--colors-graya11)',
|
||||
scaleA12: 'var(--colors-graya12)',
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
## Run storybook locally
|
||||
|
||||
Supabase UI uses storybook to develop and organise components.
|
||||
They can be viewed locally in the Storybook docs explorer
|
||||
|
||||
make sure you are in the supabase-ui folder
|
||||
|
||||
```cli
|
||||
cd supabase-ui
|
||||
```
|
||||
|
||||
install dependencies
|
||||
|
||||
> :warning: **This project currently won't work using the latest node version. Internally we are using node 17.**
|
||||
|
||||
```cli
|
||||
npm install
|
||||
```
|
||||
|
||||
run storybook
|
||||
|
||||
```cli
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
Storybook runs by default on `http://localhost:6006/`
|
||||
|
||||
## Local Development
|
||||
|
||||
If you want to test Supabase UI components locally, in context in another project locally, then you will need to `npm link` the supabase-ui project locally.
|
||||
|
||||
Follow these instructions here ->
|
||||
[NPM Linking and Unlinking instructions](https://dev.to/erinbush/npm-linking-and-unlinking-2h1g)
|
||||
|
||||
### Common issues
|
||||
|
||||
_A common issue found with local testing is multiple versions of react running._
|
||||
|
||||
You may need to npm-link the react node module in the target app you want to locally test the library in. Then use that version of react inside the library, and then npm-link the library so the target app can use the library with just the 1 version of react.
|
||||
|
||||
Step by step:
|
||||
|
||||
• run npm link in /your-app/node_modules/react. This should make the React's global link.
|
||||
|
||||
• run npm link react in /supabase/ui. This should make the library use the application’s React copy.
|
||||
|
||||
• run npm link @supabase/ui in /your-app
|
||||
|
||||
## Icon generation script
|
||||
|
||||
There is a script that generates new icon files, and sets them up for individual export.
|
||||
|
||||
```cli
|
||||
npm run build:icons
|
||||
```
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,288 @@
|
||||
export default {
|
||||
IconActivity: 'src/components/Icon/icons/IconActivity/index.tsx',
|
||||
IconAirplay: 'src/components/Icon/icons/IconAirplay/index.tsx',
|
||||
IconAlertCircle: 'src/components/Icon/icons/IconAlertCircle/index.tsx',
|
||||
IconAlertOctagon: 'src/components/Icon/icons/IconAlertOctagon/index.tsx',
|
||||
IconAlertTriangle: 'src/components/Icon/icons/IconAlertTriangle/index.tsx',
|
||||
IconAlignCenter: 'src/components/Icon/icons/IconAlignCenter/index.tsx',
|
||||
IconAlignJustify: 'src/components/Icon/icons/IconAlignJustify/index.tsx',
|
||||
IconAlignLeft: 'src/components/Icon/icons/IconAlignLeft/index.tsx',
|
||||
IconAlignRight: 'src/components/Icon/icons/IconAlignRight/index.tsx',
|
||||
IconAnchor: 'src/components/Icon/icons/IconAnchor/index.tsx',
|
||||
IconAperture: 'src/components/Icon/icons/IconAperture/index.tsx',
|
||||
IconArchive: 'src/components/Icon/icons/IconArchive/index.tsx',
|
||||
IconArrowDownCircle: 'src/components/Icon/icons/IconArrowDownCircle/index.tsx',
|
||||
IconArrowDownLeft: 'src/components/Icon/icons/IconArrowDownLeft/index.tsx',
|
||||
IconArrowDownRight: 'src/components/Icon/icons/IconArrowDownRight/index.tsx',
|
||||
IconArrowDown: 'src/components/Icon/icons/IconArrowDown/index.tsx',
|
||||
IconArrowLeftCircle: 'src/components/Icon/icons/IconArrowLeftCircle/index.tsx',
|
||||
IconArrowLeft: 'src/components/Icon/icons/IconArrowLeft/index.tsx',
|
||||
IconArrowRightCircle: 'src/components/Icon/icons/IconArrowRightCircle/index.tsx',
|
||||
IconArrowRight: 'src/components/Icon/icons/IconArrowRight/index.tsx',
|
||||
IconArrowUpCircle: 'src/components/Icon/icons/IconArrowUpCircle/index.tsx',
|
||||
IconArrowUpLeft: 'src/components/Icon/icons/IconArrowUpLeft/index.tsx',
|
||||
IconArrowUpRight: 'src/components/Icon/icons/IconArrowUpRight/index.tsx',
|
||||
IconArrowUp: 'src/components/Icon/icons/IconArrowUp/index.tsx',
|
||||
IconAtSign: 'src/components/Icon/icons/IconAtSign/index.tsx',
|
||||
IconAward: 'src/components/Icon/icons/IconAward/index.tsx',
|
||||
IconBarChart2: 'src/components/Icon/icons/IconBarChart2/index.tsx',
|
||||
IconBarChart: 'src/components/Icon/icons/IconBarChart/index.tsx',
|
||||
IconBatteryCharging: 'src/components/Icon/icons/IconBatteryCharging/index.tsx',
|
||||
IconBattery: 'src/components/Icon/icons/IconBattery/index.tsx',
|
||||
IconBellOff: 'src/components/Icon/icons/IconBellOff/index.tsx',
|
||||
IconBell: 'src/components/Icon/icons/IconBell/index.tsx',
|
||||
IconBluetooth: 'src/components/Icon/icons/IconBluetooth/index.tsx',
|
||||
IconBold: 'src/components/Icon/icons/IconBold/index.tsx',
|
||||
IconBookOpen: 'src/components/Icon/icons/IconBookOpen/index.tsx',
|
||||
IconBook: 'src/components/Icon/icons/IconBook/index.tsx',
|
||||
IconBookmark: 'src/components/Icon/icons/IconBookmark/index.tsx',
|
||||
IconBox: 'src/components/Icon/icons/IconBox/index.tsx',
|
||||
IconBriefcase: 'src/components/Icon/icons/IconBriefcase/index.tsx',
|
||||
IconCalendar: 'src/components/Icon/icons/IconCalendar/index.tsx',
|
||||
IconCameraOff: 'src/components/Icon/icons/IconCameraOff/index.tsx',
|
||||
IconCamera: 'src/components/Icon/icons/IconCamera/index.tsx',
|
||||
IconCast: 'src/components/Icon/icons/IconCast/index.tsx',
|
||||
IconCheckCircle: 'src/components/Icon/icons/IconCheckCircle/index.tsx',
|
||||
IconCheckSquare: 'src/components/Icon/icons/IconCheckSquare/index.tsx',
|
||||
IconCheck: 'src/components/Icon/icons/IconCheck/index.tsx',
|
||||
IconChevronDown: 'src/components/Icon/icons/IconChevronDown/index.tsx',
|
||||
IconChevronLeft: 'src/components/Icon/icons/IconChevronLeft/index.tsx',
|
||||
IconChevronRight: 'src/components/Icon/icons/IconChevronRight/index.tsx',
|
||||
IconChevronUp: 'src/components/Icon/icons/IconChevronUp/index.tsx',
|
||||
IconChevronsDown: 'src/components/Icon/icons/IconChevronsDown/index.tsx',
|
||||
IconChevronsLeft: 'src/components/Icon/icons/IconChevronsLeft/index.tsx',
|
||||
IconChevronsRight: 'src/components/Icon/icons/IconChevronsRight/index.tsx',
|
||||
IconChevronsUp: 'src/components/Icon/icons/IconChevronsUp/index.tsx',
|
||||
IconChrome: 'src/components/Icon/icons/IconChrome/index.tsx',
|
||||
IconCircle: 'src/components/Icon/icons/IconCircle/index.tsx',
|
||||
IconClipboard: 'src/components/Icon/icons/IconClipboard/index.tsx',
|
||||
IconClock: 'src/components/Icon/icons/IconClock/index.tsx',
|
||||
IconCloudDrizzle: 'src/components/Icon/icons/IconCloudDrizzle/index.tsx',
|
||||
IconCloudLightning: 'src/components/Icon/icons/IconCloudLightning/index.tsx',
|
||||
IconCloudOff: 'src/components/Icon/icons/IconCloudOff/index.tsx',
|
||||
IconCloudRain: 'src/components/Icon/icons/IconCloudRain/index.tsx',
|
||||
IconCloudSnow: 'src/components/Icon/icons/IconCloudSnow/index.tsx',
|
||||
IconCloud: 'src/components/Icon/icons/IconCloud/index.tsx',
|
||||
IconCode: 'src/components/Icon/icons/IconCode/index.tsx',
|
||||
IconCodepen: 'src/components/Icon/icons/IconCodepen/index.tsx',
|
||||
IconCodesandbox: 'src/components/Icon/icons/IconCodesandbox/index.tsx',
|
||||
IconCoffee: 'src/components/Icon/icons/IconCoffee/index.tsx',
|
||||
IconColumns: 'src/components/Icon/icons/IconColumns/index.tsx',
|
||||
IconCommand: 'src/components/Icon/icons/IconCommand/index.tsx',
|
||||
IconCompass: 'src/components/Icon/icons/IconCompass/index.tsx',
|
||||
IconCopy: 'src/components/Icon/icons/IconCopy/index.tsx',
|
||||
IconCornerDownLeft: 'src/components/Icon/icons/IconCornerDownLeft/index.tsx',
|
||||
IconCornerDownRight: 'src/components/Icon/icons/IconCornerDownRight/index.tsx',
|
||||
IconCornerLeftDown: 'src/components/Icon/icons/IconCornerLeftDown/index.tsx',
|
||||
IconCornerLeftUp: 'src/components/Icon/icons/IconCornerLeftUp/index.tsx',
|
||||
IconCornerRightDown: 'src/components/Icon/icons/IconCornerRightDown/index.tsx',
|
||||
IconCornerRightUp: 'src/components/Icon/icons/IconCornerRightUp/index.tsx',
|
||||
IconCornerUpLeft: 'src/components/Icon/icons/IconCornerUpLeft/index.tsx',
|
||||
IconCornerUpRight: 'src/components/Icon/icons/IconCornerUpRight/index.tsx',
|
||||
IconCpu: 'src/components/Icon/icons/IconCpu/index.tsx',
|
||||
IconCreditCard: 'src/components/Icon/icons/IconCreditCard/index.tsx',
|
||||
IconCrop: 'src/components/Icon/icons/IconCrop/index.tsx',
|
||||
IconCrosshair: 'src/components/Icon/icons/IconCrosshair/index.tsx',
|
||||
IconDatabase: 'src/components/Icon/icons/IconDatabase/index.tsx',
|
||||
IconDelete: 'src/components/Icon/icons/IconDelete/index.tsx',
|
||||
IconDisc: 'src/components/Icon/icons/IconDisc/index.tsx',
|
||||
IconDivideCircle: 'src/components/Icon/icons/IconDivideCircle/index.tsx',
|
||||
IconDivideSquare: 'src/components/Icon/icons/IconDivideSquare/index.tsx',
|
||||
IconDivide: 'src/components/Icon/icons/IconDivide/index.tsx',
|
||||
IconDollarSign: 'src/components/Icon/icons/IconDollarSign/index.tsx',
|
||||
IconDownloadCloud: 'src/components/Icon/icons/IconDownloadCloud/index.tsx',
|
||||
IconDownload: 'src/components/Icon/icons/IconDownload/index.tsx',
|
||||
IconDribbble: 'src/components/Icon/icons/IconDribbble/index.tsx',
|
||||
IconDroplet: 'src/components/Icon/icons/IconDroplet/index.tsx',
|
||||
IconEdit2: 'src/components/Icon/icons/IconEdit2/index.tsx',
|
||||
IconEdit3: 'src/components/Icon/icons/IconEdit3/index.tsx',
|
||||
IconEdit: 'src/components/Icon/icons/IconEdit/index.tsx',
|
||||
IconExternalLink: 'src/components/Icon/icons/IconExternalLink/index.tsx',
|
||||
IconEyeOff: 'src/components/Icon/icons/IconEyeOff/index.tsx',
|
||||
IconEye: 'src/components/Icon/icons/IconEye/index.tsx',
|
||||
IconFacebook: 'src/components/Icon/icons/IconFacebook/index.tsx',
|
||||
IconFastForward: 'src/components/Icon/icons/IconFastForward/index.tsx',
|
||||
IconFeather: 'src/components/Icon/icons/IconFeather/index.tsx',
|
||||
IconFigma: 'src/components/Icon/icons/IconFigma/index.tsx',
|
||||
IconFileMinus: 'src/components/Icon/icons/IconFileMinus/index.tsx',
|
||||
IconFilePlus: 'src/components/Icon/icons/IconFilePlus/index.tsx',
|
||||
IconFileText: 'src/components/Icon/icons/IconFileText/index.tsx',
|
||||
IconFile: 'src/components/Icon/icons/IconFile/index.tsx',
|
||||
IconFilm: 'src/components/Icon/icons/IconFilm/index.tsx',
|
||||
IconFilter: 'src/components/Icon/icons/IconFilter/index.tsx',
|
||||
IconFlag: 'src/components/Icon/icons/IconFlag/index.tsx',
|
||||
IconFolderMinus: 'src/components/Icon/icons/IconFolderMinus/index.tsx',
|
||||
IconFolderPlus: 'src/components/Icon/icons/IconFolderPlus/index.tsx',
|
||||
IconFolder: 'src/components/Icon/icons/IconFolder/index.tsx',
|
||||
IconFramer: 'src/components/Icon/icons/IconFramer/index.tsx',
|
||||
IconFrown: 'src/components/Icon/icons/IconFrown/index.tsx',
|
||||
IconGift: 'src/components/Icon/icons/IconGift/index.tsx',
|
||||
IconGitBranch: 'src/components/Icon/icons/IconGitBranch/index.tsx',
|
||||
IconGitCommit: 'src/components/Icon/icons/IconGitCommit/index.tsx',
|
||||
IconGitMerge: 'src/components/Icon/icons/IconGitMerge/index.tsx',
|
||||
IconGitPullRequest: 'src/components/Icon/icons/IconGitPullRequest/index.tsx',
|
||||
IconGitHub: 'src/components/Icon/icons/IconGitHub/index.tsx',
|
||||
IconGitlab: 'src/components/Icon/icons/IconGitlab/index.tsx',
|
||||
IconGlobe: 'src/components/Icon/icons/IconGlobe/index.tsx',
|
||||
IconGrid: 'src/components/Icon/icons/IconGrid/index.tsx',
|
||||
IconHardDrive: 'src/components/Icon/icons/IconHardDrive/index.tsx',
|
||||
IconHash: 'src/components/Icon/icons/IconHash/index.tsx',
|
||||
IconHeadphones: 'src/components/Icon/icons/IconHeadphones/index.tsx',
|
||||
IconHeart: 'src/components/Icon/icons/IconHeart/index.tsx',
|
||||
IconHelpCircle: 'src/components/Icon/icons/IconHelpCircle/index.tsx',
|
||||
IconHexagon: 'src/components/Icon/icons/IconHexagon/index.tsx',
|
||||
IconHome: 'src/components/Icon/icons/IconHome/index.tsx',
|
||||
IconImage: 'src/components/Icon/icons/IconImage/index.tsx',
|
||||
IconInbox: 'src/components/Icon/icons/IconInbox/index.tsx',
|
||||
IconInfo: 'src/components/Icon/icons/IconInfo/index.tsx',
|
||||
IconInstagram: 'src/components/Icon/icons/IconInstagram/index.tsx',
|
||||
IconItalic: 'src/components/Icon/icons/IconItalic/index.tsx',
|
||||
IconKey: 'src/components/Icon/icons/IconKey/index.tsx',
|
||||
IconLayers: 'src/components/Icon/icons/IconLayers/index.tsx',
|
||||
IconLayout: 'src/components/Icon/icons/IconLayout/index.tsx',
|
||||
IconLifeBuoy: 'src/components/Icon/icons/IconLifeBuoy/index.tsx',
|
||||
IconLink2: 'src/components/Icon/icons/IconLink2/index.tsx',
|
||||
IconLink: 'src/components/Icon/icons/IconLink/index.tsx',
|
||||
IconLinkedin: 'src/components/Icon/icons/IconLinkedin/index.tsx',
|
||||
IconList: 'src/components/Icon/icons/IconList/index.tsx',
|
||||
IconLoader: 'src/components/Icon/icons/IconLoader/index.tsx',
|
||||
IconLock: 'src/components/Icon/icons/IconLock/index.tsx',
|
||||
IconLogIn: 'src/components/Icon/icons/IconLogIn/index.tsx',
|
||||
IconLogOut: 'src/components/Icon/icons/IconLogOut/index.tsx',
|
||||
IconMail: 'src/components/Icon/icons/IconMail/index.tsx',
|
||||
IconMapPin: 'src/components/Icon/icons/IconMapPin/index.tsx',
|
||||
IconMap: 'src/components/Icon/icons/IconMap/index.tsx',
|
||||
IconMaximize2: 'src/components/Icon/icons/IconMaximize2/index.tsx',
|
||||
IconMaximize: 'src/components/Icon/icons/IconMaximize/index.tsx',
|
||||
IconMeh: 'src/components/Icon/icons/IconMeh/index.tsx',
|
||||
IconMenu: 'src/components/Icon/icons/IconMenu/index.tsx',
|
||||
IconMessageCircle: 'src/components/Icon/icons/IconMessageCircle/index.tsx',
|
||||
IconMessageSquare: 'src/components/Icon/icons/IconMessageSquare/index.tsx',
|
||||
IconMicOff: 'src/components/Icon/icons/IconMicOff/index.tsx',
|
||||
IconMic: 'src/components/Icon/icons/IconMic/index.tsx',
|
||||
IconMinimize2: 'src/components/Icon/icons/IconMinimize2/index.tsx',
|
||||
IconMinimize: 'src/components/Icon/icons/IconMinimize/index.tsx',
|
||||
IconMinusCircle: 'src/components/Icon/icons/IconMinusCircle/index.tsx',
|
||||
IconMinusSquare: 'src/components/Icon/icons/IconMinusSquare/index.tsx',
|
||||
IconMinus: 'src/components/Icon/icons/IconMinus/index.tsx',
|
||||
IconMonitor: 'src/components/Icon/icons/IconMonitor/index.tsx',
|
||||
IconMoon: 'src/components/Icon/icons/IconMoon/index.tsx',
|
||||
IconMoreHorizontal: 'src/components/Icon/icons/IconMoreHorizontal/index.tsx',
|
||||
IconMoreVertical: 'src/components/Icon/icons/IconMoreVertical/index.tsx',
|
||||
IconMousePointer: 'src/components/Icon/icons/IconMousePointer/index.tsx',
|
||||
IconMove: 'src/components/Icon/icons/IconMove/index.tsx',
|
||||
IconMusic: 'src/components/Icon/icons/IconMusic/index.tsx',
|
||||
IconNavigation2: 'src/components/Icon/icons/IconNavigation2/index.tsx',
|
||||
IconNavigation: 'src/components/Icon/icons/IconNavigation/index.tsx',
|
||||
IconOctagon: 'src/components/Icon/icons/IconOctagon/index.tsx',
|
||||
IconPackage: 'src/components/Icon/icons/IconPackage/index.tsx',
|
||||
IconPaperclip: 'src/components/Icon/icons/IconPaperclip/index.tsx',
|
||||
IconPauseCircle: 'src/components/Icon/icons/IconPauseCircle/index.tsx',
|
||||
IconPause: 'src/components/Icon/icons/IconPause/index.tsx',
|
||||
IconPenTool: 'src/components/Icon/icons/IconPenTool/index.tsx',
|
||||
IconPercent: 'src/components/Icon/icons/IconPercent/index.tsx',
|
||||
IconPhoneCall: 'src/components/Icon/icons/IconPhoneCall/index.tsx',
|
||||
IconPhoneForwarded: 'src/components/Icon/icons/IconPhoneForwarded/index.tsx',
|
||||
IconPhoneIncoming: 'src/components/Icon/icons/IconPhoneIncoming/index.tsx',
|
||||
IconPhoneMissed: 'src/components/Icon/icons/IconPhoneMissed/index.tsx',
|
||||
IconPhoneOff: 'src/components/Icon/icons/IconPhoneOff/index.tsx',
|
||||
IconPhoneOutgoing: 'src/components/Icon/icons/IconPhoneOutgoing/index.tsx',
|
||||
IconPhone: 'src/components/Icon/icons/IconPhone/index.tsx',
|
||||
IconPieChart: 'src/components/Icon/icons/IconPieChart/index.tsx',
|
||||
IconPlayCircle: 'src/components/Icon/icons/IconPlayCircle/index.tsx',
|
||||
IconPlay: 'src/components/Icon/icons/IconPlay/index.tsx',
|
||||
IconPlusCircle: 'src/components/Icon/icons/IconPlusCircle/index.tsx',
|
||||
IconPlusSquare: 'src/components/Icon/icons/IconPlusSquare/index.tsx',
|
||||
IconPlus: 'src/components/Icon/icons/IconPlus/index.tsx',
|
||||
IconPocket: 'src/components/Icon/icons/IconPocket/index.tsx',
|
||||
IconPower: 'src/components/Icon/icons/IconPower/index.tsx',
|
||||
IconPrinter: 'src/components/Icon/icons/IconPrinter/index.tsx',
|
||||
IconRadio: 'src/components/Icon/icons/IconRadio/index.tsx',
|
||||
IconRefreshCcw: 'src/components/Icon/icons/IconRefreshCcw/index.tsx',
|
||||
IconRefreshCw: 'src/components/Icon/icons/IconRefreshCw/index.tsx',
|
||||
IconRepeat: 'src/components/Icon/icons/IconRepeat/index.tsx',
|
||||
IconRewind: 'src/components/Icon/icons/IconRewind/index.tsx',
|
||||
IconRotateCcw: 'src/components/Icon/icons/IconRotateCcw/index.tsx',
|
||||
IconRotateCw: 'src/components/Icon/icons/IconRotateCw/index.tsx',
|
||||
IconRss: 'src/components/Icon/icons/IconRss/index.tsx',
|
||||
IconSave: 'src/components/Icon/icons/IconSave/index.tsx',
|
||||
IconScissors: 'src/components/Icon/icons/IconScissors/index.tsx',
|
||||
IconSearch: 'src/components/Icon/icons/IconSearch/index.tsx',
|
||||
IconSend: 'src/components/Icon/icons/IconSend/index.tsx',
|
||||
IconServer: 'src/components/Icon/icons/IconServer/index.tsx',
|
||||
IconSettings: 'src/components/Icon/icons/IconSettings/index.tsx',
|
||||
IconShare2: 'src/components/Icon/icons/IconShare2/index.tsx',
|
||||
IconShare: 'src/components/Icon/icons/IconShare/index.tsx',
|
||||
IconShieldOff: 'src/components/Icon/icons/IconShieldOff/index.tsx',
|
||||
IconShield: 'src/components/Icon/icons/IconShield/index.tsx',
|
||||
IconShoppingBag: 'src/components/Icon/icons/IconShoppingBag/index.tsx',
|
||||
IconShoppingCart: 'src/components/Icon/icons/IconShoppingCart/index.tsx',
|
||||
IconShuffle: 'src/components/Icon/icons/IconShuffle/index.tsx',
|
||||
IconSidebar: 'src/components/Icon/icons/IconSidebar/index.tsx',
|
||||
IconSkipBack: 'src/components/Icon/icons/IconSkipBack/index.tsx',
|
||||
IconSkipForward: 'src/components/Icon/icons/IconSkipForward/index.tsx',
|
||||
IconSlack: 'src/components/Icon/icons/IconSlack/index.tsx',
|
||||
IconSlash: 'src/components/Icon/icons/IconSlash/index.tsx',
|
||||
IconSliders: 'src/components/Icon/icons/IconSliders/index.tsx',
|
||||
IconSmartphone: 'src/components/Icon/icons/IconSmartphone/index.tsx',
|
||||
IconSmile: 'src/components/Icon/icons/IconSmile/index.tsx',
|
||||
IconSpeaker: 'src/components/Icon/icons/IconSpeaker/index.tsx',
|
||||
IconSquare: 'src/components/Icon/icons/IconSquare/index.tsx',
|
||||
IconStar: 'src/components/Icon/icons/IconStar/index.tsx',
|
||||
IconStopCircle: 'src/components/Icon/icons/IconStopCircle/index.tsx',
|
||||
IconSun: 'src/components/Icon/icons/IconSun/index.tsx',
|
||||
IconSunrise: 'src/components/Icon/icons/IconSunrise/index.tsx',
|
||||
IconSunset: 'src/components/Icon/icons/IconSunset/index.tsx',
|
||||
IconTablet: 'src/components/Icon/icons/IconTablet/index.tsx',
|
||||
IconTag: 'src/components/Icon/icons/IconTag/index.tsx',
|
||||
IconTarget: 'src/components/Icon/icons/IconTarget/index.tsx',
|
||||
IconTerminal: 'src/components/Icon/icons/IconTerminal/index.tsx',
|
||||
IconThermometer: 'src/components/Icon/icons/IconThermometer/index.tsx',
|
||||
IconThumbsDown: 'src/components/Icon/icons/IconThumbsDown/index.tsx',
|
||||
IconThumbsUp: 'src/components/Icon/icons/IconThumbsUp/index.tsx',
|
||||
IconToggleLeft: 'src/components/Icon/icons/IconToggleLeft/index.tsx',
|
||||
IconToggleRight: 'src/components/Icon/icons/IconToggleRight/index.tsx',
|
||||
IconTool: 'src/components/Icon/icons/IconTool/index.tsx',
|
||||
IconTrash2: 'src/components/Icon/icons/IconTrash2/index.tsx',
|
||||
IconTrash: 'src/components/Icon/icons/IconTrash/index.tsx',
|
||||
IconTrello: 'src/components/Icon/icons/IconTrello/index.tsx',
|
||||
IconTrendingDown: 'src/components/Icon/icons/IconTrendingDown/index.tsx',
|
||||
IconTrendingUp: 'src/components/Icon/icons/IconTrendingUp/index.tsx',
|
||||
IconTriangle: 'src/components/Icon/icons/IconTriangle/index.tsx',
|
||||
IconTruck: 'src/components/Icon/icons/IconTruck/index.tsx',
|
||||
IconTv: 'src/components/Icon/icons/IconTv/index.tsx',
|
||||
IconTwitch: 'src/components/Icon/icons/IconTwitch/index.tsx',
|
||||
IconTwitter: 'src/components/Icon/icons/IconTwitter/index.tsx',
|
||||
IconType: 'src/components/Icon/icons/IconType/index.tsx',
|
||||
IconUmbrella: 'src/components/Icon/icons/IconUmbrella/index.tsx',
|
||||
IconUnderline: 'src/components/Icon/icons/IconUnderline/index.tsx',
|
||||
IconUnlock: 'src/components/Icon/icons/IconUnlock/index.tsx',
|
||||
IconUploadCloud: 'src/components/Icon/icons/IconUploadCloud/index.tsx',
|
||||
IconUpload: 'src/components/Icon/icons/IconUpload/index.tsx',
|
||||
IconUserCheck: 'src/components/Icon/icons/IconUserCheck/index.tsx',
|
||||
IconUserMinus: 'src/components/Icon/icons/IconUserMinus/index.tsx',
|
||||
IconUserPlus: 'src/components/Icon/icons/IconUserPlus/index.tsx',
|
||||
IconUserX: 'src/components/Icon/icons/IconUserX/index.tsx',
|
||||
IconUser: 'src/components/Icon/icons/IconUser/index.tsx',
|
||||
IconUsers: 'src/components/Icon/icons/IconUsers/index.tsx',
|
||||
IconVideoOff: 'src/components/Icon/icons/IconVideoOff/index.tsx',
|
||||
IconVideo: 'src/components/Icon/icons/IconVideo/index.tsx',
|
||||
IconVoicemail: 'src/components/Icon/icons/IconVoicemail/index.tsx',
|
||||
IconVolume1: 'src/components/Icon/icons/IconVolume1/index.tsx',
|
||||
IconVolume2: 'src/components/Icon/icons/IconVolume2/index.tsx',
|
||||
IconVolumeX: 'src/components/Icon/icons/IconVolumeX/index.tsx',
|
||||
IconVolume: 'src/components/Icon/icons/IconVolume/index.tsx',
|
||||
IconWatch: 'src/components/Icon/icons/IconWatch/index.tsx',
|
||||
IconWifiOff: 'src/components/Icon/icons/IconWifiOff/index.tsx',
|
||||
IconWifi: 'src/components/Icon/icons/IconWifi/index.tsx',
|
||||
IconWind: 'src/components/Icon/icons/IconWind/index.tsx',
|
||||
IconXCircle: 'src/components/Icon/icons/IconXCircle/index.tsx',
|
||||
IconXOctagon: 'src/components/Icon/icons/IconXOctagon/index.tsx',
|
||||
IconXSquare: 'src/components/Icon/icons/IconXSquare/index.tsx',
|
||||
IconX: 'src/components/Icon/icons/IconX/index.tsx',
|
||||
IconYoutube: 'src/components/Icon/icons/IconYoutube/index.tsx',
|
||||
IconZapOff: 'src/components/Icon/icons/IconZapOff/index.tsx',
|
||||
IconZap: 'src/components/Icon/icons/IconZap/index.tsx',
|
||||
IconZoomIn: 'src/components/Icon/icons/IconZoomIn/index.tsx',
|
||||
IconZoomOut: 'src/components/Icon/icons/IconZoomOut/index.tsx'
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,83 @@
|
||||
const fs = require('fs')
|
||||
const util = require('util')
|
||||
var icons = require('./icons.json') //with path
|
||||
|
||||
let iconList = {}
|
||||
let importList = []
|
||||
|
||||
//
|
||||
// create icon .tsx files
|
||||
//
|
||||
|
||||
Object.values(icons).map((icon) => {
|
||||
iconList[
|
||||
`Icon${icon.name}`
|
||||
] = `src/components/Icon/icons/Icon${icon.name}/index.tsx`
|
||||
importList.push(`export * as Icon${icon.name} from './Icon${icon.name}'`)
|
||||
|
||||
var dir = `src/components/Icon/icons/Icon${icon.name}`
|
||||
|
||||
fs.mkdir(dir, { recursive: true }, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
|
||||
fs.writeFile(
|
||||
`${dir}/index.tsx`,
|
||||
`export { default as Icon${icon.name} } from './Icon${icon.name}'`,
|
||||
function (err) {
|
||||
if (err) return console.log(err)
|
||||
}
|
||||
)
|
||||
|
||||
fs.writeFile(
|
||||
`${dir}/Icon${icon.name}.tsx`,
|
||||
`// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from '${icon.path}'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function Icon${icon.name}(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default Icon${icon.name}
|
||||
`,
|
||||
function (err) {
|
||||
if (err) return console.log(err)
|
||||
// console.log(`${icon.name} > ${icon.path}`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
//
|
||||
// create export file for icons
|
||||
//
|
||||
|
||||
fs.writeFile(
|
||||
`src/components/Icon/icons/index.tsx`,
|
||||
`// export icons
|
||||
${importList
|
||||
.map((icon) => {
|
||||
return `${icon}\n`
|
||||
})
|
||||
.join('')}`,
|
||||
function (err) {
|
||||
if (err) return console.log(err)
|
||||
// console.log(`wrote file for rollup`)
|
||||
}
|
||||
)
|
||||
|
||||
//
|
||||
// create input destinations for rollup
|
||||
//
|
||||
|
||||
fs.writeFile(
|
||||
`internals/icons.js`,
|
||||
`export default ${util.inspect(iconList)}`,
|
||||
function (err) {
|
||||
if (err) return console.log(err)
|
||||
// console.log(`wrote file for rollup`)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"name": "ui",
|
||||
"version": "0.0.0",
|
||||
"main": "/src/index.tsx",
|
||||
"types": "/sric/index.tsx",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production rollup -c",
|
||||
"build-watch": "NODE_ENV=production rollup -c -w",
|
||||
"test": "react-scripts test",
|
||||
"storybook": "start-storybook -s ./src/static -p 6006",
|
||||
"build-storybook": "build-storybook",
|
||||
"build:icons": "node --experimental-modules internals/populate-icons.js",
|
||||
"typedoc:json": "typedoc --json spec/spec.json --mode modules --includeDeclarations --excludeExternals"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.0.0",
|
||||
"@mertasan/tailwindcss-variables": "^2.0.1",
|
||||
"@radix-ui/colors": "^0.1.8",
|
||||
"@radix-ui/react-accordion": "^0.1.5",
|
||||
"@radix-ui/react-collapsible": "^0.1.5",
|
||||
"@radix-ui/react-context-menu": "^0.1.0",
|
||||
"@radix-ui/react-dialog": "^0.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.4",
|
||||
"@radix-ui/react-popover": "^0.1.0",
|
||||
"@radix-ui/react-portal": "^0.1.3",
|
||||
"@radix-ui/react-tabs": "^0.1.0",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"formik": "^2.2.9",
|
||||
"lodash": "^4.17.20",
|
||||
"postcss": "^8.4.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"tailwindcss": "^3.0.15",
|
||||
"tailwindcss-radix": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.11.6",
|
||||
"@babel/core": "^7.11.6",
|
||||
"@babel/plugin-transform-runtime": "^7.11.5",
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@rollup/plugin-babel": "^5.2.1",
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"@storybook/addon-actions": "^6.2.2",
|
||||
"@storybook/addon-essentials": "^6.2.2",
|
||||
"@storybook/addon-links": "^6.2.2",
|
||||
"@storybook/react": "^6.2.2",
|
||||
"@supabase/supabase-js": "^1.21.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.0.4",
|
||||
"@testing-library/user-event": "^12.1.7",
|
||||
"@types/lodash": "^4.14.172",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-add-react-displayname": "^0.0.5",
|
||||
"babel-plugin-macros": "^2.8.0",
|
||||
"css-loader": "^5.0.1",
|
||||
"eslint-config-prettier": "^6.12.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-modules-values": "^4.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.1.2",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-hot-toast": "^2.0.0",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"rollup": "^2.28.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-node-externals": "^2.2.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"rollup-plugin-postcss-modules": "^2.0.2",
|
||||
"rollup-plugin-typescript2": "^0.29.0",
|
||||
"storybook-dark-mode": "^1.0.4",
|
||||
"typedoc": "^0.19.1",
|
||||
"typescript": "4.2.2",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "^2.3.2"
|
||||
},
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
"/node_modules/(?!react-feather)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import'),
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
],
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,132 @@
|
||||
import babel from '@rollup/plugin-babel'
|
||||
import external from 'rollup-plugin-peer-deps-external'
|
||||
// import postcss from 'rollup-plugin-postcss-modules'
|
||||
// import postcss from 'rollup-plugin-postcss'
|
||||
import del from 'rollup-plugin-delete'
|
||||
import pkg from './package.json'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
// so JS can be rolled with TS
|
||||
// remove when JS files have been removed
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import copy from 'rollup-plugin-copy'
|
||||
|
||||
import icons from './internals/icons'
|
||||
|
||||
// console.log('Expected Externals', [
|
||||
// ...Object.keys(pkg.dependencies || {}),
|
||||
// ...Object.keys(pkg.peerDependencies || {}),
|
||||
// './src',
|
||||
// ])
|
||||
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx']
|
||||
|
||||
export default [
|
||||
{
|
||||
input: {
|
||||
index: 'src/index.tsx',
|
||||
// avatar: 'src/components/Avatar/index.tsx',
|
||||
button: 'src/components/Button/index.tsx',
|
||||
// typography: 'src/components/Typography/index.tsx',
|
||||
icon: 'src/components/Icon/index.tsx',
|
||||
// image: 'src/components/Image/index.tsx',
|
||||
card: 'src/components/Card/index.tsx',
|
||||
badge: 'src/components/Badge/index.tsx',
|
||||
alert: 'src/components/Alert/index.tsx',
|
||||
accordion: 'src/components/Accordion/index.tsx',
|
||||
tabs: 'src/components/Tabs/index.tsx',
|
||||
menu: 'src/components/Menu/index.tsx',
|
||||
modal: 'src/components/Modal/index.tsx',
|
||||
modal: 'src/components/Popover/index.tsx',
|
||||
sidepanel: 'src/components/SidePanel/index.tsx',
|
||||
dropdown: 'src/components/Dropdown/index.tsx',
|
||||
form: 'src/components/Form/index.tsx',
|
||||
contextmenu: 'src/components/ContextMenu/index.tsx',
|
||||
space: 'src/components/Space/index.tsx',
|
||||
loading: 'src/components/Loading/index.tsx',
|
||||
divider: 'src/components/Divider/index.tsx',
|
||||
select: 'src/components/Select/index.tsx',
|
||||
listbox: 'src/components/Listbox/index.tsx',
|
||||
checkbox: 'src/components/Checkbox/index.tsx',
|
||||
input: 'src/components/Input/index.tsx',
|
||||
radio: 'src/components/Radio/index.tsx',
|
||||
toggle: 'src/components/Toggle/index.tsx',
|
||||
// upload: 'src/components/Upload/index.tsx',
|
||||
// auth: 'src/components/Auth/index.tsx',
|
||||
// auth: 'src/components/ThemeProvider/index.tsx',
|
||||
...icons,
|
||||
},
|
||||
external: [
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
...Object.keys(pkg.peerDependencies || {}),
|
||||
'./src',
|
||||
],
|
||||
output: [
|
||||
{
|
||||
dir: 'dist/cjs',
|
||||
format: 'cjs',
|
||||
preserveModules: true,
|
||||
preserveModulesRoot: 'src',
|
||||
exports: 'named',
|
||||
},
|
||||
{
|
||||
dir: 'dist/esm',
|
||||
format: 'es',
|
||||
preserveModules: true,
|
||||
preserveModulesRoot: 'src',
|
||||
exports: 'named',
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
external(),
|
||||
typescript(),
|
||||
// so JS can be rolled with TS
|
||||
// remove when JS files have been removed
|
||||
nodeResolve({
|
||||
ignoreGlobal: false,
|
||||
include: ['node_modules/**'],
|
||||
extensions,
|
||||
// skip: keys(EXTERNALS), // <<-- skip: ['react', 'react-dom']
|
||||
}),
|
||||
commonjs({
|
||||
ignoreGlobal: false,
|
||||
include: 'node_modules/**',
|
||||
}),
|
||||
// postcss({
|
||||
// // plugins: require('./postcss.config').plugins,
|
||||
// // plugins: [
|
||||
// // require('postcss-import'),
|
||||
// // require('tailwindcss'),
|
||||
// // require('autoprefixer'),
|
||||
// // ],
|
||||
// // modules: true,
|
||||
// minimize: true,
|
||||
// sourceMap: false,
|
||||
// // extract: false,
|
||||
// minimize: true,
|
||||
// // modules: {
|
||||
// // // see generateScopedName options here
|
||||
// // // https://github.com/css-modules/postcss-modules
|
||||
// // generateScopedName: '[local]',
|
||||
// // },
|
||||
// }),
|
||||
babel({
|
||||
babelHelpers: 'runtime',
|
||||
exclude: 'node_modules/**',
|
||||
extensions,
|
||||
}),
|
||||
del({ targets: ['dist/*'] }),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'ui.config.js', dest: 'dist/config' },
|
||||
{ src: 'default-colors.js', dest: 'dist/config' },
|
||||
{
|
||||
src: 'src/lib/theme/defaultTheme.ts',
|
||||
dest: 'dist/config',
|
||||
rename: () => 'default-theme.js',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
]
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,72 @@
|
||||
/* .sbui-accordion-container {
|
||||
@apply flex flex-col rounded-md;
|
||||
}
|
||||
|
||||
.sbui-accordion-container--bordered {
|
||||
@apply border border-solid;
|
||||
@apply bg-white border-gray-200;
|
||||
@apply dark:bg-dark-700 dark:border-darkmode;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item {
|
||||
@apply w-full;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__button {
|
||||
@apply flex justify-between w-full text-left cursor-pointer px-6 py-4 border-0 border-solid font-medium text-base rounded-t-md bg-transparent;
|
||||
|
||||
@apply border-gray-200 text-gray-500 hover:text-gray-600;
|
||||
@apply dark:border-darkmode dark:text-dark-200 dark:hover:text-white;
|
||||
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__button .sbui-typography-text {
|
||||
@apply w-full max-w-none;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__button .sbui-typography-text:last-child {
|
||||
@apply pl-2;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__button .sbui-icon {
|
||||
@apply mt-px;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__button--open .sbui-icon {
|
||||
@apply transform rotate-180;
|
||||
} */
|
||||
/*
|
||||
.sbui-accordion-container--bordered
|
||||
.sbui-accordion-item__button:not(:first-child) {
|
||||
@apply rounded-none border-t border-gray-200 dark:border-darkmode;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__panel {
|
||||
@apply px-6 py-4;
|
||||
} */
|
||||
|
||||
/* .sbui-accordion-item__panel--enter {
|
||||
@apply transition-max-height ease-in-out duration-700 overflow-hidden;
|
||||
}
|
||||
|
||||
.sbui-accordion-item__panel--enterFrom {
|
||||
@apply max-h-0;
|
||||
}
|
||||
|
||||
.sbui-accordion-item__panel--enterTo {
|
||||
@apply max-h-screen;
|
||||
}
|
||||
|
||||
.sbui-accordion-item__panel--leave {
|
||||
@apply transition-max-height ease-in-out duration-300 overflow-hidden;
|
||||
}
|
||||
|
||||
.sbui-accordion-item__panel--leaveFrom {
|
||||
@apply max-h-screen;
|
||||
}
|
||||
|
||||
.sbui-accordion-item__panel--leaveTo {
|
||||
@apply max-h-0;
|
||||
} */
|
||||
@@ -0,0 +1,201 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Accordion } from '.'
|
||||
import { Typography } from '../Typography'
|
||||
import { IconArrowUp } from '../Icon/icons/IconArrowUp'
|
||||
import { Badge } from '../Badge'
|
||||
|
||||
export default {
|
||||
title: 'Displays/Accordion',
|
||||
component: Accordion,
|
||||
}
|
||||
|
||||
export const OneItem = (args: any) => (
|
||||
<Accordion {...args} justified={false}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
export const MultipleItems = (args: any) => (
|
||||
<Accordion {...args} openBehaviour="multiple">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
export const Bordered = (args: any) => (
|
||||
<Accordion {...args}>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'1'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'2'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'3'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Title of the thing
|
||||
</span>
|
||||
}
|
||||
id={'4'}
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
Bordered.args = {
|
||||
type: 'bordered',
|
||||
}
|
||||
|
||||
export const LeftAlignedChevron = (args: any) => (
|
||||
<Accordion {...args} justified={false} chevronAlign="left">
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
First item
|
||||
</span>
|
||||
<Badge>Test badge</Badge>
|
||||
</>
|
||||
}
|
||||
id="first"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item
|
||||
header={
|
||||
<>
|
||||
<span className="text-scale-900 group-hover:text-scale-1200">
|
||||
Second item
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
id="second"
|
||||
>
|
||||
<span className="text-scale-900">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
|
||||
amet labore.
|
||||
</span>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
)
|
||||
|
||||
// export const withDefaultActive = Multiple.bind({})
|
||||
// withDefaultActive.args = {
|
||||
// defaultActiveId: [1],
|
||||
// }
|
||||
|
||||
// export const withIconLeft = Multiple.bind({})
|
||||
// withIconLeft.args = {
|
||||
// iconPosition: 'left',
|
||||
// }
|
||||
|
||||
// export const withCustomIcon = Multiple.bind({})
|
||||
// withCustomIcon.args = {
|
||||
// icon: <IconArrowUp />,
|
||||
// }
|
||||
@@ -0,0 +1,162 @@
|
||||
import React, { createContext, useContext, useState } from 'react'
|
||||
|
||||
import { IconChevronUp } from '../Icon/icons/IconChevronUp'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
import * as RadixAccordion from '@radix-ui/react-accordion'
|
||||
import { IconChevronDown } from '../..'
|
||||
import { Transition } from '@headlessui/react'
|
||||
|
||||
// type ContextValue = Required<
|
||||
// Pick<AccordionProps, 'defaultActiveId' | 'icon' | 'iconPosition'>
|
||||
// > &
|
||||
// Pick<AccordionProps, 'onChange'>
|
||||
|
||||
type Type = 'default' | 'bordered'
|
||||
type Size = 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
type Align = 'left' | 'right'
|
||||
|
||||
interface ContextValue {
|
||||
bordered?: boolean
|
||||
type: Type
|
||||
justified: Boolean
|
||||
chevronAlign: Align
|
||||
// currentItems: string[]
|
||||
}
|
||||
|
||||
const AccordionContext = createContext<ContextValue>({
|
||||
chevronAlign: 'left',
|
||||
justified: true,
|
||||
type: 'default',
|
||||
// currentItems: [],
|
||||
})
|
||||
|
||||
interface AccordionProps {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
defaultActiveId?: (string | number)[]
|
||||
icon?: React.ReactNode
|
||||
iconPosition?: Align
|
||||
bordered: boolean
|
||||
onChange?: (item: string | string[]) => void
|
||||
openBehaviour: 'single' | 'multiple'
|
||||
type: Type
|
||||
size: Size
|
||||
defaultValue?: string | string[] | undefined
|
||||
justified: Boolean
|
||||
chevronAlign: Align
|
||||
}
|
||||
|
||||
function Accordion({
|
||||
children,
|
||||
className,
|
||||
defaultActiveId = [],
|
||||
icon = <IconChevronUp strokeWidth={2} />,
|
||||
iconPosition = 'right',
|
||||
onChange,
|
||||
openBehaviour = 'multiple',
|
||||
type = 'default',
|
||||
// size, // TO DO
|
||||
defaultValue = undefined,
|
||||
justified = true,
|
||||
chevronAlign,
|
||||
}: AccordionProps) {
|
||||
// const [currentItems, setCurrentItems] = useState(defaultValue || [])
|
||||
|
||||
const __styles = styleHandler('accordion')
|
||||
|
||||
let containerClasses = [__styles.variants[type].base]
|
||||
|
||||
if (className) {
|
||||
containerClasses.push(className)
|
||||
}
|
||||
|
||||
// let currentItems = defaultValue || []
|
||||
|
||||
const contextValue = {
|
||||
chevronAlign,
|
||||
justified,
|
||||
type,
|
||||
defaultValue,
|
||||
}
|
||||
|
||||
function handleOnChange(e: string | string[]) {
|
||||
if (onChange) onChange(e)
|
||||
const value = e == typeof String ? e.split(' ') : e
|
||||
// setCurrentItems(e)
|
||||
console.log('about to change state')
|
||||
// currentItems = e
|
||||
// console.log('currentItems', currentItems)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore */}
|
||||
<RadixAccordion.Root
|
||||
type={openBehaviour}
|
||||
onValueChange={handleOnChange}
|
||||
defaultValue={defaultValue}
|
||||
className={containerClasses.join(' ')}
|
||||
children={
|
||||
<AccordionContext.Provider value={{ ...contextValue }}>
|
||||
<div className={containerClasses.join(' ')}>{children}</div>
|
||||
</AccordionContext.Provider>
|
||||
}
|
||||
></RadixAccordion.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
header: React.ReactNode
|
||||
id: string
|
||||
icon?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Item({ children, className, header, id, icon }: ItemProps) {
|
||||
const __styles = styleHandler('accordion')
|
||||
// const [open, setOpen] = useState(false)
|
||||
|
||||
const {
|
||||
type,
|
||||
justified,
|
||||
chevronAlign,
|
||||
// currentItems,
|
||||
// defaultActiveId, iconPosition, onChange
|
||||
} = useContext(AccordionContext)
|
||||
|
||||
let triggerClasses = [__styles.variants[type].trigger]
|
||||
if (justified) triggerClasses.push(__styles.justified)
|
||||
if (className) triggerClasses.push(className)
|
||||
|
||||
let chevronClasses = [
|
||||
__styles.chevron.base,
|
||||
__styles.chevron.align[chevronAlign],
|
||||
]
|
||||
|
||||
// console.log('currentItems', currentItems)
|
||||
|
||||
return (
|
||||
<RadixAccordion.Item
|
||||
value={id}
|
||||
className={__styles.variants[type].container}
|
||||
>
|
||||
<RadixAccordion.Trigger className={triggerClasses.join(' ')}>
|
||||
{header}
|
||||
<IconChevronDown
|
||||
aria-hidden
|
||||
className={chevronClasses.join(' ')}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</RadixAccordion.Trigger>
|
||||
<RadixAccordion.Content className={__styles.variants[type].content}>
|
||||
<div className={__styles.variants[type].panel}>{children}</div>
|
||||
</RadixAccordion.Content>
|
||||
</RadixAccordion.Item>
|
||||
)
|
||||
}
|
||||
|
||||
Accordion.Item = Item
|
||||
export default Accordion
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Accordion } from './Accordion'
|
||||
@@ -0,0 +1,63 @@
|
||||
/* .sbui-alert-container {
|
||||
@apply rounded-md p-4;
|
||||
}
|
||||
|
||||
.sbui-alert-container--danger {
|
||||
@apply bg-red-600 bg-opacity-10 text-red-600;
|
||||
}
|
||||
|
||||
.sbui-alert-container--warning {
|
||||
@apply bg-yellow-600 bg-opacity-10 text-yellow-600;
|
||||
}
|
||||
|
||||
.sbui-alert-container--info {
|
||||
@apply bg-blue-600 bg-opacity-10 text-blue-600;
|
||||
}
|
||||
|
||||
.sbui-alert-container--success {
|
||||
@apply bg-green-600 bg-opacity-10 text-green-600;
|
||||
} */
|
||||
|
||||
/* .sbui-alert-title {
|
||||
@apply m-0 text-sm font-medium;
|
||||
}
|
||||
|
||||
.sbui-alert-description {
|
||||
@apply mt-2 text-sm;
|
||||
}
|
||||
|
||||
.sbui-alert-description--danger {
|
||||
@apply text-red-500;
|
||||
}
|
||||
|
||||
.sbui-alert-description--warning {
|
||||
@apply text-yellow-500;
|
||||
}
|
||||
|
||||
.sbui-alert-description--info {
|
||||
@apply text-blue-500;
|
||||
}
|
||||
|
||||
.sbui-alert-description--success {
|
||||
@apply text-green-500;
|
||||
}
|
||||
|
||||
.sbui-close-button {
|
||||
@apply cursor-pointer inline-flex transition ease-in-out duration-200 bg-transparent border-transparent rounded-md p-1.5 focus:outline-none;
|
||||
}
|
||||
|
||||
.sbui-close-button--success {
|
||||
@apply text-green-500 hover:bg-green-600 hover:bg-opacity-10;
|
||||
}
|
||||
|
||||
.sbui-close-button--danger {
|
||||
@apply text-red-500 hover:bg-red-600 hover:bg-opacity-10;
|
||||
}
|
||||
|
||||
.sbui-close-button--warning {
|
||||
@apply text-yellow-500 hover:bg-yellow-600 hover:bg-opacity-10;
|
||||
}
|
||||
|
||||
.sbui-close-button--info {
|
||||
@apply text-blue-500 hover:bg-blue-600 hover:bg-opacity-10;
|
||||
} */
|
||||
@@ -0,0 +1,148 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Alert } from './../../index'
|
||||
|
||||
// import ThemeProvider from '../../theme/themeContext'
|
||||
|
||||
export default {
|
||||
title: 'Displays/Alerts',
|
||||
component: Alert,
|
||||
}
|
||||
|
||||
export const Neutral = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const Success = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const SuccessWithIcon = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const Danger = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const DangerWithIcon = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const Info = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const InfoWithIcon = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const Warning = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const WarningWithIcon = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const Closable = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
export const WithAction = (args: any) => (
|
||||
<Alert {...args}>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet
|
||||
labore.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
Neutral.args = {
|
||||
title: 'Success alert with icon',
|
||||
withIcon: true,
|
||||
}
|
||||
|
||||
Success.args = {
|
||||
title: 'Success alert',
|
||||
variant: 'success',
|
||||
}
|
||||
|
||||
SuccessWithIcon.args = {
|
||||
title: 'Success alert with icon',
|
||||
variant: 'success',
|
||||
withIcon: true,
|
||||
}
|
||||
|
||||
Danger.args = {
|
||||
title: 'Danger alert',
|
||||
variant: 'danger',
|
||||
}
|
||||
|
||||
DangerWithIcon.args = {
|
||||
title: 'Danger alert with icon',
|
||||
variant: 'danger',
|
||||
withIcon: true,
|
||||
}
|
||||
|
||||
Info.args = {
|
||||
title: 'Info alert',
|
||||
variant: 'info',
|
||||
}
|
||||
|
||||
InfoWithIcon.args = {
|
||||
title: 'Info alert with icon',
|
||||
variant: 'info',
|
||||
withIcon: true,
|
||||
}
|
||||
|
||||
Warning.args = {
|
||||
title: 'Warning alert',
|
||||
variant: 'warning',
|
||||
}
|
||||
|
||||
WarningWithIcon.args = {
|
||||
title: 'Warning alert with icon',
|
||||
variant: 'warning',
|
||||
withIcon: true,
|
||||
}
|
||||
|
||||
Closable.args = {
|
||||
title: 'Closable alert',
|
||||
closable: true,
|
||||
}
|
||||
|
||||
WithAction.args = {
|
||||
title: 'Alert with action',
|
||||
variant: 'warning',
|
||||
withIcon: true,
|
||||
actions: <div>Hello</div>,
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import Alert from './Alert'
|
||||
|
||||
const VARIANTS = ['success', 'danger', 'warning', 'info']
|
||||
|
||||
describe('#Alert', () => {
|
||||
it('should render elements correctly', () => {
|
||||
render(<Alert title="Required Title">{'Description'}</Alert>)
|
||||
expect(screen.queryByText('Required Title')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Description')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should add custom classes to container div', () => {
|
||||
const { container } = render(
|
||||
<Alert title="Required Title" className={'custom classes'}>
|
||||
{'Description'}
|
||||
</Alert>
|
||||
)
|
||||
expect(container.querySelector('div')).toHaveClass(
|
||||
`sbui-alert-container sbui-alert-container--success custom classes`
|
||||
)
|
||||
})
|
||||
|
||||
it('should close alert when close button clicked', () => {
|
||||
render(
|
||||
<Alert title="Required Title" closable>
|
||||
{'Description'}
|
||||
</Alert>
|
||||
)
|
||||
|
||||
const closeButton = screen.getByRole('button')
|
||||
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
expect(screen.queryByText('Description')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render an icon when passed withIcon', () => {
|
||||
const { container } = render(
|
||||
<Alert title="Required Title" withIcon>
|
||||
{'Description'}
|
||||
</Alert>
|
||||
)
|
||||
|
||||
expect(
|
||||
container.querySelector('div.flex-shrink-0 > svg')
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it.each(VARIANTS)(
|
||||
'should have "sbui-alert-[container|description]--%s" class',
|
||||
(variant) => {
|
||||
const { container } = render(
|
||||
<Alert title="Required Title" variant={variant}>
|
||||
{'Description'}
|
||||
</Alert>
|
||||
)
|
||||
|
||||
expect(container.querySelector('div')).toHaveClass(
|
||||
`sbui-alert-container sbui-alert-container--${variant}`
|
||||
)
|
||||
expect(screen.queryByText('Description')).toHaveClass(
|
||||
`sbui-alert-description sbui-alert-description--${variant}`
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from 'react'
|
||||
import { IconAlertTriangle } from '../Icon/icons/IconAlertTriangle'
|
||||
|
||||
import { IconInfo } from '../Icon/icons/IconInfo'
|
||||
import { IconX } from '../Icon/icons/IconX'
|
||||
|
||||
// @ts-ignore
|
||||
// import AlertStyles from './Alert.module.css'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { IconAlertOctagon } from '../Icon/icons/IconAlertOctagon'
|
||||
import { IconCheckCircle } from '../Icon/icons/IconCheckCircle'
|
||||
|
||||
interface Props {
|
||||
variant?: 'success' | 'danger' | 'warning' | 'info' | 'neutral'
|
||||
className?: string
|
||||
title: string
|
||||
withIcon?: boolean
|
||||
closable?: boolean
|
||||
children?: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
actions?: React.ReactNode
|
||||
}
|
||||
|
||||
const icons: Record<
|
||||
'success' | 'danger' | 'warning' | 'info' | 'neutral',
|
||||
React.ReactElement
|
||||
> = {
|
||||
danger: <IconAlertOctagon strokeWidth={1.5} size={18} />,
|
||||
success: <IconCheckCircle strokeWidth={1.5} size={18} />,
|
||||
warning: <IconAlertTriangle strokeWidth={1.5} size={18} />,
|
||||
info: <IconInfo strokeWidth={1.5} size={18} />,
|
||||
neutral: <></>,
|
||||
}
|
||||
|
||||
function Alert({
|
||||
variant = 'neutral',
|
||||
className,
|
||||
title,
|
||||
withIcon,
|
||||
closable,
|
||||
children,
|
||||
icon,
|
||||
actions,
|
||||
}: Props) {
|
||||
let __styles = styleHandler('alert')
|
||||
|
||||
const [visible, setVisible] = useState(true)
|
||||
|
||||
let containerClasses = [__styles.base]
|
||||
containerClasses.push(__styles.variant[variant].base)
|
||||
|
||||
if (className) containerClasses.push(className)
|
||||
|
||||
let descriptionClasses = [
|
||||
__styles.description,
|
||||
__styles.variant[variant].description,
|
||||
]
|
||||
let closeButtonClasses = [__styles.close]
|
||||
|
||||
return (
|
||||
<>
|
||||
{visible && (
|
||||
<div className={containerClasses.join(' ')}>
|
||||
{withIcon ? (
|
||||
<div className={__styles.variant[variant].icon}>
|
||||
{withIcon && icons[variant]}
|
||||
</div>
|
||||
) : null}
|
||||
{icon && icon}
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div>
|
||||
<h3
|
||||
className={[
|
||||
__styles.variant[variant].header,
|
||||
__styles.header,
|
||||
].join(' ')}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<div className={descriptionClasses.join(' ')}>{children}</div>
|
||||
</div>
|
||||
{actions}
|
||||
</div>
|
||||
{closable && (
|
||||
<button
|
||||
aria-label="Close alert"
|
||||
onClick={() => setVisible(false)}
|
||||
className={closeButtonClasses.join(' ')}
|
||||
>
|
||||
<IconX strokeWidth={2} size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Alert
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Alert } from './Alert'
|
||||
@@ -0,0 +1,7 @@
|
||||
.sbui-auth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sbui-auth-label {
|
||||
@apply block text-sm text-gray-700 dark:text-gray-200 font-medium;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Auth } from './'
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
// @ts-ignore
|
||||
import { Typography, Button, Space } from '../../index'
|
||||
|
||||
const supabase = createClient(
|
||||
'https://rsnibhkhsbfnncjmwnkj.supabase.co',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNTIxNDE1MywiZXhwIjoxOTMwNzkwMTUzfQ.OQEbAaTfgDdLCCht251P2JRD3QDnui6nsU8N-tZA_Mc'
|
||||
)
|
||||
|
||||
export default {
|
||||
title: 'Auth/Auth',
|
||||
component: Auth,
|
||||
}
|
||||
|
||||
const Container = (props: any) => {
|
||||
const { user } = Auth.useUser()
|
||||
if (user)
|
||||
return (
|
||||
<>
|
||||
<Typography.Text>Signed in: {user.email}</Typography.Text>
|
||||
<Button block onClick={() => props.supabaseClient.auth.signOut()}>
|
||||
Sign out
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
return props.children
|
||||
}
|
||||
|
||||
export const Default = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const withSocialAuth = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const withAllSocialAuth = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const withSocialLargeButtons = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const withColouredSocialAuth = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const withSocialAuthHorizontal = (args: any) => (
|
||||
<Auth.UserContextProvider {...args}>
|
||||
<Container {...args}>
|
||||
<Auth {...args} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
)
|
||||
export const updatePassword = (args: any) => <Auth.UpdatePassword {...args} />
|
||||
|
||||
export const ChangeViewState = (args: any) => {
|
||||
const [view, setView] = useState<
|
||||
'sign_in' | 'sign_up' | 'forgotten_password' | 'magic_link'
|
||||
>('sign_in')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space>
|
||||
<Button
|
||||
type={view === 'sign_up' ? 'primary' : 'default'}
|
||||
onClick={() => setView('sign_up')}
|
||||
>
|
||||
Sign up
|
||||
</Button>
|
||||
<Button
|
||||
type={view === 'sign_in' ? 'primary' : 'default'}
|
||||
onClick={() => setView('sign_in')}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
type={view === 'forgotten_password' ? 'primary' : 'default'}
|
||||
onClick={() => setView('forgotten_password')}
|
||||
>
|
||||
Forgotten password
|
||||
</Button>
|
||||
<Button
|
||||
type={view === 'magic_link' ? 'primary' : 'default'}
|
||||
onClick={() => setView('magic_link')}
|
||||
>
|
||||
Magic link
|
||||
</Button>
|
||||
</Space>
|
||||
<Auth.UserContextProvider supabaseClient={supabase}>
|
||||
<Container supabaseClient={supabase}>
|
||||
<Auth supabaseClient={supabase} view={view} />
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Default.args = {
|
||||
supabaseClient: supabase,
|
||||
}
|
||||
|
||||
withSocialAuth.args = {
|
||||
supabaseClient: supabase,
|
||||
providers: ['facebook', 'google'],
|
||||
}
|
||||
|
||||
withAllSocialAuth.args = {
|
||||
supabaseClient: supabase,
|
||||
providers: [
|
||||
'apple',
|
||||
'azure',
|
||||
'bitbucket',
|
||||
'discord',
|
||||
'facebook',
|
||||
'github',
|
||||
'gitlab',
|
||||
'google',
|
||||
'twitch',
|
||||
'twitter',
|
||||
],
|
||||
}
|
||||
|
||||
withSocialLargeButtons.args = {
|
||||
supabaseClient: supabase,
|
||||
providers: [
|
||||
'apple',
|
||||
'azure',
|
||||
'bitbucket',
|
||||
'discord',
|
||||
'facebook',
|
||||
'github',
|
||||
'gitlab',
|
||||
'google',
|
||||
'twitch',
|
||||
'twitter',
|
||||
],
|
||||
socialButtonSize: 'large',
|
||||
}
|
||||
|
||||
withColouredSocialAuth.args = {
|
||||
supabaseClient: supabase,
|
||||
socialColors: true,
|
||||
providers: [
|
||||
'apple',
|
||||
'azure',
|
||||
'bitbucket',
|
||||
'discord',
|
||||
'facebook',
|
||||
'github',
|
||||
'gitlab',
|
||||
'google',
|
||||
'twitch',
|
||||
'twitter',
|
||||
],
|
||||
}
|
||||
|
||||
withSocialAuthHorizontal.args = {
|
||||
supabaseClient: supabase,
|
||||
providers: ['facebook', 'google'],
|
||||
socialLayout: 'horizontal',
|
||||
}
|
||||
|
||||
updatePassword.args = {
|
||||
supabaseClient: supabase,
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { SupabaseClient, Provider } from '@supabase/supabase-js'
|
||||
import {
|
||||
Input,
|
||||
Checkbox,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Divider,
|
||||
IconKey,
|
||||
IconMail,
|
||||
IconInbox,
|
||||
IconLock,
|
||||
} from './../../index'
|
||||
import { UserContextProvider, useUser } from './UserContext'
|
||||
import * as SocialIcons from './Icons'
|
||||
// @ts-ignore
|
||||
import AuthStyles from './Auth.module.css'
|
||||
|
||||
const VIEWS: ViewsMap = {
|
||||
SIGN_IN: 'sign_in',
|
||||
SIGN_UP: 'sign_up',
|
||||
FORGOTTEN_PASSWORD: 'forgotten_password',
|
||||
MAGIC_LINK: 'magic_link',
|
||||
UPDATE_PASSWORD: 'update_password',
|
||||
}
|
||||
|
||||
interface ViewsMap {
|
||||
[key: string]: ViewType
|
||||
}
|
||||
|
||||
type ViewType =
|
||||
| 'sign_in'
|
||||
| 'sign_up'
|
||||
| 'forgotten_password'
|
||||
| 'magic_link'
|
||||
| 'update_password'
|
||||
|
||||
type RedirectTo = undefined | string
|
||||
|
||||
export interface Props {
|
||||
supabaseClient: SupabaseClient
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
socialLayout?: 'horizontal' | 'vertical'
|
||||
socialColors?: boolean
|
||||
socialButtonSize?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
providers?: Provider[]
|
||||
verticalSocialLayout?: any
|
||||
view?: ViewType
|
||||
redirectTo?: RedirectTo
|
||||
onlyThirdPartyProviders?: boolean
|
||||
magicLink?: boolean
|
||||
}
|
||||
|
||||
function Auth({
|
||||
supabaseClient,
|
||||
className,
|
||||
style,
|
||||
socialLayout = 'vertical',
|
||||
socialColors = false,
|
||||
socialButtonSize = 'medium',
|
||||
providers,
|
||||
view = 'sign_in',
|
||||
redirectTo,
|
||||
onlyThirdPartyProviders = false,
|
||||
magicLink = false,
|
||||
}: Props): JSX.Element | null {
|
||||
const [authView, setAuthView] = useState(view)
|
||||
const [defaultEmail, setDefaultEmail] = useState('')
|
||||
const [defaultPassword, setDefaultPassword] = useState('')
|
||||
|
||||
const verticalSocialLayout = socialLayout === 'vertical' ? true : false
|
||||
|
||||
let containerClasses = [AuthStyles['sbui-auth']]
|
||||
if (className) {
|
||||
containerClasses.push(className)
|
||||
}
|
||||
|
||||
const Container = (props: any) => (
|
||||
<div className={containerClasses.join(' ')} style={style}>
|
||||
<Space size={8} direction={'vertical'}>
|
||||
<SocialAuth
|
||||
supabaseClient={supabaseClient}
|
||||
verticalSocialLayout={verticalSocialLayout}
|
||||
providers={providers}
|
||||
socialLayout={socialLayout}
|
||||
socialButtonSize={socialButtonSize}
|
||||
socialColors={socialColors}
|
||||
redirectTo={redirectTo}
|
||||
onlyThirdPartyProviders={onlyThirdPartyProviders}
|
||||
magicLink={magicLink}
|
||||
/>
|
||||
{!onlyThirdPartyProviders && props.children}
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// handle view override
|
||||
setAuthView(view)
|
||||
}, [view])
|
||||
|
||||
switch (authView) {
|
||||
case VIEWS.SIGN_IN:
|
||||
case VIEWS.SIGN_UP:
|
||||
return (
|
||||
<Container>
|
||||
<EmailAuth
|
||||
id={authView === VIEWS.SIGN_UP ? 'auth-sign-up' : 'auth-sign-in'}
|
||||
supabaseClient={supabaseClient}
|
||||
authView={authView}
|
||||
setAuthView={setAuthView}
|
||||
defaultEmail={defaultEmail}
|
||||
defaultPassword={defaultPassword}
|
||||
setDefaultEmail={setDefaultEmail}
|
||||
setDefaultPassword={setDefaultPassword}
|
||||
redirectTo={redirectTo}
|
||||
magicLink={magicLink}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
case VIEWS.FORGOTTEN_PASSWORD:
|
||||
return (
|
||||
<Container>
|
||||
<ForgottenPassword
|
||||
supabaseClient={supabaseClient}
|
||||
setAuthView={setAuthView}
|
||||
redirectTo={redirectTo}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
|
||||
case VIEWS.MAGIC_LINK:
|
||||
return (
|
||||
<Container>
|
||||
<MagicLink
|
||||
supabaseClient={supabaseClient}
|
||||
setAuthView={setAuthView}
|
||||
redirectTo={redirectTo}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
|
||||
case VIEWS.UPDATE_PASSWORD:
|
||||
return (
|
||||
<Container>
|
||||
<UpdatePassword supabaseClient={supabaseClient} />
|
||||
</Container>
|
||||
)
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function SocialAuth({
|
||||
className,
|
||||
style,
|
||||
supabaseClient,
|
||||
children,
|
||||
socialLayout = 'vertical',
|
||||
socialColors = false,
|
||||
socialButtonSize,
|
||||
providers,
|
||||
verticalSocialLayout,
|
||||
redirectTo,
|
||||
onlyThirdPartyProviders,
|
||||
magicLink,
|
||||
...props
|
||||
}: Props) {
|
||||
const buttonStyles: any = {
|
||||
azure: {
|
||||
backgroundColor: '#008AD7',
|
||||
color: 'white',
|
||||
},
|
||||
bitbucket: {
|
||||
backgroundColor: '#205081',
|
||||
color: 'white',
|
||||
},
|
||||
facebook: {
|
||||
backgroundColor: '#4267B2',
|
||||
color: 'white',
|
||||
},
|
||||
github: {
|
||||
backgroundColor: '#333',
|
||||
color: 'white',
|
||||
},
|
||||
gitlab: {
|
||||
backgroundColor: '#FC6D27',
|
||||
},
|
||||
google: {
|
||||
backgroundColor: '#ce4430',
|
||||
color: 'white',
|
||||
},
|
||||
twitter: {
|
||||
backgroundColor: '#1DA1F2',
|
||||
color: 'white',
|
||||
},
|
||||
apple: {
|
||||
backgroundColor: '#000',
|
||||
color: 'white',
|
||||
},
|
||||
discord: {
|
||||
backgroundColor: '#404fec',
|
||||
color: 'white',
|
||||
},
|
||||
twitch: {
|
||||
backgroundColor: '#9146ff',
|
||||
color: 'white',
|
||||
},
|
||||
}
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleProviderSignIn = async (provider: Provider) => {
|
||||
setLoading(true)
|
||||
const { error } = await supabaseClient.auth.signIn(
|
||||
{ provider },
|
||||
{ redirectTo }
|
||||
)
|
||||
if (error) setError(error.message)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Space size={8} direction={'vertical'}>
|
||||
{providers && providers.length > 0 && (
|
||||
<React.Fragment>
|
||||
<Space size={4} direction={'vertical'}>
|
||||
<Typography.Text
|
||||
type="secondary"
|
||||
className={AuthStyles['sbui-auth-label']}
|
||||
>
|
||||
Sign in with
|
||||
</Typography.Text>
|
||||
<Space size={2} direction={socialLayout}>
|
||||
{providers.map((provider) => {
|
||||
// @ts-ignore
|
||||
const AuthIcon = SocialIcons[provider]
|
||||
return (
|
||||
<div
|
||||
key={provider}
|
||||
style={!verticalSocialLayout ? { flexGrow: 1 } : {}}
|
||||
>
|
||||
<Button
|
||||
block
|
||||
type="default"
|
||||
shadow
|
||||
size={socialButtonSize}
|
||||
style={socialColors ? buttonStyles[provider] : {}}
|
||||
icon={<AuthIcon />}
|
||||
loading={loading}
|
||||
onClick={() => handleProviderSignIn(provider)}
|
||||
className="flex items-center"
|
||||
>
|
||||
{verticalSocialLayout && 'Sign up with ' + provider}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Space>
|
||||
</Space>
|
||||
{!onlyThirdPartyProviders && <Divider>or continue with</Divider>}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
|
||||
function EmailAuth({
|
||||
authView,
|
||||
defaultEmail,
|
||||
defaultPassword,
|
||||
id,
|
||||
setAuthView,
|
||||
setDefaultEmail,
|
||||
setDefaultPassword,
|
||||
supabaseClient,
|
||||
redirectTo,
|
||||
magicLink,
|
||||
}: {
|
||||
authView: ViewType
|
||||
defaultEmail: string
|
||||
defaultPassword: string
|
||||
id: 'auth-sign-up' | 'auth-sign-in'
|
||||
setAuthView: any
|
||||
setDefaultEmail: (email: string) => void
|
||||
setDefaultPassword: (password: string) => void
|
||||
supabaseClient: SupabaseClient
|
||||
redirectTo?: RedirectTo
|
||||
magicLink?: boolean
|
||||
}) {
|
||||
const isMounted = useRef<boolean>(true)
|
||||
const [email, setEmail] = useState(defaultEmail)
|
||||
const [password, setPassword] = useState(defaultPassword)
|
||||
const [rememberMe, setRememberMe] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setEmail(defaultEmail)
|
||||
setPassword(defaultPassword)
|
||||
|
||||
return () => {
|
||||
isMounted.current = false
|
||||
}
|
||||
}, [authView])
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setLoading(true)
|
||||
switch (authView) {
|
||||
case 'sign_in':
|
||||
const { error: signInError } = await supabaseClient.auth.signIn(
|
||||
{
|
||||
email,
|
||||
password,
|
||||
},
|
||||
{ redirectTo }
|
||||
)
|
||||
if (signInError) setError(signInError.message)
|
||||
break
|
||||
case 'sign_up':
|
||||
const { error: signUpError, data: signUpData } =
|
||||
await supabaseClient.auth.signUp(
|
||||
{
|
||||
email,
|
||||
password,
|
||||
},
|
||||
{ redirectTo }
|
||||
)
|
||||
if (signUpError) setError(signUpError.message)
|
||||
// checking if it has access_token to know if email verification is disabled
|
||||
else if (signUpData?.hasOwnProperty('confirmation_sent_at'))
|
||||
setMessage('Check your email for the confirmation link.')
|
||||
break
|
||||
}
|
||||
|
||||
/*
|
||||
* it is possible the auth component may have been unmounted at this point
|
||||
* check if component is mounted before setting a useState
|
||||
*/
|
||||
if (isMounted.current) setLoading(false)
|
||||
}
|
||||
|
||||
const handleViewChange = (newView: ViewType) => {
|
||||
setDefaultEmail(email)
|
||||
setDefaultPassword(password)
|
||||
setAuthView(newView)
|
||||
}
|
||||
|
||||
return (
|
||||
<form id={id} onSubmit={handleSubmit}>
|
||||
<Space size={6} direction={'vertical'}>
|
||||
<Space size={3} direction={'vertical'}>
|
||||
<Input
|
||||
label="Email address"
|
||||
autoComplete="email"
|
||||
defaultValue={email}
|
||||
icon={<IconMail size={21} stroke={'#666666'} />}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
type="password"
|
||||
defaultValue={password}
|
||||
autoComplete="current-password"
|
||||
icon={<IconKey size={21} stroke={'#666666'} />}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
<Space direction="vertical" size={6}>
|
||||
<Space style={{ justifyContent: 'space-between' }}>
|
||||
<Checkbox
|
||||
label="Remember me"
|
||||
name="remember_me"
|
||||
id="remember_me"
|
||||
onChange={(value: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setRememberMe(value.target.checked)
|
||||
}
|
||||
/>
|
||||
{authView === VIEWS.SIGN_IN && (
|
||||
<Typography.Link
|
||||
href="#auth-forgot-password"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
setAuthView(VIEWS.FORGOTTEN_PASSWORD)
|
||||
}}
|
||||
>
|
||||
Forgot your password?
|
||||
</Typography.Link>
|
||||
)}
|
||||
</Space>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
size="large"
|
||||
icon={<IconLock size={21} />}
|
||||
loading={loading}
|
||||
block
|
||||
>
|
||||
{authView === VIEWS.SIGN_IN ? 'Sign in' : 'Sign up'}
|
||||
</Button>
|
||||
</Space>
|
||||
<Space direction="vertical" style={{ textAlign: 'center' }}>
|
||||
{authView === VIEWS.SIGN_IN && magicLink && (
|
||||
<Typography.Link
|
||||
href="#auth-magic-link"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
setAuthView(VIEWS.MAGIC_LINK)
|
||||
}}
|
||||
>
|
||||
Sign in with magic link
|
||||
</Typography.Link>
|
||||
)}
|
||||
{authView === VIEWS.SIGN_IN ? (
|
||||
<Typography.Link
|
||||
href="#auth-sign-up"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
handleViewChange(VIEWS.SIGN_UP)
|
||||
}}
|
||||
>
|
||||
Don't have an account? Sign up
|
||||
</Typography.Link>
|
||||
) : (
|
||||
<Typography.Link
|
||||
href="#auth-sign-in"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
handleViewChange(VIEWS.SIGN_IN)
|
||||
}}
|
||||
>
|
||||
Do you have an account? Sign in
|
||||
</Typography.Link>
|
||||
)}
|
||||
{message && <Typography.Text>{message}</Typography.Text>}
|
||||
{error && <Typography.Text type="danger">{error}</Typography.Text>}
|
||||
</Space>
|
||||
</Space>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function MagicLink({
|
||||
setAuthView,
|
||||
supabaseClient,
|
||||
redirectTo,
|
||||
}: {
|
||||
setAuthView: any
|
||||
supabaseClient: SupabaseClient
|
||||
redirectTo?: RedirectTo
|
||||
}) {
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleMagicLinkSignIn = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setMessage('')
|
||||
setLoading(true)
|
||||
const { error } = await supabaseClient.auth.signIn(
|
||||
{ email },
|
||||
{ redirectTo }
|
||||
)
|
||||
if (error) setError(error.message)
|
||||
else setMessage('Check your email for the magic link')
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<form id="auth-magic-link" onSubmit={handleMagicLinkSignIn}>
|
||||
<Space size={4} direction={'vertical'}>
|
||||
<Space size={3} direction={'vertical'}>
|
||||
<Input
|
||||
label="Email address"
|
||||
placeholder="Your email address"
|
||||
icon={<IconMail size={21} stroke={'#666666'} />}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
icon={<IconInbox size={21} />}
|
||||
loading={loading}
|
||||
>
|
||||
Send magic link
|
||||
</Button>
|
||||
</Space>
|
||||
<Typography.Link
|
||||
href="#auth-sign-in"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
setAuthView(VIEWS.SIGN_IN)
|
||||
}}
|
||||
>
|
||||
Sign in with password
|
||||
</Typography.Link>
|
||||
{message && <Typography.Text>{message}</Typography.Text>}
|
||||
{error && <Typography.Text type="danger">{error}</Typography.Text>}
|
||||
</Space>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function ForgottenPassword({
|
||||
setAuthView,
|
||||
supabaseClient,
|
||||
redirectTo,
|
||||
}: {
|
||||
setAuthView: any
|
||||
supabaseClient: SupabaseClient
|
||||
redirectTo?: RedirectTo
|
||||
}) {
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handlePasswordReset = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setMessage('')
|
||||
setLoading(true)
|
||||
const { error } = await supabaseClient.auth.api.resetPasswordForEmail(
|
||||
email,
|
||||
{ redirectTo }
|
||||
)
|
||||
if (error) setError(error.message)
|
||||
else setMessage('Check your email for the password reset link')
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<form id="auth-forgot-password" onSubmit={handlePasswordReset}>
|
||||
<Space size={4} direction={'vertical'}>
|
||||
<Space size={3} direction={'vertical'}>
|
||||
<Input
|
||||
label="Email address"
|
||||
placeholder="Your email address"
|
||||
icon={<IconMail size={21} stroke={'#666666'} />}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
icon={<IconInbox size={21} />}
|
||||
loading={loading}
|
||||
>
|
||||
Send reset password instructions
|
||||
</Button>
|
||||
</Space>
|
||||
<Typography.Link
|
||||
href="#auth-sign-in"
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
e.preventDefault()
|
||||
setAuthView(VIEWS.SIGN_IN)
|
||||
}}
|
||||
>
|
||||
Go back to sign in
|
||||
</Typography.Link>
|
||||
{message && <Typography.Text>{message}</Typography.Text>}
|
||||
{error && <Typography.Text type="danger">{error}</Typography.Text>}
|
||||
</Space>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
function UpdatePassword({
|
||||
supabaseClient,
|
||||
}: {
|
||||
supabaseClient: SupabaseClient
|
||||
}) {
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handlePasswordReset = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setMessage('')
|
||||
setLoading(true)
|
||||
const { error } = await supabaseClient.auth.update({ password })
|
||||
if (error) setError(error.message)
|
||||
else setMessage('Your password has been updated')
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<form id="auth-update-password" onSubmit={handlePasswordReset}>
|
||||
<Space size={4} direction={'vertical'}>
|
||||
<Space size={3} direction={'vertical'}>
|
||||
<Input
|
||||
label="New password"
|
||||
placeholder="Enter your new password"
|
||||
type="password"
|
||||
icon={<IconKey size={21} stroke={'#666666'} />}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
size="large"
|
||||
htmlType="submit"
|
||||
icon={<IconKey size={21} />}
|
||||
loading={loading}
|
||||
>
|
||||
Update password
|
||||
</Button>
|
||||
</Space>
|
||||
{message && <Typography.Text>{message}</Typography.Text>}
|
||||
{error && <Typography.Text type="danger">{error}</Typography.Text>}
|
||||
</Space>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
Auth.ForgottenPassword = ForgottenPassword
|
||||
Auth.UpdatePassword = UpdatePassword
|
||||
Auth.MagicLink = MagicLink
|
||||
Auth.UserContextProvider = UserContextProvider
|
||||
Auth.useUser = useUser
|
||||
|
||||
export default Auth
|
||||
@@ -0,0 +1,236 @@
|
||||
import React from 'react'
|
||||
|
||||
const size = 21
|
||||
|
||||
export const google = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="google"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 488 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const facebook = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="facebook-square"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h137.25V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.27c-30.81 0-40.42 19.12-40.42 38.73V256h68.78l-11 71.69h-57.78V480H400a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const twitter = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="twitter"
|
||||
className="svg-inline--fa fa-twitter fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const apple = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="apple"
|
||||
className="svg-inline--fa fa-apple fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const github = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="github"
|
||||
className="svg-inline--fa fa-github fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 496 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const gitlab = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="gitlab"
|
||||
className="svg-inline--fa fa-gitlab fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M105.2 24.9c-3.1-8.9-15.7-8.9-18.9 0L29.8 199.7h132c-.1 0-56.6-174.8-56.6-174.8zM.9 287.7c-2.6 8 .3 16.9 7.1 22l247.9 184-226.2-294zm160.8-88l94.3 294 94.3-294zm349.4 88l-28.8-88-226.3 294 247.9-184c6.9-5.1 9.7-14 7.2-22zM425.7 24.9c-3.1-8.9-15.7-8.9-18.9 0l-56.6 174.8h132z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
export const bitbucket = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="bitbucket"
|
||||
className="svg-inline--fa fa-bitbucket fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.2 32A16 16 0 0 0 6 47.8a26.35 26.35 0 0 0 .2 2.8l67.9 412.1a21.77 21.77 0 0 0 21.3 18.2h325.7a16 16 0 0 0 16-13.4L505 50.7a16 16 0 0 0-13.2-18.3 24.58 24.58 0 0 0-2.8-.2L22.2 32zm285.9 297.8h-104l-28.1-147h157.3l-25.2 147z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const discord = () => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 71 55"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0)">
|
||||
<path
|
||||
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const azure = () => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 96 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="e399c19f-b68f-429d-b176-18c2117ff73c"
|
||||
x1="-1032.172"
|
||||
x2="-1059.213"
|
||||
y1="145.312"
|
||||
y2="65.426"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#fff"></stop>
|
||||
<stop offset="1" stopColor="#fff"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="ac2a6fc2-ca48-4327-9a3c-d4dcc3256e15"
|
||||
x1="-1023.725"
|
||||
x2="-1029.98"
|
||||
y1="108.083"
|
||||
y2="105.968"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopOpacity=".3"></stop>
|
||||
<stop offset=".071" stopOpacity=".2"></stop>
|
||||
<stop offset=".321" stopOpacity=".1"></stop>
|
||||
<stop offset=".623" stopOpacity=".05"></stop>
|
||||
<stop offset="1" stopOpacity="0"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="a7fee970-a784-4bb1-af8d-63d18e5f7db9"
|
||||
x1="-1027.165"
|
||||
x2="-997.482"
|
||||
y1="147.642"
|
||||
y2="68.561"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stopColor="#fff"></stop>
|
||||
<stop offset="1" stopColor="#fff"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill="url(#e399c19f-b68f-429d-b176-18c2117ff73c)"
|
||||
d="M33.338 6.544h26.038l-27.03 80.087a4.152 4.152 0 0 1-3.933 2.824H8.149a4.145 4.145 0 0 1-3.928-5.47L29.404 9.368a4.152 4.152 0 0 1 3.934-2.825z"
|
||||
></path>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M71.175 60.261h-41.29a1.911 1.911 0 0 0-1.305 3.309l26.532 24.764a4.171 4.171 0 0 0 2.846 1.121h23.38z"
|
||||
></path>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M33.338 6.544a4.118 4.118 0 0 0-3.943 2.879L4.252 83.917a4.14 4.14 0 0 0 3.908 5.538h20.787a4.443 4.443 0 0 0 3.41-2.9l5.014-14.777 17.91 16.705a4.237 4.237 0 0 0 2.666.972H81.24L71.024 60.261l-29.781.007L59.47 6.544z"
|
||||
></path>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M66.595 9.364a4.145 4.145 0 0 0-3.928-2.82H33.648a4.146 4.146 0 0 1 3.928 2.82l25.184 74.62a4.146 4.146 0 0 1-3.928 5.472h29.02a4.146 4.146 0 0 0 3.927-5.472z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const twitch = () => (
|
||||
<svg
|
||||
width={size}
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="twitch"
|
||||
className="svg-inline--fa fa-twitch fa-w-16"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M391.17,103.47H352.54v109.7h38.63ZM285,103H246.37V212.75H285ZM120.83,0,24.31,91.42V420.58H140.14V512l96.53-91.42h77.25L487.69,256V0ZM449.07,237.75l-77.22,73.12H294.61l-67.6,64v-64H140.14V36.58H449.07Z"
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
import React, { useEffect, useState, createContext, useContext } from 'react'
|
||||
import { SupabaseClient, Session, User } from '@supabase/supabase-js'
|
||||
|
||||
export interface AuthSession {
|
||||
user: User | null
|
||||
session: Session | null
|
||||
}
|
||||
|
||||
const UserContext = createContext<AuthSession>({ user: null, session: null })
|
||||
|
||||
export interface Props {
|
||||
supabaseClient: SupabaseClient
|
||||
[propName: string]: any
|
||||
}
|
||||
|
||||
export const UserContextProvider = (props: Props) => {
|
||||
const { supabaseClient } = props
|
||||
const [session, setSession] = useState<Session | null>(
|
||||
supabaseClient.auth.session()
|
||||
)
|
||||
const [user, setUser] = useState<User | null>(session?.user ?? null)
|
||||
|
||||
useEffect(() => {
|
||||
const { data: authListener } = supabaseClient.auth.onAuthStateChange(
|
||||
async (event, session) => {
|
||||
setSession(session)
|
||||
setUser(session?.user ?? null)
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
authListener?.unsubscribe()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const value = {
|
||||
session,
|
||||
user,
|
||||
}
|
||||
return <UserContext.Provider value={value} {...props} />
|
||||
}
|
||||
|
||||
export const useUser = () => {
|
||||
const context = useContext(UserContext)
|
||||
if (context === undefined) {
|
||||
throw new Error(`useUser must be used within a UserContextProvider.`)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Auth } from './Auth'
|
||||
@@ -0,0 +1,65 @@
|
||||
.sbui-avatar {
|
||||
@apply rounded-full;
|
||||
}
|
||||
|
||||
.sbui-avatar-type-rounded {
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
.sbui-avatar-type-square {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.sbui-avatar-image {
|
||||
object-fit: 'contain';
|
||||
border-radius: '50%';
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.sbui-avatar-children {
|
||||
@apply flex;
|
||||
@apply justify-center;
|
||||
@apply items-center
|
||||
}
|
||||
|
||||
.sbui-avatar-icon {
|
||||
@apply flex;
|
||||
@apply justify-center;
|
||||
@apply items-center;
|
||||
background-color: cadetblue;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.sbui-avatar-text {
|
||||
@apply flex;
|
||||
@apply justify-center;
|
||||
@apply items-center;
|
||||
background-color: cadetblue;
|
||||
float: left;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.sbui-avatar-fallback {
|
||||
@apply flex;
|
||||
@apply justify-center;
|
||||
@apply items-center;
|
||||
background-color: cadetblue;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.sbui-avatar-single {
|
||||
|
||||
}
|
||||
|
||||
.sbui-avatar-multiple {
|
||||
|
||||
}
|
||||
|
||||
.sbui-image-responsive {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react'
|
||||
import { IconAirplay } from '../Icon/icons/IconAirplay'
|
||||
|
||||
import Avatar from './Avatar'
|
||||
|
||||
export default {
|
||||
title: 'General/Avatar',
|
||||
component: Avatar,
|
||||
}
|
||||
|
||||
export const Image = (args: any) => {
|
||||
return (
|
||||
<>
|
||||
<Avatar {...args} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const Text = (args: any) => {
|
||||
return (
|
||||
<>
|
||||
<Avatar {...args} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const Icon = (args: any) => {
|
||||
return (
|
||||
<>
|
||||
<Avatar {...args} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const Fallback = (args: any) => {
|
||||
return (
|
||||
<>
|
||||
<Avatar {...args} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Image.args = {
|
||||
active: true,
|
||||
src: 'https://via.placeholder.com/150',
|
||||
}
|
||||
|
||||
Text.args = {
|
||||
active: true,
|
||||
text: 'Shoury',
|
||||
}
|
||||
|
||||
Icon.args = {
|
||||
active: true,
|
||||
Icon: IconAirplay,
|
||||
}
|
||||
|
||||
Fallback.args = {
|
||||
active: true,
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from 'react'
|
||||
import { Icon } from '../Icon/IconImportHandler'
|
||||
import { IconUser } from '../Icon/icons/IconUser'
|
||||
// @ts-ignore
|
||||
import AvatarStyles from './Avatar.module.css'
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode
|
||||
src?: string | undefined
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
alt?: string
|
||||
responsive?: boolean
|
||||
text?: string
|
||||
variant?: 'circle' | 'square'
|
||||
AvatarIcon?: Icon
|
||||
size: number
|
||||
}
|
||||
|
||||
export default function Avatar({
|
||||
src,
|
||||
style,
|
||||
className,
|
||||
alt,
|
||||
responsive,
|
||||
text,
|
||||
variant,
|
||||
AvatarIcon,
|
||||
size,
|
||||
children,
|
||||
}: Props) {
|
||||
const classes = [AvatarStyles['sbui-avatar']]
|
||||
classes.push(className)
|
||||
let objectToRender
|
||||
|
||||
const imageExist = () => {
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
if (img.complete) {
|
||||
return true
|
||||
} else {
|
||||
img.onload = () => {
|
||||
return true
|
||||
}
|
||||
img.onerror = () => {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (imageExist && src) {
|
||||
classes.push(AvatarStyles['sbui-avatar-image'])
|
||||
objectToRender = (
|
||||
<img
|
||||
className={classes.join(' ')}
|
||||
src={src}
|
||||
alt={alt}
|
||||
style={{ height: size, width: size, ...style }}
|
||||
/>
|
||||
)
|
||||
} else if (AvatarIcon) {
|
||||
classes.push(AvatarStyles['sbui-avatar-icon'])
|
||||
objectToRender = (
|
||||
<div
|
||||
className={classes.join(' ')}
|
||||
style={{ height: size, width: size, ...style }}
|
||||
>
|
||||
<AvatarIcon />
|
||||
</div>
|
||||
)
|
||||
} else if (text) {
|
||||
classes.push(AvatarStyles['sbui-avatar-text'])
|
||||
objectToRender = (
|
||||
<div
|
||||
className={classes.join(' ')}
|
||||
style={{ height: size, width: size, ...style }}
|
||||
>
|
||||
<p>{text[0]}</p>
|
||||
</div>
|
||||
)
|
||||
} else if (children) {
|
||||
classes.push(AvatarStyles['sbui-avatar-children'])
|
||||
objectToRender = (
|
||||
<div
|
||||
className={classes.join(' ')}
|
||||
style={{ height: size, width: size, ...style }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
classes.push(AvatarStyles['sbui-avatar-fallback'])
|
||||
objectToRender = (
|
||||
<div
|
||||
className={classes.join(' ')}
|
||||
style={{ height: size, width: size, ...style }}
|
||||
>
|
||||
<IconUser />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <>{objectToRender}</>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Image } from './Avatar'
|
||||
@@ -0,0 +1,43 @@
|
||||
/* .sbui-badge {
|
||||
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-500 bg-opacity-10 text-brand-800;
|
||||
}
|
||||
|
||||
.sbui-badge--large {
|
||||
@apply px-3 py-0.5 rounded-full text-sm;
|
||||
}
|
||||
|
||||
.sbui-badge-dot {
|
||||
@apply -ml-0.5 mr-1.5 h-2 w-2;
|
||||
}
|
||||
|
||||
.sbui-badge--gray {
|
||||
@apply bg-gray-600 bg-opacity-10 text-gray-500;
|
||||
}
|
||||
|
||||
.sbui-badge--red {
|
||||
@apply bg-red-600 bg-opacity-10 text-red-500;
|
||||
}
|
||||
|
||||
.sbui-badge--yellow {
|
||||
@apply bg-yellow-600 bg-opacity-10 text-yellow-500;
|
||||
}
|
||||
|
||||
.sbui-badge--green {
|
||||
@apply bg-green-600 bg-opacity-10 text-green-500;
|
||||
}
|
||||
|
||||
.sbui-badge--blue {
|
||||
@apply bg-blue-600 bg-opacity-10 text-blue-500;
|
||||
}
|
||||
|
||||
.sbui-badge--indigo {
|
||||
@apply bg-indigo-600 bg-opacity-10 text-indigo-500;
|
||||
}
|
||||
|
||||
.sbui-badge--purple {
|
||||
@apply bg-purple-600 bg-opacity-10 text-purple-500;
|
||||
}
|
||||
|
||||
.sbui-badge--pink {
|
||||
@apply bg-pink-600 bg-opacity-10 text-pink-500;
|
||||
} */
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Badge } from './'
|
||||
|
||||
export default {
|
||||
title: 'Displays/Badge',
|
||||
component: Badge,
|
||||
}
|
||||
|
||||
const colors = [
|
||||
'brand',
|
||||
'scale',
|
||||
'tomato',
|
||||
'red',
|
||||
'crimson',
|
||||
'pink',
|
||||
'plum',
|
||||
'purple',
|
||||
'violet',
|
||||
'indigo',
|
||||
'blue',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'grass',
|
||||
'brown',
|
||||
'orange',
|
||||
'sky',
|
||||
'mint',
|
||||
'lime',
|
||||
'yellow',
|
||||
'amber',
|
||||
'gold',
|
||||
'bronze',
|
||||
'gray',
|
||||
'mauve',
|
||||
'slate',
|
||||
'sage',
|
||||
'olive',
|
||||
'sand',
|
||||
]
|
||||
|
||||
const sizes: string[] = ['small', 'large']
|
||||
|
||||
export const Default = (args: any) => <Badge {...args}>Hello world</Badge>
|
||||
|
||||
export const withColor = (args: any) => <Badge {...args}>Hello world</Badge>
|
||||
|
||||
export const withDot = (args: any) => <Badge {...args}>Hello world</Badge>
|
||||
|
||||
export const large = (args: any) => <Badge {...args}>Hello world</Badge>
|
||||
|
||||
export const withDotLarge = (args: any) => <Badge {...args}>Hello world</Badge>
|
||||
|
||||
export const withCustomClassNames = (args: any) => (
|
||||
<Badge {...args}>Hello world</Badge>
|
||||
)
|
||||
|
||||
export const allBadges = () => (
|
||||
<>
|
||||
<div className="flex flex-row gap-6 mx-auto my-4">
|
||||
{sizes.map((size, sizeIndex) => (
|
||||
<>
|
||||
<h3 className="text-scale-900">{size}</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{colors.map((x, colorIndex) => (
|
||||
// @ts-ignore
|
||||
<Badge size={sizes[sizeIndex]} color={colors[colorIndex]}>
|
||||
Supabase
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
Default.args = {}
|
||||
|
||||
withColor.args = {
|
||||
color: 'red',
|
||||
}
|
||||
|
||||
withDot.args = {
|
||||
dot: true,
|
||||
}
|
||||
|
||||
large.args = {
|
||||
size: 'large',
|
||||
}
|
||||
|
||||
withDotLarge.args = {
|
||||
size: 'large',
|
||||
dot: true,
|
||||
}
|
||||
|
||||
withCustomClassNames.args = {
|
||||
className: 'border-teal-100 border-2'
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import Badge from './Badge'
|
||||
|
||||
const SIZES = ['small', 'large']
|
||||
const COLORS = [
|
||||
'gray',
|
||||
'red',
|
||||
'yellow',
|
||||
'green',
|
||||
'blue',
|
||||
'indigo',
|
||||
'purple',
|
||||
'pink'
|
||||
]
|
||||
|
||||
describe('#Badge', () => {
|
||||
it('should render component correctly', () => {
|
||||
const wrapper = render(<Badge>Badge</Badge>)
|
||||
expect(screen.getByText('Badge')).toBeInTheDocument()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should render different text', () => {
|
||||
const wrapper = render(<Badge>Badge</Badge>)
|
||||
expect(screen.getByText('Badge')).toBeInTheDocument()
|
||||
wrapper.rerender(<Badge>徽章</Badge>)
|
||||
expect(screen.getByText('徽章')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with dot', () => {
|
||||
const wrapper = render(<Badge dot={true}>Badge</Badge>)
|
||||
expect(screen.getByText('Badge')).toBeInTheDocument()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it.each(COLORS)('should have %s color', (color) => {
|
||||
render(<Badge color={color}>{color}</Badge>)
|
||||
expect(screen.getByText(color)).toHaveClass(
|
||||
`sbui-badge sbui-badge--${color}`
|
||||
)
|
||||
})
|
||||
|
||||
it.each(SIZES)('should render with %s size', (size) => {
|
||||
render(<Badge size={size}>{size}</Badge>)
|
||||
expect(screen.getByText(size)).toHaveClass(
|
||||
`sbui-badge ${size === 'large' ? 'sbui-badge--large' : ''}`
|
||||
)
|
||||
})
|
||||
|
||||
it('should render with Classes',()=>{
|
||||
render(<Badge className='border-2'>Badge</Badge>)
|
||||
expect(screen.getByText('Badge')).toHaveClass('border-2')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
interface Props {
|
||||
color?:
|
||||
| 'brand'
|
||||
| 'scale'
|
||||
| 'tomato'
|
||||
| 'red'
|
||||
| 'crimson'
|
||||
| 'pink'
|
||||
| 'plum'
|
||||
| 'purple'
|
||||
| 'violet'
|
||||
| 'indigo'
|
||||
| 'blue'
|
||||
| 'cyan'
|
||||
| 'teal'
|
||||
| 'green'
|
||||
| 'grass'
|
||||
| 'brown'
|
||||
| 'orange'
|
||||
| 'sky'
|
||||
| 'mint'
|
||||
| 'lime'
|
||||
| 'yellow'
|
||||
| 'amber'
|
||||
| 'gold'
|
||||
| 'bronze'
|
||||
| 'gray'
|
||||
| 'mauve'
|
||||
| 'slate'
|
||||
| 'sage'
|
||||
| 'olive'
|
||||
| 'sand'
|
||||
children: string | React.ReactNode
|
||||
size?: 'large' | 'small'
|
||||
dot?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Badge({ color = 'brand', children, size, dot, className }: Props) {
|
||||
const __styles = styleHandler('badge')
|
||||
|
||||
let classes = [__styles.base]
|
||||
if (color) {
|
||||
classes.push(__styles.color[color])
|
||||
}
|
||||
if (size === 'large') {
|
||||
classes.push(__styles.size.large)
|
||||
}
|
||||
if (className) {
|
||||
classes.push(className)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes.join(' ')}>
|
||||
{dot && (
|
||||
<svg
|
||||
className={`${__styles.dot} ${__styles.color[color]}`}
|
||||
fill="currentColor"
|
||||
viewBox="0 0 8 8"
|
||||
>
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
export default Badge
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Badge } from './Badge'
|
||||
@@ -0,0 +1,34 @@
|
||||
.sbui-breadcrumb--container {
|
||||
@apply flex flex-row items-center;
|
||||
@apply p-0 my-0;
|
||||
}
|
||||
|
||||
.sbui-breadcrumb--item-container {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.sbui-breadcrumb--item {
|
||||
@apply text-gray-500 hover:text-gray-600;
|
||||
@apply cursor-pointer;
|
||||
@apply dark:text-dark-200 dark:hover:text-brand-700;
|
||||
}
|
||||
|
||||
.sbui-breadcrumb--item__active {
|
||||
@apply text-brand-700 dark:text-brand-700;
|
||||
}
|
||||
|
||||
.sbui-breadcrumb--separator {
|
||||
@apply mx-2;
|
||||
@apply text-gray-500 hover:text-gray-600;
|
||||
@apply dark:text-dark-200 dark:hover:text-white;
|
||||
}
|
||||
|
||||
.sbui-breadcrumb--separator-small {
|
||||
@apply mx-2;
|
||||
}
|
||||
.sbui-breadcrumb--separator-medium {
|
||||
@apply mx-4;
|
||||
}
|
||||
.sbui-breadcrumb--separator-large {
|
||||
@apply mx-6;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import { Breadcrumb } from '../Breadcrumb'
|
||||
import { Textarea } from '../Textarea'
|
||||
import Typography from '../Typography'
|
||||
|
||||
export default {
|
||||
title: 'Navigation/Breadcrumb',
|
||||
component: Breadcrumb,
|
||||
}
|
||||
|
||||
export const Default = ({ children, ...args }) => (
|
||||
<Breadcrumb {...args}>{children}</Breadcrumb>
|
||||
)
|
||||
|
||||
Default.args = {
|
||||
children: [
|
||||
<Breadcrumb.Item>Brothers</Breadcrumb.Item>,
|
||||
<Breadcrumb.Item>Grimm</Breadcrumb.Item>,
|
||||
<Breadcrumb.Item active>Hansel</Breadcrumb.Item>,
|
||||
<Breadcrumb.Item>Gretel</Breadcrumb.Item>,
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import Breadcrumb from './Breadcrumb';
|
||||
|
||||
describe('#Breadcrumb', () => {
|
||||
it('should render breadcrumb correctly', async () => {
|
||||
render(
|
||||
<Breadcrumb data-testid="breadcrumb">
|
||||
<p> Item 1 </p>
|
||||
<p> Item 2 </p>
|
||||
</Breadcrumb>
|
||||
)
|
||||
expect(screen.queryByTestId('breadcrumb')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have "breadcrumb--item" class', () => {
|
||||
render(
|
||||
<Breadcrumb data-testid="breadcrumb">
|
||||
<p> Item 1 </p>
|
||||
<p> Item 2 </p>
|
||||
</Breadcrumb>
|
||||
)
|
||||
expect(screen.queryByTestId('breadcrumb')).toHaveClass('.sbui-breadcrumb--item ')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import { IconChevronRight } from '../Icon/icons/IconChevronRight'
|
||||
// @ts-ignore
|
||||
import BreadcrumbStyle from './Breadcrumb.module.css'
|
||||
|
||||
interface Props {
|
||||
children?: [React.ReactNode]
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
spacing?: 'small' | 'medium' | 'large'
|
||||
}
|
||||
|
||||
const Breadcrumb = ({
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
spacing = 'small',
|
||||
}: Props) => {
|
||||
let classes = [BreadcrumbStyle['sbui-breadcrumb--container']]
|
||||
let seperatorClasses = [BreadcrumbStyle['sbui-breadcrumb--separator']]
|
||||
|
||||
if (className) {
|
||||
classes.push(className)
|
||||
}
|
||||
|
||||
if (spacing) {
|
||||
seperatorClasses.push(`sbui-breadcrumb--separator-${spacing}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<ol className={classes.join(' ')} style={style} aria-label="Breadcrumb">
|
||||
{children!.map((child: React.ReactNode, idx: number) => (
|
||||
<li className={BreadcrumbStyle['sbui-breadcrumb--item-container']}>
|
||||
{child}
|
||||
{idx + 1 < children!.length && (
|
||||
<IconChevronRight
|
||||
size={12}
|
||||
className={seperatorClasses.join(' ')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
children: React.ReactNode
|
||||
active?: boolean
|
||||
onClick?: any
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export function Item({ children, active, onClick, style }: ItemProps) {
|
||||
let classes = [BreadcrumbStyle['sbui-breadcrumb--item']]
|
||||
if (active) classes.push(BreadcrumbStyle['sbui-breadcrumb--item__active'])
|
||||
return (
|
||||
<span
|
||||
className={classes.join(' ')}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
aria-current={active ? 'page' : false}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
Breadcrumb.Item = Item
|
||||
|
||||
export default Breadcrumb
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Breadcrumb } from './Breadcrumb'
|
||||
@@ -0,0 +1,156 @@
|
||||
/* .sbui-btn-container {
|
||||
@apply inline-flex font-medium;
|
||||
} */
|
||||
|
||||
/* .sbui-btn {
|
||||
@apply relative cursor-pointer inline-flex items-center space-x-2 text-center border border-solid border-transparent transition ease-out duration-200 outline-none;
|
||||
@apply rounded;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
} */
|
||||
|
||||
/* .sbui-btn:focus {
|
||||
@apply outline-none;
|
||||
} */
|
||||
|
||||
/* .sbui-btn-container--shadow {
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
||||
} */
|
||||
|
||||
/* .sbui-btn--w-full {
|
||||
@apply w-full flex items-center justify-center;
|
||||
} */
|
||||
|
||||
/* .sbui-btn:disabled {
|
||||
@apply opacity-75 cursor-not-allowed;
|
||||
pointer-events: none;
|
||||
} */
|
||||
|
||||
/*
|
||||
Button sizes
|
||||
*/
|
||||
|
||||
.sbui-btn--tiny {
|
||||
@apply px-2.5 py-1.5 text-xs;
|
||||
}
|
||||
.sbui-btn--small {
|
||||
@apply px-3 py-2 text-sm leading-4;
|
||||
}
|
||||
.sbui-btn--medium {
|
||||
@apply px-4 py-2 text-sm;
|
||||
}
|
||||
.sbui-btn--large {
|
||||
@apply px-4 py-2 text-base;
|
||||
}
|
||||
.sbui-btn--xlarge {
|
||||
@apply px-6 py-3 text-base;
|
||||
}
|
||||
|
||||
/*
|
||||
Button varients
|
||||
*/
|
||||
/* .sbui-btn-primary {
|
||||
@apply text-white bg-brand-800 hover:bg-brand-600;
|
||||
@apply dark:hover:bg-brand-900;
|
||||
} */
|
||||
.dark .sbui-btn-primary {
|
||||
text-shadow: 0px 0px 4px rgba(38 111 78 / 50%);
|
||||
}
|
||||
|
||||
/* .sbui-btn-default {
|
||||
@apply text-gray-500 bg-white border-gray-200 hover:text-gray-600;
|
||||
@apply hover:bg-white hover:border-gray-200;
|
||||
|
||||
@apply dark:border-gray-600 dark:bg-gray-600 dark:text-dark-200;
|
||||
@apply dark:hover:bg-gray-700;
|
||||
} */
|
||||
.dark .sbui-btn-default {
|
||||
text-shadow: 0px 0px 4px rgba(31 31 31 / 50%);
|
||||
}
|
||||
/*
|
||||
.sbui-btn-secondary {
|
||||
@apply text-gray-600 bg-gray-200;
|
||||
@apply hover:text-gray-700 hover:bg-gray-300;
|
||||
|
||||
@apply dark:text-gray-300 dark:bg-gray-500;
|
||||
@apply dark:hover:bg-gray-600;
|
||||
} */
|
||||
.dark .sbui-btn-secondary {
|
||||
text-shadow: 0px 0px 4px rgba(42 42 42 / 50%);
|
||||
}
|
||||
|
||||
/* .sbui-btn-outline {
|
||||
@apply border text-gray-500 bg-transparent border-gray-200 border-solid hover:bg-white hover:text-gray-600 hover:border-gray-600 dark:text-white dark:border-dark-400 dark:hover:text-dark-600 dark:hover:border-white;
|
||||
} */
|
||||
/* .sbui-btn-dashed {
|
||||
@apply border text-gray-500 bg-transparent border-gray-200 border-dashed hover:text-gray-600 hover:border-gray-600;
|
||||
@apply dark:text-white dark:border-dark-400 dark:hover:text-white dark:hover:border-white;
|
||||
} */
|
||||
/* .sbui-btn-link {
|
||||
@apply bg-transparent text-brand-800 border-none hover:bg-brand-900 hover:bg-opacity-25;
|
||||
box-shadow: none;
|
||||
} */
|
||||
/* .sbui-btn-text {
|
||||
@apply text-gray-400 bg-transparent border-none hover:bg-gray-200 hover:bg-opacity-25;
|
||||
@apply dark:text-dark-300 dark:hover:bg-dark-600;
|
||||
box-shadow: none;
|
||||
} */
|
||||
|
||||
/* .sbui-btn--danger {
|
||||
@apply border-red-500 text-red-500;
|
||||
@apply dark:border-red-500 dark:text-red-500;
|
||||
} */
|
||||
|
||||
/* .dark .sbui-btn--danger {
|
||||
text-shadow: 0px 0px 4px rgba(229, 62, 62 / 50%);
|
||||
}
|
||||
|
||||
.sbui-btn-primary.sbui-btn--danger {
|
||||
@apply bg-red-500 text-white;
|
||||
@apply hover:bg-red-600 hover:border-red-600;
|
||||
}
|
||||
.sbui-btn-default.sbui-btn--danger,
|
||||
.sbui-btn-secondary.sbui-btn--danger,
|
||||
.sbui-btn-outline.sbui-btn--danger,
|
||||
.sbui-btn-dashed.sbui-btn--danger,
|
||||
.sbui-btn-link.sbui-btn--danger,
|
||||
.sbui-btn-text.sbui-btn--danger {
|
||||
@apply hover:bg-red-600 hover:text-white hover:border-red-600;
|
||||
} */
|
||||
/* {
|
||||
@apply hover:bg-red-600 hover:text-white hover:border-red-600;
|
||||
} */
|
||||
|
||||
/*
|
||||
Animation for icon
|
||||
*/
|
||||
/* .sbui-btn--anim--spin {
|
||||
@apply animate-spin;
|
||||
} */
|
||||
|
||||
/* @keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
} */
|
||||
|
||||
/* .sbui-btn-loader--center {
|
||||
position: absolute;
|
||||
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
right: 50%;
|
||||
bottom: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: auto 0;
|
||||
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.sbui-btn--text-fade-out span {
|
||||
@apply opacity-0;
|
||||
} */
|
||||
@@ -0,0 +1,260 @@
|
||||
// import React, { useRef, useState } from 'react'
|
||||
// import defaultTheme from '../../theme/defaultTheme'
|
||||
// import { ThemeProvider } from './../ThemeProvider'
|
||||
// import customThemeExample from '../../lib/theme/customThemeExample'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { Button, IconPackage, IconChevronRight } from './../../index'
|
||||
|
||||
export default {
|
||||
title: 'General/Button',
|
||||
component: Button,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => <Button {...args}>Button text</Button>
|
||||
export const withStyles = (args: any) => <Button {...args}>Button text</Button>
|
||||
export const withIcon = (args: any) => <Button {...args}>Button text</Button>
|
||||
export const withIconRight = (args: any) => (
|
||||
<Button {...args}>Button text</Button>
|
||||
)
|
||||
export const withBlock = (args: any) => <Button {...args}>Button text</Button>
|
||||
export const withOnlyIcon = (args: any) => <Button {...args} />
|
||||
export const withOnlyLoading = (args: any) => <Button {...args} />
|
||||
export const withLoadingCentered = (args: any) => (
|
||||
<Button {...args}>Loading icon is centered</Button>
|
||||
)
|
||||
export const withRef = () => {
|
||||
const buttonRef = useRef(null)
|
||||
const [msg, setMsg] = useState('Click button to console.log Ref')
|
||||
|
||||
function onClick() {
|
||||
const message = `button:${buttonRef?.current} `
|
||||
setMsg(message)
|
||||
// console.log(message)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button ref={buttonRef} onClick={onClick}>
|
||||
Button with forwardRef
|
||||
</Button>
|
||||
|
||||
<p style={{ color: '#666666' }}>{msg}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const allButtons = (args: any) => (
|
||||
<>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<Button {...args} size="tiny">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="default">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="secondary">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="alternative">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="link">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="text">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="dashed">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="outline">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="danger">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="tiny" type="warning">
|
||||
Button text
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<Button {...args} size="small">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="default">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="secondary">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="alternative">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="link">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="text">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="dashed">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="outline">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="danger">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="small" type="warning">
|
||||
Button text
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<Button {...args} size="medium">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="default">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="secondary">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="alternative">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="link">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="text">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="dashed">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="outline">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="danger">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="medium" type="warning">
|
||||
Button text
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<Button {...args} size="large">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="default">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="secondary">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="alternative">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="link">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="text">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="dashed">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="outline">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="danger">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="large" type="warning">
|
||||
Button text
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<Button {...args} size="xlarge">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="default">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="secondary">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="alternative">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="link">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="text">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="dashed">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="outline">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="danger">
|
||||
Button text
|
||||
</Button>
|
||||
<Button {...args} size="xlarge" type="warning">
|
||||
Button text
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
export const withCustomTag = (args: any) => (
|
||||
<Button {...args}>Button text</Button>
|
||||
)
|
||||
|
||||
const icon = <IconPackage />
|
||||
|
||||
withIcon.args = {
|
||||
type: 'primary',
|
||||
icon: icon,
|
||||
}
|
||||
|
||||
withIconRight.args = {
|
||||
type: 'primary',
|
||||
iconRight: <IconChevronRight strokeWidth={2} />,
|
||||
}
|
||||
|
||||
withStyles.args = {
|
||||
type: 'primary',
|
||||
style: { backgroundColor: 'red', color: 'yellow' },
|
||||
}
|
||||
|
||||
withBlock.args = {
|
||||
type: 'primary',
|
||||
block: true,
|
||||
}
|
||||
|
||||
withOnlyIcon.args = {
|
||||
icon: icon,
|
||||
}
|
||||
|
||||
withOnlyLoading.args = {
|
||||
loading: true,
|
||||
}
|
||||
|
||||
withLoadingCentered.args = {
|
||||
loading: true,
|
||||
loadingCentered: true,
|
||||
}
|
||||
|
||||
allButtons.args = {
|
||||
loading: false,
|
||||
danger: false,
|
||||
}
|
||||
|
||||
withCustomTag.args = {
|
||||
as: 'span',
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import Button from './Button'
|
||||
|
||||
const SIZES = ['tiny', 'small', 'medium', 'large', 'xlarge']
|
||||
const TYPES = [
|
||||
'primary',
|
||||
'default',
|
||||
'secondary',
|
||||
'outline',
|
||||
'dashed',
|
||||
'link',
|
||||
'text',
|
||||
]
|
||||
|
||||
describe('#Button', () => {
|
||||
it('should render button correctly', () => {
|
||||
const wrapper = render(<Button>Button</Button>)
|
||||
expect(wrapper.getByText('Button')).toBeInTheDocument()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should render different text', () => {
|
||||
const wrapper = render(<Button>Button</Button>)
|
||||
expect(screen.getByText('Button')).toBeInTheDocument()
|
||||
wrapper.rerender(<Button>按钮</Button>)
|
||||
expect(screen.getByText('按钮')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should ignore events when disabled', () => {
|
||||
const WrapperButton = () => {
|
||||
const [state, setState] = React.useState('state1')
|
||||
return (
|
||||
<Button disabled onClick={() => setState('state2')}>
|
||||
{state}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
render(<WrapperButton />)
|
||||
expect(screen.getByText('state1')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByText('state1'))
|
||||
expect(screen.getByText('state1')).toBeInTheDocument()
|
||||
expect(screen.queryByText('state2')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should ignore events when loading', () => {
|
||||
const WrapperButton = () => {
|
||||
const [state, setState] = React.useState('state1')
|
||||
return (
|
||||
<Button loading onClick={() => setState('state2')}>
|
||||
{state}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
render(<WrapperButton />)
|
||||
fireEvent.click(screen.getByText('state1'))
|
||||
expect(screen.queryByText('state2')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have "w-full" class', async () => {
|
||||
render(<Button block>Button Block</Button>)
|
||||
expect(screen.queryByRole('button')).toHaveClass(
|
||||
'sbui-btn sbui-btn-primary sbui-btn--w-full sbui-btn-container--shadow sbui-btn--tiny'
|
||||
)
|
||||
})
|
||||
|
||||
it.each(TYPES)('should have "btn--%s" class', (type) => {
|
||||
render(<Button type={type}>Button Variant</Button>)
|
||||
|
||||
if (type !== 'text' && type !== 'link') {
|
||||
expect(screen.queryByRole('button')).toHaveClass(
|
||||
`sbui-btn sbui-btn-${type} sbui-btn-container--shadow sbui-btn--tiny`
|
||||
)
|
||||
} else {
|
||||
expect(screen.queryByRole('button')).toHaveClass(
|
||||
`sbui-btn sbui-btn-${type} sbui-btn--tiny`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it.each(SIZES)('should have "btn--%s" class', (size) => {
|
||||
render(<Button size={size}>Button</Button>)
|
||||
expect(screen.queryByRole('button')).toHaveClass(
|
||||
`sbui-btn sbui-btn-primary sbui-btn-container--shadow sbui-btn--${size}`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,182 @@
|
||||
import React, { forwardRef, useRef, useImperativeHandle } from 'react'
|
||||
import { IconContext } from '../Icon/IconContext'
|
||||
import { IconLoader } from '../Icon/icons/IconLoader'
|
||||
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
block?: boolean
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||
icon?: React.ReactNode
|
||||
iconRight?: React.ReactNode
|
||||
loading?: boolean
|
||||
loadingCentered?: boolean
|
||||
shadow?: boolean
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
style?: React.CSSProperties
|
||||
type?:
|
||||
| 'primary'
|
||||
| 'default'
|
||||
| 'secondary'
|
||||
| 'alternative'
|
||||
| 'outline'
|
||||
| 'dashed'
|
||||
| 'link'
|
||||
| 'text'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
danger?: boolean
|
||||
htmlType?: 'button' | 'submit' | 'reset'
|
||||
ref?: any
|
||||
ariaSelected?: boolean
|
||||
ariaControls?: string
|
||||
tabIndex?: 0 | -1
|
||||
role?: string
|
||||
textAlign?: 'left' | 'center' | 'right'
|
||||
as?: keyof JSX.IntrinsicElements
|
||||
form?: string
|
||||
}
|
||||
|
||||
interface CustomButtonProps extends React.HTMLAttributes<HTMLOrSVGElement> {}
|
||||
|
||||
export interface RefHandle {
|
||||
// container: () => HTMLElement | null
|
||||
button: () => HTMLButtonElement | null
|
||||
}
|
||||
|
||||
const Button = forwardRef<RefHandle, ButtonProps>(
|
||||
(
|
||||
{
|
||||
block,
|
||||
className,
|
||||
children,
|
||||
danger,
|
||||
disabled = false,
|
||||
onClick,
|
||||
icon,
|
||||
iconRight,
|
||||
loading = false,
|
||||
loadingCentered = false,
|
||||
shadow = true,
|
||||
size = 'tiny',
|
||||
style,
|
||||
type = 'primary',
|
||||
htmlType = 'button',
|
||||
ariaSelected,
|
||||
ariaControls,
|
||||
tabIndex,
|
||||
role,
|
||||
as,
|
||||
textAlign = 'center',
|
||||
...props
|
||||
}: ButtonProps,
|
||||
ref
|
||||
) => {
|
||||
// button ref
|
||||
// const containerRef = useRef<HTMLElement>(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
button: () => {
|
||||
return buttonRef.current
|
||||
},
|
||||
}))
|
||||
|
||||
let __styles = styleHandler('button')
|
||||
|
||||
// styles
|
||||
const showIcon = loading || icon
|
||||
|
||||
let classes = [__styles.base]
|
||||
let containerClasses = [__styles.container]
|
||||
|
||||
classes.push(__styles.type[type])
|
||||
|
||||
if (block) {
|
||||
containerClasses.push(__styles.block)
|
||||
classes.push(__styles.block)
|
||||
}
|
||||
|
||||
if (shadow && type !== 'link' && type !== 'text') {
|
||||
classes.push(__styles.shadow)
|
||||
}
|
||||
|
||||
if (size) {
|
||||
classes.push(__styles.size[size])
|
||||
}
|
||||
|
||||
if (className) {
|
||||
classes.push(className)
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
classes.push(__styles.disabled)
|
||||
}
|
||||
|
||||
const iconLoaderClasses = [__styles.loading]
|
||||
|
||||
// custom button tag
|
||||
const CustomButton: React.FC<CustomButtonProps> = ({ ...props }) => {
|
||||
const Tag = as as keyof JSX.IntrinsicElements
|
||||
return <Tag {...props} />
|
||||
}
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
{showIcon &&
|
||||
(loading ? (
|
||||
<IconLoader size={size} className={iconLoaderClasses.join(' ')} />
|
||||
) : icon ? (
|
||||
<IconContext.Provider value={{ contextSize: size }}>
|
||||
{icon}
|
||||
</IconContext.Provider>
|
||||
) : null)}
|
||||
{children && <span className={__styles.label}>{children}</span>}
|
||||
{iconRight && !loading && (
|
||||
<IconContext.Provider value={{ contextSize: size }}>
|
||||
{iconRight}
|
||||
</IconContext.Provider>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
if (as) {
|
||||
return (
|
||||
<CustomButton
|
||||
{...props}
|
||||
className={classes.join(' ')}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
>
|
||||
{buttonContent}
|
||||
</CustomButton>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
// <span ref={containerRef} className={containerClasses.join(' ')}>
|
||||
<button
|
||||
{...props}
|
||||
ref={buttonRef}
|
||||
className={classes.join(' ')}
|
||||
disabled={loading || (disabled && true)}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
type={htmlType}
|
||||
aria-selected={ariaSelected}
|
||||
aria-controls={ariaControls}
|
||||
tabIndex={tabIndex}
|
||||
role={role}
|
||||
form={props.form}
|
||||
>
|
||||
{buttonContent}
|
||||
</button>
|
||||
// </span>
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export default Button
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Button } from './Button'
|
||||
@@ -0,0 +1,27 @@
|
||||
.sbui-card {
|
||||
@apply flex flex-col bg-white dark:bg-dark-700 rounded-md shadow-lg overflow-hidden relative;
|
||||
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.sbui-card--hoverable {
|
||||
@apply transition transform hover:-translate-y-1 hover:shadow-2xl;
|
||||
}
|
||||
|
||||
.dark .sbui-card {
|
||||
border: 1px solid #2a2a2a;
|
||||
}
|
||||
|
||||
.sbui-card-head {
|
||||
@apply px-8 py-6 flex justify-between;
|
||||
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.dark .sbui-card-head {
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
}
|
||||
|
||||
.sbui-card-content {
|
||||
@apply p-8;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react'
|
||||
import Typography from '../Typography'
|
||||
// import { AutoForm } from 'uniforms'
|
||||
|
||||
import { Card } from './'
|
||||
|
||||
export default {
|
||||
title: 'Displays/Card',
|
||||
component: Card,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => (
|
||||
<Card {...args}>
|
||||
<Typography.Title level={5}>Card content</Typography.Title>
|
||||
<Typography.Title level={5}>Card content</Typography.Title>
|
||||
<Typography.Title level={5}>Card content</Typography.Title>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export const withCover = (args: any) => (
|
||||
<Card {...args}>
|
||||
<Typography.Text type="secondary">Sub title here</Typography.Text>
|
||||
<Typography.Title level={3}>To Do List with Vue.JS</Typography.Title>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export const withMeta = (args: any) => (
|
||||
<Card {...args}>
|
||||
<Card.Meta title={'To Do List with Vue.JS'} description={'To Do List with Vue.JS'}/>
|
||||
</Card>
|
||||
)
|
||||
|
||||
export const withHover = (args: any) => (
|
||||
<Card {...args}>
|
||||
<Card.Meta title={'To Do List with Vue.JS'} description={'To Do List with Vue.JS'}/>
|
||||
</Card>
|
||||
)
|
||||
|
||||
Default.args = {
|
||||
title: 'I am a title',
|
||||
titleExtra: <Typography.Link>Learn more</Typography.Link>,
|
||||
}
|
||||
|
||||
withCover.args = {
|
||||
cover: (
|
||||
<img
|
||||
className="h-64 w-full object-cover"
|
||||
src={'https://supabase.io/new/images/case-study-monitoro.jpg'}
|
||||
alt={'title'}
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
withMeta.args = {
|
||||
title: 'title is here'
|
||||
}
|
||||
|
||||
withHover.args = {
|
||||
title: 'This card can hover',
|
||||
hoverable: true
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
import Typography from '../Typography'
|
||||
// @ts-ignore
|
||||
import CardStyles from './Card.module.css'
|
||||
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
interface CardProps {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
cover?: React.ReactNode
|
||||
description?: string
|
||||
hoverable?: boolean
|
||||
style?: React.CSSProperties
|
||||
title?: string
|
||||
titleExtra?: React.ReactNode
|
||||
}
|
||||
|
||||
function Card({
|
||||
children,
|
||||
className,
|
||||
cover,
|
||||
hoverable,
|
||||
style,
|
||||
title,
|
||||
titleExtra,
|
||||
}: CardProps) {
|
||||
let __styles = styleHandler('card')
|
||||
|
||||
let classes = [__styles.base]
|
||||
if (hoverable) classes.push(__styles.hoverable)
|
||||
if (className) classes.push(className)
|
||||
|
||||
return (
|
||||
<div className={classes.join(' ')} style={style}>
|
||||
{title && (
|
||||
<div className={__styles.head}>
|
||||
<Typography.Text style={{ margin: 0 }}>{title}</Typography.Text>
|
||||
<Typography.Link style={{ margin: 0 }}>{titleExtra}</Typography.Link>
|
||||
</div>
|
||||
)}
|
||||
{cover}
|
||||
<div className={__styles.content}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface MetaProps {
|
||||
title?: string
|
||||
description?: string
|
||||
style?: React.CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
function Meta({ title, description, style, className }: MetaProps) {
|
||||
return (
|
||||
<div style={style} className={className}>
|
||||
<Typography.Title style={{ margin: '0' }} level={5}>
|
||||
{title}
|
||||
</Typography.Title>
|
||||
<div>
|
||||
<Typography.Text type="secondary">{description}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Card.Meta = Meta
|
||||
export default Card
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Card } from './Card'
|
||||
@@ -0,0 +1,127 @@
|
||||
/* clear native styling */
|
||||
/* .sbui-checkbox[type='checkbox'] {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
-webkit-print-color-adjust: exact;
|
||||
color-adjust: exact;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
background-origin: border-box;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
color: #2563eb;
|
||||
background-color: #fff;
|
||||
border-color: #6b7280;
|
||||
border-width: 1px;
|
||||
} */
|
||||
|
||||
/* .sbui-checkbox[type='checkbox']:checked {
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
.sbui-checkbox[type='checkbox']:checked {
|
||||
@apply dark:bg-brand-700;
|
||||
@apply bg-brand-700;
|
||||
border-color: transparent;
|
||||
background-size: 100% 100%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
} */
|
||||
|
||||
/* .sbui-checkbox {
|
||||
@apply border border-solid rounded;
|
||||
|
||||
@apply text-brand-700 border-gray-300 transition-all;
|
||||
@apply hover:border-brand-500 focus:ring-brand-500 focus:outline-none;
|
||||
|
||||
@apply dark:bg-transparent dark:border-dark-400 dark:text-white;
|
||||
@apply dark:hover:border-brand-500;
|
||||
|
||||
margin-top: 2px;
|
||||
} */
|
||||
/*
|
||||
.sbui-checkbox-container {
|
||||
@apply flex cursor-pointer;
|
||||
} */
|
||||
|
||||
/* .sbui-checkbox__label-container {
|
||||
@apply leading-none;
|
||||
} */
|
||||
|
||||
/* .sbui-checkbox__label-container__label {
|
||||
@apply text-gray-700 dark:text-white cursor-pointer mt-0;
|
||||
}
|
||||
|
||||
.sbui-checkbox__label-container__label__span {
|
||||
@apply font-medium;
|
||||
}
|
||||
|
||||
.sbui-checkbox__label-container__label__span .sbui-checkbox__label-text-before,
|
||||
.sbui-checkbox__label-container__label__span .sbui-checkbox__label-text-after {
|
||||
@apply text-gray-400 dark:text-gray-300;
|
||||
}
|
||||
|
||||
.sbui-checkbox__label-container__label__p {
|
||||
@apply mt-0 text-gray-400 dark:text-gray-300;
|
||||
} */
|
||||
|
||||
/*
|
||||
Label sizes
|
||||
*/
|
||||
|
||||
/* .sbui-checkbox-container--tiny .sbui-checkbox__label-container__label,
|
||||
.sbui-checkbox-container--tiny .sbui-checkbox__label-container__label__span,
|
||||
.sbui-checkbox-container--tiny .sbui-checkbox__label-container__label__p {
|
||||
@apply text-xs;
|
||||
}
|
||||
.sbui-checkbox-container--small .sbui-checkbox__label-container__label,
|
||||
.sbui-checkbox-container--small .sbui-checkbox__label-container__label__span,
|
||||
.sbui-checkbox-container--small .sbui-checkbox__label-container__label__p {
|
||||
@apply text-sm;
|
||||
}
|
||||
.sbui-checkbox-container--medium .sbui-checkbox__label-container__label,
|
||||
.sbui-checkbox-container--medium .sbui-checkbox__label-container__label__span,
|
||||
.sbui-checkbox-container--medium .sbui-checkbox__label-container__label__p {
|
||||
@apply text-sm;
|
||||
}
|
||||
.sbui-checkbox-container--large .sbui-checkbox__label-container__label,
|
||||
.sbui-checkbox-container--large .sbui-checkbox__label-container__label__span,
|
||||
.sbui-checkbox-container--large .sbui-checkbox__label-container__label__p {
|
||||
@apply text-base;
|
||||
}
|
||||
.sbui-checkbox-container--xlarge .sbui-checkbox__label-container__label,
|
||||
.sbui-checkbox-container--xlarge .sbui-checkbox__label-container__label__span,
|
||||
.sbui-checkbox-container--xlarge .sbui-checkbox__label-container__label__p {
|
||||
@apply text-base;
|
||||
} */
|
||||
|
||||
/*
|
||||
Checkbox sizes
|
||||
*/
|
||||
|
||||
/* .sbui-checkbox-container--tiny .sbui-checkbox {
|
||||
@apply h-3 w-3 mt-1 mr-3;
|
||||
}
|
||||
|
||||
.sbui-checkbox-container--small .sbui-checkbox {
|
||||
@apply h-3.5 w-3.5 mt-0.5 mr-3.5;
|
||||
}
|
||||
|
||||
.sbui-checkbox-container--medium .sbui-checkbox {
|
||||
@apply h-4 w-4 mt-0.5 mr-3.5;
|
||||
}
|
||||
|
||||
.sbui-checkbox-container--large .sbui-checkbox {
|
||||
@apply h-5 w-5 mt-0.5 mr-4;
|
||||
}
|
||||
|
||||
.sbui-checkbox-container--xlarge .sbui-checkbox {
|
||||
@apply h-5 w-5 mt-0.5 mr-4;
|
||||
} */
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Checkbox } from '.'
|
||||
|
||||
export default {
|
||||
title: 'Data Input/Checkbox',
|
||||
component: Checkbox,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => <Checkbox {...args} />
|
||||
|
||||
export const withGroup = (args: any) => (
|
||||
<Checkbox.Group {...args}>
|
||||
<Checkbox
|
||||
id="checkbox1"
|
||||
name="checkbox1"
|
||||
label="JavaScript"
|
||||
description="JavaScript, often abbreviated as JS, is a programming language that conforms to the ECMAScript specification."
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox2"
|
||||
name="checkbox2"
|
||||
label="Typescript"
|
||||
description="TypeScript is a programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript and adds optional static typing to the language. "
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox3"
|
||||
name="checkbox3"
|
||||
label="React"
|
||||
description="React is an open-source, front end, JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies."
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
)
|
||||
|
||||
export const withGroupHorizontal = (args: any) => (
|
||||
<Checkbox.Group {...args}>
|
||||
<Checkbox
|
||||
id="checkbox1"
|
||||
name="checkbox1"
|
||||
label="JavaScript"
|
||||
description="JavaScript, often abbreviated as JS, is a programming language that conforms to the ECMAScript specification."
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox2"
|
||||
name="checkbox2"
|
||||
label="Typescript"
|
||||
description="TypeScript is a programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript and adds optional static typing to the language. "
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox3"
|
||||
name="checkbox3"
|
||||
label="React"
|
||||
description="React is an open-source, front end, JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies."
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
)
|
||||
|
||||
export const size = (args: any) => (
|
||||
<Checkbox.Group {...args}>
|
||||
<Checkbox
|
||||
id="checkbox1"
|
||||
name="checkbox1"
|
||||
label="JavaScript"
|
||||
description="JavaScript, often abbreviated as JS, is a programming language that conforms to the ECMAScript specification."
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox2"
|
||||
name="checkbox2"
|
||||
label="Typescript"
|
||||
description="TypeScript is a programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript and adds optional static typing to the language. "
|
||||
/>
|
||||
<Checkbox
|
||||
id="checkbox3"
|
||||
name="checkbox3"
|
||||
label="React"
|
||||
description="React is an open-source, front end, JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies."
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
)
|
||||
|
||||
export const withBeforeAndAfterLabels = (args: any) => (
|
||||
<Checkbox.Group {...args} />
|
||||
)
|
||||
|
||||
Default.args = {
|
||||
label: 'This is the label',
|
||||
description: 'This is the description',
|
||||
}
|
||||
|
||||
withGroup.args = {
|
||||
id: 'checkobox-q',
|
||||
label: 'This is the label',
|
||||
description: 'This is the description',
|
||||
disabled: false,
|
||||
size: 'medium',
|
||||
className: 'font-sans',
|
||||
layout: 'vertical',
|
||||
}
|
||||
|
||||
withGroupHorizontal.args = {
|
||||
id: 'checkobox-q',
|
||||
label: 'This is the label',
|
||||
description: 'This is the description',
|
||||
disabled: false,
|
||||
className: 'font-sans',
|
||||
layout: 'horizontal',
|
||||
}
|
||||
|
||||
size.args = {
|
||||
id: 'checkobox-q',
|
||||
label: 'Control the size of the checkboxes',
|
||||
description:
|
||||
'You can add a size just the Group component and it will affect the children',
|
||||
disabled: false,
|
||||
className: 'font-sans',
|
||||
layout: 'horizontal',
|
||||
size: 'tiny',
|
||||
}
|
||||
|
||||
withBeforeAndAfterLabels.args = {
|
||||
label: 'Label',
|
||||
beforeLabel: 'Before : ',
|
||||
afterLabel: ' : After',
|
||||
options: [
|
||||
{
|
||||
label: 'Label',
|
||||
beforeLabel: 'Before : ',
|
||||
afterLabel: ' : After',
|
||||
description: 'Description',
|
||||
},
|
||||
],
|
||||
className: 'font-sans',
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import Checkbox from './Checkbox'
|
||||
|
||||
describe('#Checkbox', () => {
|
||||
it('should render checkbox correctly', () => {
|
||||
render(<Checkbox data-testid="form-checkbox" label="labelIsRequired" />)
|
||||
expect(screen.queryByTestId('form-checkbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show label', () => {
|
||||
render(<Checkbox data-testid="form-checkbox" label="JavaScript" />)
|
||||
expect(screen.queryByText('JavaScript')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,231 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { FormLayout } from '../../lib/Layout/FormLayout'
|
||||
import { CheckboxContext } from './CheckboxContext'
|
||||
// @ts-ignore
|
||||
import CheckboxStyles from './Checkbox.module.css'
|
||||
|
||||
import defaultTheme from '../../lib/theme/defaultTheme'
|
||||
|
||||
import { useFormContext } from '../Form/FormContext'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
export interface InputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
afterLabel?: string
|
||||
beforeLabel?: string
|
||||
description?: string
|
||||
label?: string
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
}
|
||||
|
||||
interface GroupProps {
|
||||
id?: string
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
error?: any
|
||||
descriptionText?: any
|
||||
label?: any
|
||||
afterLabel?: string
|
||||
beforeLabel?: string
|
||||
labelOptional?: any
|
||||
name?: any
|
||||
value?: any
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
options?: Array<InputProps>
|
||||
defaultValue?: string
|
||||
onChange?(x: React.ChangeEvent<HTMLInputElement>): void
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
|
||||
}
|
||||
|
||||
function Group({
|
||||
id,
|
||||
layout = 'vertical',
|
||||
error,
|
||||
descriptionText,
|
||||
label,
|
||||
afterLabel,
|
||||
beforeLabel,
|
||||
labelOptional,
|
||||
children,
|
||||
className,
|
||||
options,
|
||||
onChange,
|
||||
size = 'medium',
|
||||
}: GroupProps) {
|
||||
const parentCallback = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChange) onChange(e)
|
||||
}
|
||||
|
||||
const __styles = styleHandler('checkbox')
|
||||
|
||||
return (
|
||||
<FormLayout
|
||||
label={label}
|
||||
afterLabel={afterLabel}
|
||||
beforeLabel={beforeLabel}
|
||||
labelOptional={labelOptional}
|
||||
layout={layout}
|
||||
id={id}
|
||||
error={error}
|
||||
descriptionText={descriptionText}
|
||||
className={className}
|
||||
size={size}
|
||||
>
|
||||
<CheckboxContext.Provider value={{ parentCallback, parentSize: size }}>
|
||||
<div className={__styles.group}>
|
||||
{options
|
||||
? options.map((option: InputProps) => {
|
||||
return (
|
||||
<Checkbox
|
||||
id={option.id}
|
||||
value={option.value}
|
||||
label={option.label}
|
||||
beforeLabel={option.beforeLabel}
|
||||
afterLabel={option.afterLabel}
|
||||
checked={option.checked}
|
||||
name={option.name}
|
||||
description={option.description}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: children}
|
||||
</div>
|
||||
</CheckboxContext.Provider>
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
className,
|
||||
id = '',
|
||||
name = '',
|
||||
label,
|
||||
afterLabel,
|
||||
beforeLabel,
|
||||
description,
|
||||
checked,
|
||||
value,
|
||||
onChange,
|
||||
onFocus,
|
||||
onBlur,
|
||||
size = 'medium',
|
||||
disabled = false,
|
||||
...props
|
||||
}: InputProps) {
|
||||
const { formContextOnChange, values, handleBlur } = useFormContext()
|
||||
|
||||
const __styles = styleHandler('checkbox')
|
||||
|
||||
return (
|
||||
<CheckboxContext.Consumer>
|
||||
{({ parentCallback, parentSize }) => {
|
||||
// if id does not exist, use label
|
||||
const markupId = id
|
||||
? id
|
||||
: name
|
||||
? name
|
||||
: label
|
||||
? label
|
||||
.toLowerCase()
|
||||
.replace(/^[^A-Z0-9]+/gi, '')
|
||||
.replace(/ /g, '-')
|
||||
: undefined
|
||||
|
||||
// @ts-ignore
|
||||
size = parentSize ? parentSize : size
|
||||
|
||||
// if name does not exist on Radio then use Context Name from Radio.Group
|
||||
// if that fails, use the id
|
||||
const markupName = name ? name : markupId
|
||||
|
||||
// check if checkbox checked is true or false
|
||||
// if neither true or false the checkbox will rely on native control
|
||||
let active = checked ?? undefined
|
||||
|
||||
// if (values && !value) value = values[id || name]
|
||||
|
||||
let containerClasses = [__styles.container]
|
||||
|
||||
function onInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
// '`onChange` callback for parent component
|
||||
if (parentCallback) parentCallback(e)
|
||||
// '`onChange` callback for this component
|
||||
if (onChange) onChange(e)
|
||||
// update form
|
||||
if (formContextOnChange) formContextOnChange(e)
|
||||
}
|
||||
|
||||
if (className) containerClasses.push(className)
|
||||
|
||||
if (values && checked === undefined) active = values[id || name]
|
||||
|
||||
function handleBlurEvent(e: React.FocusEvent<HTMLInputElement>) {
|
||||
if (handleBlur) handleBlur(e)
|
||||
if (onBlur) onBlur(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={containerClasses.join(' ')}>
|
||||
<input
|
||||
id={markupId}
|
||||
name={markupName}
|
||||
type="checkbox"
|
||||
// className={CheckboxStyles['sbui-checkbox']}
|
||||
className={[__styles.base, __styles.size[size]].join(' ')}
|
||||
onChange={onInputChange}
|
||||
onFocus={onFocus ? (event) => onFocus(event) : undefined}
|
||||
onBlur={handleBlurEvent}
|
||||
checked={active}
|
||||
value={value ? value : markupId}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<label
|
||||
className={[__styles.label.base, __styles.label[size]].join(' ')}
|
||||
htmlFor={markupId}
|
||||
>
|
||||
<span>
|
||||
{beforeLabel && (
|
||||
<span
|
||||
className={[
|
||||
__styles.label_before.base,
|
||||
__styles.label_before[size],
|
||||
].join(' ')}
|
||||
>
|
||||
{beforeLabel}
|
||||
</span>
|
||||
)}
|
||||
{label}
|
||||
{afterLabel && (
|
||||
<span
|
||||
className={[
|
||||
__styles.label_after.base,
|
||||
__styles.label_after[size],
|
||||
].join(' ')}
|
||||
>
|
||||
{afterLabel}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{description && (
|
||||
<p
|
||||
className={[
|
||||
__styles.description.base,
|
||||
__styles.description[size],
|
||||
].join(' ')}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</CheckboxContext.Consumer>
|
||||
)
|
||||
}
|
||||
|
||||
Checkbox.Group = Group
|
||||
export default Checkbox
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
// Make sure the shape of the default value passed to
|
||||
// createContext matches the shape that the consumers expect!
|
||||
export const CheckboxContext = createContext({
|
||||
parentCallback: (e: any) => {},
|
||||
parentSize: '',
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
import Checkbox from './Checkbox'
|
||||
export default Checkbox
|
||||
export { default as Checkbox } from './Checkbox'
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
// import { AutoForm } from 'uniforms'
|
||||
|
||||
import { Collapsible } from '.'
|
||||
import { IconChevronUp } from '../..'
|
||||
import { Button } from '../Button'
|
||||
|
||||
export default {
|
||||
title: 'Displays/Collapsible',
|
||||
component: Collapsible,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => {
|
||||
return (
|
||||
<>
|
||||
hello all
|
||||
<Collapsible className="-space-y-px">
|
||||
<Collapsible.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group text-scale-1200 flex justify-between items-center w-full bg-scale-300 rounded border border-scale-500 p-3"
|
||||
>
|
||||
Hello
|
||||
<div className="flex gap-2 items-center">
|
||||
<IconChevronUp className="transition data-open-parent:rotate-0 data-closed-parent:rotate-180" />
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
Turn it on
|
||||
</Button>
|
||||
</div>
|
||||
</button>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div className="group text-scale-1200 flex justify-between items-center w-full bg-scale-300 rounded border border-scale-500 p-3">
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
<Collapsible>
|
||||
<Collapsible.Trigger asChild>
|
||||
<button type="button">Hello</button>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
<h3 className="text-tomato-900">I am content</h3>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Default.args = {
|
||||
placeholder: 'Type text here ...',
|
||||
disabled: false,
|
||||
label: 'Name',
|
||||
layout: 'vertical',
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import * as RadixCollapsible from '@radix-ui/react-collapsible'
|
||||
import { Button } from '../Button'
|
||||
import { IconChevronRight, IconGlobe } from '../..'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
|
||||
export interface CollapsibleProps extends RadixCollapsible.CollapsibleProps {
|
||||
children: React.ReactNode
|
||||
// onChange?: (e: boolean) => void
|
||||
}
|
||||
|
||||
export const Collapsible = ({
|
||||
open = undefined,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: CollapsibleProps) => {
|
||||
// const [_open, setOpen] = React.useState(open)
|
||||
|
||||
// function handleOpenChange(e: boolean) {
|
||||
// console.log(e)
|
||||
// if (onChange) onChange(e)
|
||||
// }
|
||||
return (
|
||||
<RadixCollapsible.Root
|
||||
defaultOpen={props.defaultOpen}
|
||||
open={open}
|
||||
onOpenChange={props.onOpenChange}
|
||||
disabled={props.disabled}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</RadixCollapsible.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export function Trigger({
|
||||
children,
|
||||
asChild,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
asChild?: boolean
|
||||
}) {
|
||||
return (
|
||||
<RadixCollapsible.Trigger asChild={asChild}>
|
||||
{children}
|
||||
</RadixCollapsible.Trigger>
|
||||
)
|
||||
}
|
||||
|
||||
export function Content({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}) {
|
||||
const __styles = styleHandler('collapsible')
|
||||
return (
|
||||
<RadixCollapsible.Content
|
||||
className={[__styles.content, className].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</RadixCollapsible.Content>
|
||||
)
|
||||
}
|
||||
|
||||
Collapsible.Trigger = Trigger
|
||||
Collapsible.Content = Content
|
||||
export default Collapsible
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Collapsible } from './Collapsible'
|
||||
@@ -0,0 +1,65 @@
|
||||
.sbui-contextmenu-item {
|
||||
@apply text-xs;
|
||||
@apply text-typography-body-light dark:text-typography-body-dark;
|
||||
@apply px-4 py-1.5 flex items-center space-x-2;
|
||||
@apply cursor-pointer;
|
||||
@apply focus:bg-bg-secondary-light dark:focus:bg-bg-primary-dark border-none;
|
||||
cursor: 'default';
|
||||
}
|
||||
|
||||
.sbui-contextmenu-item svg {
|
||||
/* @apply text-typography-body-secondary-light dark:text-typography-body-secondary-dark; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu-label {
|
||||
/* @apply text-typography-body-secondary-light dark:text-typography-body-secondary-dark; */
|
||||
/* @apply px-4 py-1.5 flex items-center space-x-2; */
|
||||
/* font-size: 0.65rem; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu-misc {
|
||||
/* @apply px-4 py-1.5; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu-item:focus-visible {
|
||||
/* border: none; */
|
||||
/* outline: none; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu-input {
|
||||
/* @apply flex items-center space-x-2; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu-input__check {
|
||||
/* @apply flex items-center; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu__content {
|
||||
/* @apply bg-bg-primary-light dark:bg-bg-secondary-dark p-0; */
|
||||
/* @apply rounded; */
|
||||
/* @apply border; */
|
||||
/* @apply shadow; */
|
||||
/* overflow: visible; */
|
||||
/* border-style: solid; */
|
||||
/* border-width: 1px; */
|
||||
}
|
||||
|
||||
@keyframes transition {
|
||||
/* 0% { */
|
||||
/* opacity: 0; */
|
||||
/* transform: scale(0.95); */
|
||||
/* } */
|
||||
/* 100% { */
|
||||
/* opacity: 1; */
|
||||
/* transform: scale(1); */
|
||||
/* } */
|
||||
}
|
||||
|
||||
.sbui-contextmenu__content {
|
||||
/* transform-origin: var(--radix-dropdown-menu-content-transform-origin); */
|
||||
/* animation: transition 0.1s ease-out; */
|
||||
}
|
||||
|
||||
.sbui-contextmenu__trigger {
|
||||
/* @apply border-none bg-transparent p-0 focus:ring-0; */
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from '../Button'
|
||||
import { Divider } from '../Divider'
|
||||
import { IconSettings, IconLogOut } from '../../index'
|
||||
import Typography from '../Typography'
|
||||
|
||||
import { ContextMenu } from '.'
|
||||
import { IconLogIn } from '../Icon/icons/IconLogIn'
|
||||
|
||||
export default {
|
||||
title: 'Navigation/ContextMenu',
|
||||
component: ContextMenu,
|
||||
}
|
||||
|
||||
const triggerArea = (
|
||||
<div
|
||||
style={{
|
||||
background: 'gray',
|
||||
width: '280px',
|
||||
height: '180px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<span>Right click this area</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const Default = (args: any) => (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<ContextMenu
|
||||
{...args}
|
||||
overlay={[
|
||||
<ContextMenu.Misc>
|
||||
<div>
|
||||
<Typography.Text small className="block">
|
||||
Signed in as{' '}
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text small strong>
|
||||
tom@example.com{' '}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</ContextMenu.Misc>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Label>Group label</ContextMenu.Label>,
|
||||
<ContextMenu.Item onClick={() => console.log('clicked')}>
|
||||
Account
|
||||
</ContextMenu.Item>,
|
||||
<ContextMenu.Item>Settings</ContextMenu.Item>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Item icon={<IconLogIn size="tiny" />}>
|
||||
Log out
|
||||
</ContextMenu.Item>,
|
||||
]}
|
||||
>
|
||||
{triggerArea}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
)
|
||||
|
||||
Default.args = {}
|
||||
|
||||
export const doNotcloseOverlay = (args: any) => (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<ContextMenu
|
||||
{...args}
|
||||
overlay={[
|
||||
<ContextMenu.Misc>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</ContextMenu.Misc>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Item>Account</ContextMenu.Item>,
|
||||
<ContextMenu.Item>Settings</ContextMenu.Item>,
|
||||
<ContextMenu.Item>
|
||||
<Button icon={<IconLogOut />}>Log out</Button>
|
||||
</ContextMenu.Item>,
|
||||
]}
|
||||
>
|
||||
{triggerArea}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
)
|
||||
|
||||
doNotcloseOverlay.args = {}
|
||||
|
||||
export const withCustomStyles = (args: any) => (
|
||||
<div
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
minHeight: '420px',
|
||||
marginTop: '220px',
|
||||
marginLeft: '400px',
|
||||
}}
|
||||
>
|
||||
<ContextMenu
|
||||
overlayStyle={{ minWidth: '500px' }}
|
||||
placement="bottomRight"
|
||||
{...args}
|
||||
overlay={[
|
||||
<ContextMenu.Item>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</ContextMenu.Item>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Item>Account</ContextMenu.Item>,
|
||||
<ContextMenu.Item>Settings</ContextMenu.Item>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Item>
|
||||
<Button type="default" icon={<IconLogOut />}>
|
||||
Log out
|
||||
</Button>
|
||||
</ContextMenu.Item>,
|
||||
]}
|
||||
>
|
||||
{triggerArea}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
)
|
||||
|
||||
withCustomStyles.args = {}
|
||||
|
||||
export const Checkbox = (args: any) => {
|
||||
const [checked, setChecked] = useState(false)
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<ContextMenu
|
||||
{...args}
|
||||
overlay={[
|
||||
<ContextMenu.Item icon={<IconSettings size="small" />}>
|
||||
Account
|
||||
</ContextMenu.Item>,
|
||||
<ContextMenu.Item>Settings</ContextMenu.Item>,
|
||||
<Divider light />,
|
||||
<ContextMenu.Checkbox checked={checked} onChange={setChecked}>
|
||||
Show subtitles
|
||||
</ContextMenu.Checkbox>,
|
||||
]}
|
||||
>
|
||||
{triggerArea}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Radio = (args: any) => {
|
||||
const [value, setValue] = useState('red')
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<ContextMenu
|
||||
{...args}
|
||||
overlay={[
|
||||
<ContextMenu.RadioGroup value={value} onChange={setValue}>
|
||||
<ContextMenu.Radio value={'red'}>Red</ContextMenu.Radio>
|
||||
<ContextMenu.Radio value={'blue'}>Blue</ContextMenu.Radio>
|
||||
<ContextMenu.Radio value={'green'}>Green</ContextMenu.Radio>
|
||||
</ContextMenu.RadioGroup>,
|
||||
]}
|
||||
>
|
||||
{triggerArea}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
// import * as RadixContextMenu from '@radix-ui/react-dropdown-menu'
|
||||
import { IconCheck } from '../Icon/icons/IconCheck'
|
||||
|
||||
// @ts-ignore
|
||||
// import ContextMenuStyles from './ContextMenu.module.css'
|
||||
|
||||
import type * as RadixContextMenuTypes from '@radix-ui/react-context-menu/'
|
||||
|
||||
import * as RadixContextMenu from '@radix-ui/react-context-menu'
|
||||
|
||||
const ContextMenuStyles = {}
|
||||
|
||||
interface RootProps {
|
||||
onOpenChange?(x: boolean): void
|
||||
alignOffset?: RadixContextMenuTypes.ContextMenuContentProps['alignOffset']
|
||||
overlay?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
function ContextMenu({
|
||||
onOpenChange,
|
||||
|
||||
alignOffset = 6,
|
||||
overlay,
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
}: RootProps) {
|
||||
// let classes = [ContextMenuStyles['sbui-contextmenu__content']]
|
||||
// if (className) {
|
||||
// classes.push(className)
|
||||
// }
|
||||
return (
|
||||
<RadixContextMenu.Root onOpenChange={onOpenChange}>
|
||||
<RadixContextMenu.Trigger
|
||||
// className={ContextMenuStyles['sbui-contextmenu__trigger']}
|
||||
>
|
||||
{children}
|
||||
</RadixContextMenu.Trigger>
|
||||
|
||||
<RadixContextMenu.Content
|
||||
sideOffset={alignOffset}
|
||||
// className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{overlay}
|
||||
</RadixContextMenu.Content>
|
||||
</RadixContextMenu.Root>
|
||||
)
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
children: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: (event: Event) => void
|
||||
}
|
||||
|
||||
export function Item({ children, icon, disabled, onClick }: ItemProps) {
|
||||
return (
|
||||
<RadixContextMenu.Item
|
||||
// className={ContextMenuStyles['sbui-contextmenu-item']}
|
||||
disabled={disabled}
|
||||
onSelect={onClick}
|
||||
>
|
||||
{icon && icon}
|
||||
<span>{children}</span>
|
||||
</RadixContextMenu.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export function Misc({ children, icon }: ItemProps) {
|
||||
return (
|
||||
<div
|
||||
// className={ContextMenuStyles['sbui-contextmenu-misc']}
|
||||
>
|
||||
{icon && icon}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface CheckboxProps {
|
||||
children: React.ReactNode
|
||||
checked?: boolean
|
||||
onChange?(x: boolean): void
|
||||
disabled?: boolean
|
||||
ItemIndicator?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
children,
|
||||
checked: propsChecked,
|
||||
onChange,
|
||||
disabled,
|
||||
ItemIndicator,
|
||||
}: CheckboxProps) {
|
||||
const [checked, setChecked] = useState(propsChecked ? propsChecked : false)
|
||||
|
||||
const handleChange = (e: boolean) => {
|
||||
if (onChange) onChange(e)
|
||||
setChecked(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixContextMenu.CheckboxItem
|
||||
checked={checked}
|
||||
onCheckedChange={handleChange}
|
||||
// className={`${ContextMenuStyles['sbui-contextmenu-item']} ${ContextMenuStyles['sbui-contextmenu-input']}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RadixContextMenu.ItemIndicator
|
||||
// className={ContextMenuStyles['sbui-contextmenu-input__check']}
|
||||
>
|
||||
{ItemIndicator ? ItemIndicator : <IconCheck size="tiny" />}
|
||||
<RadixContextMenu.CheckboxItem />
|
||||
</RadixContextMenu.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
</RadixContextMenu.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
ItemIndicator?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Radio({ children, value, ItemIndicator }: RadioProps) {
|
||||
return (
|
||||
<RadixContextMenu.RadioItem
|
||||
value={value}
|
||||
// className={`${ContextMenuStyles['sbui-contextmenu-item']} ${ContextMenuStyles['sbui-contextmenu-input']}`}
|
||||
>
|
||||
<RadixContextMenu.ItemIndicator
|
||||
// className={ContextMenuStyles['sbui-contextmenu-input__check']}
|
||||
>
|
||||
{ItemIndicator ? ItemIndicator : <IconCheck size="tiny" />}
|
||||
</RadixContextMenu.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
</RadixContextMenu.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioGroupProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
onChange?(x: string): void
|
||||
}
|
||||
|
||||
export function RadioGroup({
|
||||
children,
|
||||
value: propsValue,
|
||||
onChange,
|
||||
}: RadioGroupProps) {
|
||||
const [value, setValue] = useState(propsValue ? propsValue : '')
|
||||
|
||||
const handleChange = (e: string) => {
|
||||
if (onChange) onChange(e)
|
||||
setValue(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixContextMenu.RadioGroup value={value} onValueChange={handleChange}>
|
||||
{children}
|
||||
</RadixContextMenu.RadioGroup>
|
||||
)
|
||||
}
|
||||
|
||||
interface LabelProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Label({ children }: LabelProps) {
|
||||
return (
|
||||
<RadixContextMenu.Label
|
||||
// className={ContextMenuStyles['sbui-contextmenu-label']}
|
||||
>
|
||||
{children}
|
||||
</RadixContextMenu.Label>
|
||||
)
|
||||
}
|
||||
|
||||
ContextMenu.Item = Item
|
||||
ContextMenu.Misc = Misc
|
||||
ContextMenu.Checkbox = Checkbox
|
||||
ContextMenu.Radio = Radio
|
||||
ContextMenu.RadioGroup = RadioGroup
|
||||
ContextMenu.Label = Label
|
||||
export default ContextMenu
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ContextMenu } from './ContextMenu'
|
||||
@@ -0,0 +1,101 @@
|
||||
.sbui-divider {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sbui-divider-vertical {
|
||||
@apply bg-gray-200 dark:bg-dark-500;
|
||||
width: 1px;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.sbui-divider-vertical.sbui-divider--light {
|
||||
@apply bg-gray-100 dark:bg-dark-600;
|
||||
}
|
||||
|
||||
.sbui-divider--no-text {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.sbui-divider > span {
|
||||
@apply text-sm;
|
||||
@apply text-gray-300;
|
||||
@apply dark:text-dark-400;
|
||||
display: inline-block;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.sbui-divider--left:before {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.sbui-divider--left:after {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.sbui-divider--right:before {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.sbui-divider--right:after {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.sbui-divider--center:before,
|
||||
.sbui-divider--center:after {
|
||||
@apply border-gray-200 dark:border-dark-500;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.sbui-divider--light.sbui-divider--left:before,
|
||||
.sbui-divider--light.sbui-divider--left:after,
|
||||
.sbui-divider--light.sbui-divider--right:before,
|
||||
.sbui-divider--light.sbui-divider--right:after,
|
||||
.sbui-divider--light.sbui-divider--center:before,
|
||||
.sbui-divider--light.sbui-divider--center:after,
|
||||
.sbui-divider--light.sbui-divider--no-text {
|
||||
@apply border-gray-100 dark:border-dark-600;
|
||||
}
|
||||
|
||||
.sbui-divider--light.sbui-divider > span {
|
||||
@apply text-gray-200;
|
||||
@apply dark:text-dark-800;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react'
|
||||
import { Button } from '../Button'
|
||||
import Typography from '../Typography'
|
||||
import { Divider } from './'
|
||||
|
||||
export default {
|
||||
title: 'Utilities/Divider',
|
||||
component: Divider,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => <Divider {...args} />
|
||||
|
||||
export const withCenterText = (args: any) => (
|
||||
<Divider {...args}>Hello world</Divider>
|
||||
)
|
||||
|
||||
export const withLeftText = (args: any) => (
|
||||
<Divider {...args}>Hello world</Divider>
|
||||
)
|
||||
|
||||
export const withRightText = (args: any) => (
|
||||
<Divider {...args}>Hello world</Divider>
|
||||
)
|
||||
|
||||
export const lighterColor = (args: any) => <Divider {...args} />
|
||||
|
||||
export const vertical = (args: any) => (
|
||||
<div style={{ height: '32px' }}>
|
||||
<div className="space-x-3 w-full">
|
||||
<Divider {...args} />
|
||||
<Button>Button one</Button>
|
||||
<Divider {...args} />
|
||||
<Button>Button two</Button>
|
||||
<Divider {...args} />
|
||||
<Typography.Text>Some text</Typography.Text>
|
||||
<Divider {...args} />
|
||||
<Button>Button three</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Default.args = {}
|
||||
|
||||
withCenterText.args = {}
|
||||
|
||||
withLeftText.args = {
|
||||
orientation: 'left',
|
||||
}
|
||||
|
||||
withRightText.args = {
|
||||
orientation: 'right',
|
||||
}
|
||||
|
||||
lighterColor.args = {
|
||||
light: true,
|
||||
}
|
||||
|
||||
vertical.args = {
|
||||
type: 'vertical',
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
// import DividerStyles from './Divider.module.css'
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
light?: boolean
|
||||
orientation?: 'left' | 'right' | 'center'
|
||||
style?: React.CSSProperties
|
||||
type?: 'vertical' | 'horizontal'
|
||||
}
|
||||
|
||||
export default function Divider({
|
||||
children,
|
||||
className,
|
||||
light = false,
|
||||
orientation = 'center',
|
||||
style,
|
||||
type = 'horizontal',
|
||||
}: Props) {
|
||||
// let classes = [
|
||||
// type === 'horizontal'
|
||||
// ? DividerStyles['sbui-divider']
|
||||
// : DividerStyles['sbui-divider-vertical'],
|
||||
// ]
|
||||
// if (light) classes.push(DividerStyles['sbui-divider--light'])
|
||||
|
||||
// if (children) {
|
||||
// classes.push(DividerStyles[`sbui-divider--${orientation}`])
|
||||
// } else if (!children && type === 'horizontal') {
|
||||
// classes.push(DividerStyles[`sbui-divider--no-text`])
|
||||
// }
|
||||
|
||||
// if (className) classes.push(className)
|
||||
|
||||
return (
|
||||
<div
|
||||
// className={classes.join(' ')}
|
||||
role="seperator"
|
||||
style={style}
|
||||
>
|
||||
{children && (
|
||||
<span
|
||||
// className={DividerStyles['sbui-divider__content']}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Divider } from './Divider'
|
||||
@@ -0,0 +1,112 @@
|
||||
/* .sbui-dropdown-item {
|
||||
@apply text-xs;
|
||||
@apply text-typography-body-light dark:text-typography-body-dark;
|
||||
@apply px-4 py-1.5 flex items-center space-x-2;
|
||||
@apply cursor-pointer;
|
||||
@apply focus:bg-bg-secondary-light dark:focus:bg-bg-primary-dark border-none;
|
||||
cursor: 'default';
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* .sbui-dropdown-item svg {
|
||||
@apply text-typography-body-secondary-light dark:text-typography-body-secondary-dark;
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown-label {
|
||||
@apply text-typography-body-secondary-light dark:text-typography-body-secondary-dark;
|
||||
@apply px-4 py-1.5 flex items-center space-x-2;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.sbui-dropdown-misc {
|
||||
@apply px-4 py-1.5;
|
||||
}
|
||||
|
||||
.sbui-dropdown-item:focus-visible {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sbui-dropdown-input {
|
||||
@apply flex items-center space-x-0 pl-8 pr-4;
|
||||
}
|
||||
|
||||
.sbui-dropdown-input__check {
|
||||
@apply absolute left-2;
|
||||
@apply flex items-center;
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown__content {
|
||||
@apply bg-bg-primary-light dark:bg-bg-secondary-dark p-0;
|
||||
@apply border border-lightmode dark:border-darkmode;
|
||||
@apply rounded;
|
||||
@apply shadow;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
} */
|
||||
|
||||
/* @keyframes fadeIn {
|
||||
from {
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
} */
|
||||
/* .sbui-dropdown__content {
|
||||
transform-origin: 'var(--radix-dropdown-menu-content-transform-origin)';
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown__content[data-state='open'] {
|
||||
animation: fadeIn 50ms ease-out;
|
||||
}
|
||||
|
||||
.sbui-dropdown__content[data-state='closed'] {
|
||||
animation: fadeOut 50ms ease-in;
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown__trigger {
|
||||
@apply border-none bg-transparent p-0 focus:ring-0;
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown__arrow {
|
||||
@apply fill-current text-bg-primary-light dark:text-bg-secondary-dark;
|
||||
@apply border-0 border-t;
|
||||
border-style: solid;
|
||||
|
||||
}
|
||||
|
||||
.sbui-dropdown-item-trigger {
|
||||
@apply text-xs;
|
||||
@apply text-typography-body-light dark:text-typography-body-dark;
|
||||
@apply px-4 py-1.5 flex items-center space-x-2;
|
||||
@apply cursor-pointer;
|
||||
cursor: 'default';
|
||||
}
|
||||
|
||||
.sbui-dropdown-item-trigger svg {
|
||||
@apply text-typography-body-secondary-light dark:text-typography-body-secondary-dark;
|
||||
}
|
||||
|
||||
.sbui-dropdown-item-trigger:focus-visible {
|
||||
border: none;
|
||||
outline: none;
|
||||
} */
|
||||
|
||||
/* .sbui-dropdown__trigger-item[data-state='open'] {
|
||||
@apply bg-bg-secondary-light dark:bg-bg-primary-dark border-none;
|
||||
} */
|
||||
@@ -0,0 +1,347 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from '../Button'
|
||||
import { Divider } from '../Divider'
|
||||
import {
|
||||
IconSettings,
|
||||
IconLogOut,
|
||||
IconChevronDown,
|
||||
IconChevronRight,
|
||||
IconHardDrive,
|
||||
} from './../../index'
|
||||
import Typography from '../Typography'
|
||||
|
||||
import { Dropdown } from './'
|
||||
import { IconLogIn } from '../Icon/icons/IconLogIn'
|
||||
import { Input } from '../Input'
|
||||
import { IconSearch } from '../Icon/icons/IconSearch'
|
||||
|
||||
export default {
|
||||
title: 'Navigation/Dropdown',
|
||||
component: Dropdown,
|
||||
}
|
||||
|
||||
const DemoContainer = ({ children }: { children: React.ReactNode }) => (
|
||||
<div
|
||||
className="
|
||||
flex justify-center items-center
|
||||
w-screen h-screen
|
||||
"
|
||||
>
|
||||
<div
|
||||
className="
|
||||
p-3 px-5
|
||||
bg-scale-300 border border-scale-600 rounded flex gap-4
|
||||
justify-between
|
||||
items-center
|
||||
w-3/4
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<h4 className="text-scale-1200 text-base">
|
||||
Shall we nest some components?
|
||||
</h4>
|
||||
<p className="text-scale-1100 text-sm">yea sure, go on then.</p>
|
||||
</div>
|
||||
<div className="relative">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const DefaultFull = (args: any) => {
|
||||
const [checked, setChecked] = useState(false)
|
||||
const [value, setValue] = useState('red')
|
||||
|
||||
return (
|
||||
<DemoContainer>
|
||||
<Dropdown
|
||||
side="bottom"
|
||||
align="end"
|
||||
overlay={[
|
||||
<Dropdown.Misc>
|
||||
<div>
|
||||
<Typography.Text small className="block">
|
||||
Signed in as{' '}
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text small strong>
|
||||
tom@example.com{' '}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Dropdown.Misc>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Label>Group label</Dropdown.Label>,
|
||||
<Dropdown.Item onClick={() => console.log('clicked')}>
|
||||
Account
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
Settings <Dropdown.RightSlot>⌘+T</Dropdown.RightSlot>
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Checkbox checked={checked} onChange={setChecked}>
|
||||
Show subtitles
|
||||
</Dropdown.Checkbox>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.RadioGroup value={value} onChange={setValue}>
|
||||
<Dropdown.Radio value={'red'}>Red</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'blue'}>Blue</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'green'}>Green</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown
|
||||
isNested
|
||||
overlay={[
|
||||
<Dropdown.RadioGroup value={value} onChange={setValue}>
|
||||
<Dropdown.Radio value={'red'}>Red</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'blue'}>Blue</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'green'}>Green</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Label>Group label</Dropdown.Label>,
|
||||
<Dropdown.Item onClick={() => console.log('clicked')}>
|
||||
Account
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
Settings <Dropdown.RightSlot>⌘+T</Dropdown.RightSlot>
|
||||
</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Dropdown.TriggerItem icon={<IconHardDrive />}>
|
||||
Open sub menu{' '}
|
||||
<Dropdown.RightSlot>
|
||||
<IconChevronRight size={14} />
|
||||
</Dropdown.RightSlot>
|
||||
</Dropdown.TriggerItem>
|
||||
</Dropdown>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item icon={<IconLogIn size="tiny" />}>
|
||||
Log out
|
||||
</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="default" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</DemoContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export const Default = (args: any) => (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.Misc>
|
||||
<div>
|
||||
<Typography.Text small className="block">
|
||||
Signed in as{' '}
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text small strong>
|
||||
tom@example.com{' '}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Dropdown.Misc>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Label>Group label</Dropdown.Label>,
|
||||
<Dropdown.Item onClick={() => console.log('clicked')}>
|
||||
Account
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
Settings <Dropdown.RightSlot>⌘+T</Dropdown.RightSlot>
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item icon={<IconLogIn size="tiny" />}>Log out</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="secondary" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
Default.args = {}
|
||||
|
||||
export const doNotcloseOverlay = (args: any) => (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.Misc>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</Dropdown.Misc>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item>Account</Dropdown.Item>,
|
||||
<Dropdown.Item>Settings</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
<Button icon={<IconLogOut />}>Log out</Button>
|
||||
</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
doNotcloseOverlay.args = {}
|
||||
|
||||
export const withCustomStyles = (args: any) => (
|
||||
<div
|
||||
style={{
|
||||
margin: '0 auto',
|
||||
minHeight: '420px',
|
||||
marginTop: '220px',
|
||||
marginLeft: '400px',
|
||||
}}
|
||||
>
|
||||
<Dropdown
|
||||
overlayStyle={{ minWidth: '500px' }}
|
||||
placement="bottomRight"
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.Item>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item>Account</Dropdown.Item>,
|
||||
<Dropdown.Item>Settings</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item>
|
||||
<Button type="default" icon={<IconLogOut />}>
|
||||
Log out
|
||||
</Button>
|
||||
</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
withCustomStyles.args = {}
|
||||
|
||||
export const SearchList = (args: any) => (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.Item>
|
||||
<Input size="tiny" icon={<IconSearch />} autofocus={false} />
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>
|
||||
<Typography.Text>Signed in as </Typography.Text>
|
||||
<Typography.Text strong>tom@example.com </Typography.Text>
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Item icon={<IconLogIn />}>
|
||||
<Typography.Text>Log out</Typography.Text>
|
||||
</Dropdown.Item>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
SearchList.args = {}
|
||||
|
||||
export const Checkbox = (args: any) => {
|
||||
const [checked, setChecked] = useState(false)
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.Item icon={<IconSettings size="small" />}>
|
||||
Account
|
||||
</Dropdown.Item>,
|
||||
<Dropdown.Item>Settings</Dropdown.Item>,
|
||||
<Dropdown.Seperator />,
|
||||
<Dropdown.Checkbox checked={checked} onChange={setChecked}>
|
||||
Show subtitles
|
||||
</Dropdown.Checkbox>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Radio = (args: any) => {
|
||||
const [value, setValue] = useState('red')
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={[
|
||||
<Dropdown.RadioGroup value={value} onChange={setValue}>
|
||||
<Dropdown.Radio value={'red'}>Red</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'blue'}>Blue</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'green'}>Green</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>,
|
||||
]}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Nested = (args: any) => {
|
||||
const [value, setValue] = useState('red')
|
||||
|
||||
return (
|
||||
<div style={{ margin: '0 auto', minHeight: '420px', marginTop: '220px' }}>
|
||||
<Dropdown
|
||||
{...args}
|
||||
overlay={
|
||||
<>
|
||||
<Dropdown.RadioGroup value={value} onChange={setValue}>
|
||||
<Dropdown.Radio value={'red'}>Red</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'blue'}>Blue</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'green'}>Green</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>
|
||||
|
||||
<Dropdown
|
||||
isNested
|
||||
overlay={[
|
||||
<Dropdown.RadioGroup value={value} onChange={setValue}>
|
||||
<Dropdown.Radio value={'red'}>Red</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'blue'}>Blue</Dropdown.Radio>
|
||||
<Dropdown.Radio value={'green'}>Green</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>,
|
||||
]}
|
||||
>
|
||||
<Dropdown.TriggerItem>Open sub menu</Dropdown.TriggerItem>
|
||||
</Dropdown>
|
||||
<Dropdown.Item>hello</Dropdown.Item>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button as="span" type="outline" iconRight={<IconChevronDown />}>
|
||||
Click for dropdown
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import * as RadixDropdown from '@radix-ui/react-dropdown-menu'
|
||||
import { IconCheck } from '../Icon/icons/IconCheck'
|
||||
|
||||
// @ts-ignore
|
||||
// import DropdownStyles from './Dropdown.module.css'
|
||||
|
||||
import type * as RadixDropdownTypes from '@radix-ui/react-dropdown-menu/'
|
||||
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { IconTarget } from '../..'
|
||||
|
||||
interface RootProps {
|
||||
open?: boolean
|
||||
arrow?: boolean
|
||||
onOpenChange?: RadixDropdownTypes.DropdownMenuProps['onOpenChange'] // DropdownMenu['onOpenChange']
|
||||
side?: RadixDropdownTypes.DropdownMenuContentProps['side']
|
||||
align?: RadixDropdownTypes.DropdownMenuContentProps['align']
|
||||
sideOffset?: RadixDropdownTypes.DropdownMenuContentProps['sideOffset']
|
||||
overlay?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' | 'content'
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
isNested?: Boolean
|
||||
}
|
||||
|
||||
function Dropdown({
|
||||
open,
|
||||
onOpenChange,
|
||||
align = 'center', //Default value
|
||||
side = 'bottom', //Default value
|
||||
sideOffset = 6,
|
||||
overlay,
|
||||
children,
|
||||
size = 'medium',
|
||||
className,
|
||||
style,
|
||||
arrow,
|
||||
isNested,
|
||||
}: RootProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
let classes = [__styles.content, __styles.size[size]]
|
||||
if (className) {
|
||||
classes.push(className)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixDropdown.Root onOpenChange={onOpenChange} open={open}>
|
||||
{isNested ? (
|
||||
<RadixDropdown.TriggerItem className={[__styles.item_nested].join(' ')}>
|
||||
{children}
|
||||
</RadixDropdown.TriggerItem>
|
||||
) : (
|
||||
<RadixDropdown.Trigger className={__styles.trigger}>
|
||||
{children}
|
||||
</RadixDropdown.Trigger>
|
||||
)}
|
||||
|
||||
<RadixDropdown.Content
|
||||
portalled={true}
|
||||
sideOffset={sideOffset}
|
||||
side={side}
|
||||
align={align}
|
||||
className={classes.join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{arrow && (
|
||||
<RadixDropdown.Arrow
|
||||
className={__styles.arrow}
|
||||
offset={10}
|
||||
></RadixDropdown.Arrow>
|
||||
)}
|
||||
{overlay}
|
||||
</RadixDropdown.Content>
|
||||
</RadixDropdown.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export function RightSlot({ children }: any) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
return <div className={__styles.right_slot}>{children}</div>
|
||||
}
|
||||
|
||||
interface ItemProps {
|
||||
children: React.ReactNode
|
||||
icon?: React.ReactNode
|
||||
disabled?: boolean
|
||||
onClick?: (event: Event) => void
|
||||
rightSlot?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Item({
|
||||
children,
|
||||
icon,
|
||||
disabled,
|
||||
onClick,
|
||||
rightSlot,
|
||||
}: ItemProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return (
|
||||
<RadixDropdown.Item
|
||||
className={__styles.item}
|
||||
disabled={disabled}
|
||||
onSelect={onClick}
|
||||
>
|
||||
{icon && icon}
|
||||
<span>{children}</span>
|
||||
</RadixDropdown.Item>
|
||||
)
|
||||
}
|
||||
|
||||
export function TriggerItem({ children, icon, disabled }: ItemProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
return (
|
||||
<div className={__styles.item}>
|
||||
{icon && icon}
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Misc({ children, icon }: ItemProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
return (
|
||||
<div className={__styles.misc}>
|
||||
{icon && icon}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface CheckboxProps {
|
||||
children: React.ReactNode
|
||||
checked?: boolean
|
||||
onChange?(x: boolean): void
|
||||
disabled?: boolean
|
||||
ItemIndicator?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Seperator() {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return <RadixDropdown.Separator className={__styles.seperator} />
|
||||
}
|
||||
|
||||
export function Checkbox({
|
||||
children,
|
||||
checked: propsChecked,
|
||||
onChange,
|
||||
disabled,
|
||||
ItemIndicator,
|
||||
}: CheckboxProps) {
|
||||
const [checked, setChecked] = useState(propsChecked ? propsChecked : false)
|
||||
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
const handleChange = (e: boolean) => {
|
||||
if (onChange) onChange(e)
|
||||
setChecked(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixDropdown.CheckboxItem
|
||||
checked={checked}
|
||||
onCheckedChange={handleChange}
|
||||
className={`${__styles.item} ${__styles.input}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
<RadixDropdown.ItemIndicator className={__styles.check}>
|
||||
{ItemIndicator ? (
|
||||
ItemIndicator
|
||||
) : (
|
||||
<IconCheck size="tiny" strokeWidth={3} />
|
||||
)}
|
||||
<RadixDropdown.CheckboxItem />
|
||||
</RadixDropdown.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
</RadixDropdown.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
ItemIndicator?: React.ReactNode
|
||||
}
|
||||
|
||||
export function Radio({ children, value, ItemIndicator }: RadioProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return (
|
||||
<RadixDropdown.RadioItem
|
||||
value={value}
|
||||
className={`${__styles.item} ${__styles.input}`}
|
||||
>
|
||||
<RadixDropdown.ItemIndicator className={__styles.check}>
|
||||
{ItemIndicator ? (
|
||||
ItemIndicator
|
||||
) : (
|
||||
<IconTarget strokeWidth={6} size={10} />
|
||||
)}
|
||||
</RadixDropdown.ItemIndicator>
|
||||
<span>{children}</span>
|
||||
</RadixDropdown.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
interface RadioGroupProps {
|
||||
children: React.ReactNode
|
||||
value: string
|
||||
onChange?(x: string): void
|
||||
}
|
||||
|
||||
export function RadioGroup({
|
||||
children,
|
||||
value: propsValue,
|
||||
onChange,
|
||||
}: RadioGroupProps) {
|
||||
const [value, setValue] = useState(propsValue ? propsValue : '')
|
||||
|
||||
const handleChange = (e: string) => {
|
||||
if (onChange) onChange(e)
|
||||
setValue(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixDropdown.RadioGroup value={value} onValueChange={handleChange}>
|
||||
{children}
|
||||
</RadixDropdown.RadioGroup>
|
||||
)
|
||||
}
|
||||
|
||||
interface LabelProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Label({ children }: LabelProps) {
|
||||
let __styles = styleHandler('dropdown')
|
||||
|
||||
return (
|
||||
<RadixDropdown.Label className={__styles.label}>
|
||||
{children}
|
||||
</RadixDropdown.Label>
|
||||
)
|
||||
}
|
||||
|
||||
Dropdown.Item = Item
|
||||
Dropdown.Misc = Misc
|
||||
Dropdown.Checkbox = Checkbox
|
||||
Dropdown.Radio = Radio
|
||||
Dropdown.RadioGroup = RadioGroup
|
||||
Dropdown.Label = Label
|
||||
Dropdown.Seperator = Seperator
|
||||
Dropdown.RightSlot = RightSlot
|
||||
Dropdown.TriggerItem = TriggerItem
|
||||
export default Dropdown
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Dropdown } from './Dropdown'
|
||||
Whitespace-only changes.
@@ -0,0 +1,569 @@
|
||||
import { Form } from '.'
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
InputNumber,
|
||||
Toggle,
|
||||
Checkbox,
|
||||
Radio,
|
||||
Select,
|
||||
Listbox,
|
||||
} from '../../index'
|
||||
|
||||
import * as Yup from 'yup'
|
||||
import { User } from '../Icon/IconImportHandler'
|
||||
import React from 'react'
|
||||
|
||||
export default {
|
||||
title: 'Data Input/Form',
|
||||
component: Form,
|
||||
}
|
||||
|
||||
interface Values {
|
||||
email: string
|
||||
last_name: string
|
||||
profession: string
|
||||
number: number | undefined
|
||||
// remember_me: boolean
|
||||
checkbox_alone: boolean
|
||||
favorite_food: string
|
||||
check_3: boolean
|
||||
check_2: boolean
|
||||
check_1: boolean
|
||||
textarea: string
|
||||
toggle: boolean
|
||||
people: number | undefined
|
||||
}
|
||||
|
||||
const INITIAL_VALUES: Values = {
|
||||
email: '',
|
||||
last_name: '',
|
||||
profession: '',
|
||||
number: undefined,
|
||||
checkbox_alone: false,
|
||||
// remember_me: false,
|
||||
favorite_food: '',
|
||||
check_3: false,
|
||||
check_2: true,
|
||||
check_1: false,
|
||||
textarea: '',
|
||||
toggle: true,
|
||||
people: undefined,
|
||||
}
|
||||
|
||||
export const InputLevelValidation = () => {
|
||||
function validateEmail(value: string) {
|
||||
console.log('validateEmail running')
|
||||
let error
|
||||
if (!value) {
|
||||
error = 'Required'
|
||||
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
|
||||
error = 'Invalid email address'
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
initialValues={{
|
||||
email: '',
|
||||
firstname: '',
|
||||
}}
|
||||
validateOnBlur
|
||||
onSubmit={(values: any, { setSubmitting }: any) => {
|
||||
setTimeout(() => {
|
||||
alert(JSON.stringify(values, null, 2))
|
||||
setSubmitting(false)
|
||||
}, 400)
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) => (
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
id="email"
|
||||
label="email"
|
||||
placeholder="something in here"
|
||||
labelOptional="This is a required field"
|
||||
validation={validateEmail}
|
||||
/>
|
||||
{/* <Input
|
||||
id="firstname"
|
||||
label="firstname"
|
||||
placeholder="something in here"
|
||||
/> */}
|
||||
<Button loading={isSubmitting} type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export const InputLevelValidationYip = () => {
|
||||
// validation schema
|
||||
const SignupSchema = Yup.object().shape({
|
||||
firstname: Yup.string()
|
||||
.min(2, 'Too Short!')
|
||||
.max(50, 'Too Long!')
|
||||
.required('Required'),
|
||||
lastname: Yup.string()
|
||||
.min(2, 'Too Short!')
|
||||
.max(50, 'Too Long!')
|
||||
.required('Required'),
|
||||
email: Yup.string().email('Invalid email').required('Required'),
|
||||
})
|
||||
|
||||
return (
|
||||
<Form
|
||||
initialValues={{
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={SignupSchema}
|
||||
onSubmit={(values: any, { setSubmitting }: any) => {
|
||||
setTimeout(() => {
|
||||
alert(JSON.stringify(values, null, 2))
|
||||
setSubmitting(false)
|
||||
}, 400)
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }: any) => (
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
id="firstname"
|
||||
name="firstname"
|
||||
label="firstname"
|
||||
placeholder="firstname"
|
||||
/>
|
||||
<Input
|
||||
id="lastname"
|
||||
name="lastname"
|
||||
label="lastname"
|
||||
placeholder="lastname"
|
||||
/>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
label="Email"
|
||||
placeholder="This is your email"
|
||||
/>
|
||||
<Button loading={isSubmitting} type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
const people = [
|
||||
{
|
||||
value: 1,
|
||||
label: 'Wade Cooper',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'Arlene Mccoy',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: 'Devon Webb',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: 'Tom Cook',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
label: 'Tanya Fox',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
label: 'Hellen Schmidt',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 7,
|
||||
label: 'Caroline Schultz',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1568409938619-12e139227838?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 8,
|
||||
label: 'Mason Heaney',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 9,
|
||||
label: 'Claudie Smitham',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1584486520270-19eca1efcce5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
label: 'Emil Schaefer',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1561505457-3bcad021f8ee?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
|
||||
},
|
||||
]
|
||||
|
||||
export const LargerExample = () => {
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
initialValues={INITIAL_VALUES}
|
||||
onSubmit={(values: any, { setSubmitting }: any) => {
|
||||
setTimeout(() => {
|
||||
alert(JSON.stringify(values, null, 2))
|
||||
setSubmitting(false)
|
||||
}, 400)
|
||||
}}
|
||||
validate={(values: Values) => {
|
||||
const errors: any = {}
|
||||
|
||||
console.log('values for validation', values)
|
||||
|
||||
if (!values.email) {
|
||||
errors.email = 'Required'
|
||||
} else if (
|
||||
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
|
||||
) {
|
||||
errors.email = 'Invalid email address'
|
||||
}
|
||||
|
||||
if (!values.profession) {
|
||||
errors.profession = 'Required'
|
||||
}
|
||||
|
||||
if (!values.textarea) {
|
||||
errors.textarea = 'Required'
|
||||
}
|
||||
|
||||
if (!values.number) {
|
||||
errors.number = 'Required'
|
||||
}
|
||||
|
||||
if (values.number <= 13) {
|
||||
errors.number = 'Must be a number above 13'
|
||||
}
|
||||
|
||||
if (values.number >= 32) {
|
||||
errors.number = 'Must be a number below 32'
|
||||
}
|
||||
|
||||
if (!values.favorite_food) {
|
||||
errors.favorite_food = 'You must select a favourite food'
|
||||
}
|
||||
|
||||
if (!values.toggle) {
|
||||
errors.toggle = 'This needs to be turned on'
|
||||
}
|
||||
|
||||
// if (!values.people) {
|
||||
// errors.people = 'Please select a person'
|
||||
// }
|
||||
|
||||
// if (values.remember_me)
|
||||
|
||||
return errors
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }: any) => (
|
||||
<>
|
||||
<div className="space-y-8">
|
||||
<p>{isSubmitting ? 'submitting' : 'not submitting'}</p>
|
||||
<Input
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="something@gmail.com"
|
||||
/>
|
||||
<Input
|
||||
id="last_name"
|
||||
label="last name"
|
||||
placeholder="something in here"
|
||||
/>
|
||||
<Select
|
||||
id="profession"
|
||||
label="Profession"
|
||||
placeholder="something in here"
|
||||
>
|
||||
<Select.Option key="empty-enum" value="">
|
||||
---
|
||||
</Select.Option>
|
||||
<Select.Option value="teacher">Teacher</Select.Option>
|
||||
<Select.Option value="firefighter">Firefighter</Select.Option>
|
||||
<Select.Option value="police">Police</Select.Option>
|
||||
</Select>
|
||||
<InputNumber
|
||||
id="number"
|
||||
label="Number"
|
||||
placeholder="124"
|
||||
labelOptional="Must be between 13 - 31"
|
||||
/>
|
||||
<Checkbox id="checkbox_alone" label="checkbox_alone" />
|
||||
<Input.TextArea id="textarea" rows={5} name="textarea" />
|
||||
<Checkbox.Group
|
||||
label="Group of checkboxes"
|
||||
layout="horizontal"
|
||||
labelOptional="Optional label"
|
||||
descriptionText="You can also show label hint text here"
|
||||
>
|
||||
<Checkbox
|
||||
name="check_1"
|
||||
id="check_1"
|
||||
label="Remember me"
|
||||
description="hello world"
|
||||
/>
|
||||
<Checkbox
|
||||
name="check_2"
|
||||
id="check_2"
|
||||
label="Remember me"
|
||||
description="hello world"
|
||||
/>
|
||||
<Checkbox
|
||||
name="check_3"
|
||||
id="check_3"
|
||||
label="Remember me"
|
||||
description="hello world"
|
||||
/>
|
||||
</Checkbox.Group>
|
||||
<Toggle id="toggle" label="Remember me" layout="horizontal" />
|
||||
<Radio.Group
|
||||
layout="horizontal"
|
||||
name="favorite_food"
|
||||
label="favorite_food"
|
||||
type="list"
|
||||
>
|
||||
<Radio value="pizza" label="Pizza" description="hello world" />
|
||||
<Radio
|
||||
value="burger"
|
||||
label="Burger"
|
||||
description="hello world"
|
||||
/>
|
||||
<Radio value="fries" label="Fries" description="hello world" />
|
||||
</Radio.Group>
|
||||
|
||||
<Listbox
|
||||
id="people"
|
||||
label="Choose a person"
|
||||
layout="horizontal"
|
||||
descriptionText="Choose a person for this role"
|
||||
>
|
||||
{people.map((person) => {
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={`people-${person.value}`}
|
||||
value={person.value}
|
||||
label={person.label}
|
||||
addOnBefore={({ active, selected }: any) => (
|
||||
<img
|
||||
src={person.avatar}
|
||||
alt=""
|
||||
className="h-6 w-6 rounded-full"
|
||||
/>
|
||||
)}
|
||||
children={({ active, selected }: any) => {
|
||||
// console.log('selected', selected)
|
||||
// console.log('active', active)
|
||||
return (
|
||||
<span className={'font-normal block truncate'}>
|
||||
{person.label}
|
||||
</span>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Listbox>
|
||||
|
||||
<Button loading={isSubmitting} type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const CardForm = () => {
|
||||
// panel
|
||||
const Panel = ({
|
||||
children,
|
||||
header,
|
||||
footer,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
header?: React.ReactNode
|
||||
footer?: React.ReactNode
|
||||
}) => (
|
||||
<div className="bg-scale-300 border border-scale-400 rounded-md w-3/4 mx-auto my-8 shadow overflow-hidden">
|
||||
{header && (
|
||||
<div className="bg-scale-100 dark:bg-scale-200 px-8 py-4 border-b border-scale-400">
|
||||
{header}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-6 py-6">{children}</div>
|
||||
{footer}
|
||||
</div>
|
||||
)
|
||||
|
||||
const Section = ({
|
||||
children,
|
||||
header,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
header?: React.ReactNode
|
||||
}) => (
|
||||
<div className="grid grid-cols-12 px-8 py-2">
|
||||
{header}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
initialValues={{ stripe_email_choice: 'emails-2' }}
|
||||
onSubmit={(values: any, { setSubmitting }: any) => {
|
||||
setTimeout(() => {
|
||||
alert(JSON.stringify(values, null, 2))
|
||||
setSubmitting(false)
|
||||
}, 400)
|
||||
}}
|
||||
validate={(values: Values) => {
|
||||
const errors: any = {}
|
||||
|
||||
return errors
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting }: any) => (
|
||||
<Panel
|
||||
header={
|
||||
<>
|
||||
<h3 className="text-scale-1200 text-xl font-semibold">
|
||||
Custom Options
|
||||
</h3>
|
||||
<p className="text-scale-900">
|
||||
These settings apply to payment pages you create using Stripe
|
||||
Checkout and Payment Links.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
footer={
|
||||
<>
|
||||
<div className="border-t border-scale-400"></div>
|
||||
<div className="py-3 px-6 flex gap-2 justify-end">
|
||||
<Button
|
||||
loading={isSubmitting}
|
||||
type="secondary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={isSubmitting}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{/* <p>{isSubmitting ? 'submitting' : 'not submitting'}</p> */}
|
||||
{/* <div className="border-t border-scale-400"></div> */}
|
||||
<Section
|
||||
header={
|
||||
<label className="text-sm text-scale-1200 col-span-4">
|
||||
Faster checkout
|
||||
</label>
|
||||
}
|
||||
>
|
||||
<Toggle
|
||||
className="col-span-8"
|
||||
id="link_with_stripe"
|
||||
label="Turn on link with Stripe"
|
||||
layout="flex"
|
||||
descriptionText="Go to Payment methods settings to configure Apple Pay and Google Pay."
|
||||
/>
|
||||
</Section>
|
||||
<div className="border-t border-scale-400"></div>
|
||||
<Section
|
||||
header={
|
||||
<label className="text-sm text-scale-1200 col-span-4">
|
||||
Faster checkout
|
||||
</label>
|
||||
}
|
||||
>
|
||||
<Radio.Group
|
||||
className="col-span-8"
|
||||
name="stripe_email_choice"
|
||||
id="stripe_email_choice"
|
||||
type="list"
|
||||
layout="vertical"
|
||||
>
|
||||
<Radio
|
||||
// id="1642469211631"
|
||||
value="emails-1"
|
||||
label="Send emails about upcoming renewals"
|
||||
description="hello world"
|
||||
align="horizontal"
|
||||
/>
|
||||
<Radio
|
||||
// id="1642469211606"
|
||||
value="emails-2"
|
||||
label="Send emails about expiring cards"
|
||||
description="hello world"
|
||||
align="horizontal"
|
||||
/>
|
||||
</Radio.Group>
|
||||
</Section>
|
||||
<div className="border-t border-scale-400"></div>
|
||||
<Section
|
||||
header={
|
||||
<label className="text-sm text-scale-1200 col-span-4">
|
||||
Faster checkout
|
||||
</label>
|
||||
}
|
||||
>
|
||||
<div className="col-span-8 space-y-4">
|
||||
<Input name="app_id" label="Application ID in Stripe" />
|
||||
<Input
|
||||
name="service_id"
|
||||
label="API key"
|
||||
descriptionText={
|
||||
<span>
|
||||
This can be found in{' '}
|
||||
<span className="text-brand-900 underline cursor-pointer transition hover:text-brand-800">
|
||||
the developer settings
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
</Panel>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export { default as AuthForm } from './examples/AuthForm'
|
||||
@@ -0,0 +1,96 @@
|
||||
import { useReducer } from 'react'
|
||||
import { useFormik, FormikConfig } from 'formik'
|
||||
import { FormContextProvider } from './FormContext'
|
||||
|
||||
// interface Props extends FormikProps<any>, Partial FormikConfig<any> {
|
||||
interface Props
|
||||
extends Omit<FormikConfig<any>, 'validateOnMount' | 'validateOnChange'> {
|
||||
children: any
|
||||
handleIsSubmitting?: any
|
||||
handleIsValidating?: any
|
||||
name?: string
|
||||
id?: string
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
function errorReducer(state: any, action: any) {
|
||||
if (!action.error) {
|
||||
const payload = state
|
||||
delete payload[action.key]
|
||||
return payload
|
||||
}
|
||||
if (action) {
|
||||
return {
|
||||
...state,
|
||||
[action.key]: action.error,
|
||||
}
|
||||
} else {
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
|
||||
export default function Form({ validate, ...props }: Props) {
|
||||
const [fieldLevelErrors, dispatchErrors] = useReducer(errorReducer, null)
|
||||
|
||||
function handleFieldLevelValidation(key: any, error: string) {
|
||||
dispatchErrors({ key, error })
|
||||
}
|
||||
|
||||
const formik = useFormik({
|
||||
validateOnBlur: true,
|
||||
...props,
|
||||
validationSchema: props.validationSchema,
|
||||
initialValues: props.initialValues,
|
||||
onSubmit: props.onSubmit,
|
||||
validate:
|
||||
validate ||
|
||||
function () {
|
||||
return fieldLevelErrors
|
||||
},
|
||||
})
|
||||
|
||||
// console.log('values', formik.values)
|
||||
// console.log('errors', formik.errors)
|
||||
// console.log('touched', formik.touched)
|
||||
|
||||
return (
|
||||
<form
|
||||
id={props.id}
|
||||
name={props.name}
|
||||
onSubmit={formik.handleSubmit}
|
||||
className={props.className}
|
||||
style={props.style}
|
||||
>
|
||||
<FormContextProvider
|
||||
values={formik.values}
|
||||
errors={formik.errors}
|
||||
formContextOnChange={formik.handleChange}
|
||||
handleBlur={formik.handleBlur}
|
||||
touched={formik.touched}
|
||||
fieldLevelValidation={handleFieldLevelValidation}
|
||||
// children={rest.children}
|
||||
children={props.children({
|
||||
/** map of field names to specific error for that field */
|
||||
errors: formik.errors, // errors,
|
||||
// /** map of field names to whether the field has been touched */
|
||||
touched: formik.touched,
|
||||
/** whether the form is currently submitting */
|
||||
isSubmitting: formik.isSubmitting,
|
||||
/** whether the form is currently validating (prior to submission) */
|
||||
isValidating: formik.isValidating,
|
||||
/** Number of times user tried to submit the form */
|
||||
submitCount: formik.submitCount,
|
||||
/** Initial values of form */
|
||||
initialValues: formik.initialValues,
|
||||
/** Current values of form */
|
||||
values: formik.values,
|
||||
/** Resets the form back to initialValues */
|
||||
handleReset: formik.handleReset,
|
||||
/** Resets the form with custom values */
|
||||
resetForm: formik.resetForm,
|
||||
})}
|
||||
/>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { createContext, useContext } from 'react'
|
||||
|
||||
interface ContextProps {
|
||||
formContextOnChange: any
|
||||
values: any
|
||||
errors: any
|
||||
handleBlur: React.FocusEventHandler | null
|
||||
touched: any
|
||||
fieldLevelValidation: any
|
||||
}
|
||||
|
||||
interface Provider extends ContextProps {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
// Make sure the shape of the default value passed to
|
||||
// createContext matches the shape that the consumers expect!
|
||||
const FormContext = createContext<ContextProps>({
|
||||
formContextOnChange: null,
|
||||
values: null,
|
||||
errors: null,
|
||||
handleBlur: null,
|
||||
touched: null,
|
||||
fieldLevelValidation: null,
|
||||
})
|
||||
|
||||
export const FormContextProvider = (props: Provider) => {
|
||||
const {
|
||||
formContextOnChange,
|
||||
values,
|
||||
errors,
|
||||
handleBlur,
|
||||
touched,
|
||||
fieldLevelValidation,
|
||||
} = props
|
||||
|
||||
const value = {
|
||||
formContextOnChange: formContextOnChange,
|
||||
values: values,
|
||||
errors: errors,
|
||||
handleBlur: handleBlur,
|
||||
touched: touched,
|
||||
fieldLevelValidation: fieldLevelValidation,
|
||||
}
|
||||
|
||||
return (
|
||||
<FormContext.Provider value={value}>{props.children}</FormContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// context helper to avoid using a consumer component
|
||||
export const useFormContext = () => {
|
||||
const context = useContext(FormContext)
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
`useFormContextOnChange must be used within a FormContextProvider.`
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,281 @@
|
||||
[
|
||||
{
|
||||
"name": "Email",
|
||||
"icon": "email-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "email-app-id",
|
||||
"label": "email App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "email-id-key",
|
||||
"label": "email client ID",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "email-id-key",
|
||||
"label": "email client ID",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "email-id-key",
|
||||
"label": "email client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Phone",
|
||||
"icon": "phone-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "select",
|
||||
"name": "phone-app-provider",
|
||||
"label": "Phone service provider",
|
||||
"description": "You can use a provider etc etc"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "phone-id-key",
|
||||
"label": "phone client ID",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "phone-id-key",
|
||||
"label": "phone client ID",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "phone-id-key",
|
||||
"label": "phone client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Google",
|
||||
"icon": "google-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "google-app-id",
|
||||
"label": "Google App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "google-id-key",
|
||||
"label": "Google client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Facebook",
|
||||
"icon": "facebook-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "facebook-app-id",
|
||||
"label": "Facebook App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "facebook-id-key",
|
||||
"label": "Facebook client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Twitter",
|
||||
"icon": "twitter-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twitter-app-id",
|
||||
"label": "Twitter App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twitter-id-key",
|
||||
"label": "Twitter client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Apple",
|
||||
"icon": "apple-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "apple-app-id",
|
||||
"label": "Apple App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "apple-id-key",
|
||||
"label": "Apple client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Microsoft",
|
||||
"icon": "microsoft-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "microsoft-app-id",
|
||||
"label": "Microsoft App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "microsoft-id-key",
|
||||
"label": "Microsoft client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Gitlab",
|
||||
"icon": "gitlab-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "gitlab-app-id",
|
||||
"label": "Gitlab App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "gitlab-id-key",
|
||||
"label": "Gitlab client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Bitbucket",
|
||||
"icon": "bitbucket-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "bitbucket-app-id",
|
||||
"label": "Bitbucket App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "bitbucket-id-key",
|
||||
"label": "Bitbucket client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Discord",
|
||||
"icon": "discord-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "discord-app-id",
|
||||
"label": "Discord App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "discord-id-key",
|
||||
"label": "Discord client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Github",
|
||||
"icon": "github-icon-dark",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "github-app-id",
|
||||
"label": "Github App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "github-id-key",
|
||||
"label": "Github client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Slack",
|
||||
"icon": "slack-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "slack-app-id",
|
||||
"label": "Slack App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "slack-id-key",
|
||||
"label": "Slack client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Spotify",
|
||||
"icon": "spotify-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "spotify-app-id",
|
||||
"label": "Spotify App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "spotify-id-key",
|
||||
"label": "Spotify client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Twitch",
|
||||
"icon": "twitch-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twitch-app-id",
|
||||
"label": "Twitch App Id",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twitch-id-key",
|
||||
"label": "Twitch client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "Twilio",
|
||||
"icon": "twilio-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twilio-app-id",
|
||||
"label": "Enable twilio Provider",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "twilio-id-key",
|
||||
"label": "twilio client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Messagebird",
|
||||
"icon": "messagebird-icon",
|
||||
"form": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "messagebird-app-id",
|
||||
"label": "Enable gitlab messagebird",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "messagebird-id-key",
|
||||
"label": "messagebird client ID",
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Form } from './Form'
|
||||
@@ -0,0 +1,39 @@
|
||||
/* .sbui-icon-container {
|
||||
@apply flex-shrink-0 flex items-center justify-center rounded-full p-3;
|
||||
}
|
||||
|
||||
.sbui-icon-container--brand {
|
||||
@apply bg-brand-800 bg-opacity-10 text-brand-800;
|
||||
}
|
||||
|
||||
.sbui-icon-container--gray {
|
||||
@apply bg-gray-600 bg-opacity-10 text-gray-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--red {
|
||||
@apply bg-red-600 bg-opacity-10 text-red-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--yellow {
|
||||
@apply bg-yellow-600 bg-opacity-10 text-yellow-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--green {
|
||||
@apply bg-green-600 bg-opacity-10 text-green-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--blue {
|
||||
@apply bg-blue-600 bg-opacity-10 text-blue-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--indigo {
|
||||
@apply bg-indigo-600 bg-opacity-10 text-indigo-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--purple {
|
||||
@apply bg-purple-600 bg-opacity-10 text-purple-500;
|
||||
}
|
||||
|
||||
.sbui-icon-container--pink {
|
||||
@apply bg-pink-600 bg-opacity-10 text-pink-500;
|
||||
} */
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
|
||||
import { IconMail, Typography } from './../../index'
|
||||
|
||||
export default {
|
||||
title: 'General/Icon',
|
||||
component: IconMail,
|
||||
}
|
||||
|
||||
export const Default = (args: any) => (
|
||||
<div className="block font-sans">
|
||||
<div>
|
||||
<Typography.Text>
|
||||
<IconMail {...args} />
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Default.args = {
|
||||
size: 16,
|
||||
strokeWidth: 2,
|
||||
}
|
||||
|
||||
// export const IconList = (args: any) => (
|
||||
// <>
|
||||
// <div className="font-sans text-xl bg-blue-100 p-5 mb-3 rounded-md">
|
||||
// This icons uses react-feather from Feather Icons. For detail you can check
|
||||
// it <a href="https://github.com/feathericons/react-feather">here</a> and{' '}
|
||||
// <a href="https://feathericons.com/">here</a>
|
||||
// </div>
|
||||
// <div className="flex flex-wrap font-sans">
|
||||
// {LIST_ICONS.map((icon) => (
|
||||
// <div className="m-2 shadow-lg rounded-lg w-36 h-36 flex flex-col justify-center items-center dark:text-white">
|
||||
// <Icon type={icon} key={icon} {...args} />
|
||||
// <span className="mt-2">{icon}</span>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
|
||||
// IconList.args = {
|
||||
// size: 16,
|
||||
// strokeWidth: 2,
|
||||
// }
|
||||
|
||||
// // const SvgMessagesIcon = () => (
|
||||
// // <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
// // <path
|
||||
// // stroke-linecap="round"
|
||||
// // stroke-linejoin="round"
|
||||
// // stroke-width="2"
|
||||
// // d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
|
||||
// // />
|
||||
// // </svg>
|
||||
// // )
|
||||
|
||||
// // export const IconWithSVG = (args: any) => (
|
||||
// // <Icon {...args} src={<SvgMessagesIcon />} />
|
||||
// // )
|
||||
|
||||
// // IconWithSVG.args = {
|
||||
// // size: 'xxxlarge',
|
||||
// // strokeWidth: 4,
|
||||
// // }
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import Icon from './Icon'
|
||||
|
||||
describe('#Icon', () => {
|
||||
it('should render Icon correctly', () => {
|
||||
render(<Icon data-testid="icon" />)
|
||||
const $elIcon = screen.queryByTestId('icon')
|
||||
expect($elIcon).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should change svg size', () => {
|
||||
render(<Icon data-testid="icon" size={40} />)
|
||||
|
||||
const $elIcon = screen.queryByTestId('icon')
|
||||
expect($elIcon.getAttribute('width')).toEqual('40')
|
||||
expect($elIcon.getAttribute('height')).toEqual('40')
|
||||
})
|
||||
|
||||
it('should change strokeWidth value', () => {
|
||||
render(<Icon data-testid="icon" strokeWidth={1} />)
|
||||
|
||||
const $elIcon = screen.queryByTestId('icon')
|
||||
expect($elIcon.getAttribute('stroke-width')).toEqual('1')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { IconContext } from './IconContext'
|
||||
// @ts-ignore
|
||||
// import IconStyles from './Icon.module.css'
|
||||
|
||||
const IconStyles = {}
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
size?:
|
||||
| 'tiny'
|
||||
| 'small'
|
||||
| 'medium'
|
||||
| 'large'
|
||||
| 'xlarge'
|
||||
| 'xxlarge'
|
||||
| 'xxxlarge'
|
||||
| number
|
||||
type?: string
|
||||
color?: string
|
||||
strokeWidth?: number
|
||||
fill?: string
|
||||
stroke?: string
|
||||
background?:
|
||||
| 'brand'
|
||||
| 'gray'
|
||||
| 'red'
|
||||
| 'yellow'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'indigo'
|
||||
| 'purple'
|
||||
| 'pink'
|
||||
src: React.ReactNode
|
||||
}
|
||||
|
||||
interface StringMap {
|
||||
[key: string]: number
|
||||
}
|
||||
|
||||
function Icon({
|
||||
className,
|
||||
size,
|
||||
type,
|
||||
color,
|
||||
strokeWidth,
|
||||
fill = undefined,
|
||||
stroke = undefined,
|
||||
background,
|
||||
src,
|
||||
...props
|
||||
}: Props) {
|
||||
const __styles = styleHandler('icon')
|
||||
|
||||
return (
|
||||
<IconContext.Consumer>
|
||||
{({ contextSize, className: contextClassName }) => {
|
||||
const defaultSizes: StringMap = {
|
||||
tiny: 14,
|
||||
small: 18,
|
||||
medium: 20,
|
||||
large: 20,
|
||||
xlarge: 24,
|
||||
xxlarge: 30,
|
||||
xxxlarge: 42,
|
||||
}
|
||||
|
||||
const defaultSize = defaultSizes['large']
|
||||
|
||||
// const iconSize = typeof size === 'string' ? defaultSizes[contextSize] : 21
|
||||
let iconSize: any = 21
|
||||
|
||||
// use contextSize of parent (via context hook) if one exists
|
||||
if (contextSize) {
|
||||
iconSize = contextSize
|
||||
? typeof contextSize === 'string'
|
||||
? defaultSizes[contextSize]
|
||||
: contextSize
|
||||
: defaultSize
|
||||
}
|
||||
|
||||
// use size prop of this component if one exists
|
||||
if (size) {
|
||||
iconSize = size
|
||||
? typeof size === 'string'
|
||||
? defaultSizes[size]
|
||||
: size
|
||||
: defaultSize
|
||||
}
|
||||
|
||||
// confitional used for Icons with no color settings
|
||||
// default these icons to use 'currentColor' ie, the text color
|
||||
const noColor = !color && !fill && !stroke
|
||||
|
||||
let classes = ['sbui-icon', className]
|
||||
if (contextClassName) {
|
||||
classes.push(contextClassName)
|
||||
}
|
||||
|
||||
const Icon = (
|
||||
// custom SVG file
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
color={!noColor ? color : 'currentColor'}
|
||||
fill={!noColor ? (fill ? fill : 'none') : 'none'}
|
||||
stroke={!noColor ? stroke : 'currentColor'}
|
||||
strokeWidth={strokeWidth}
|
||||
className={classes.join(' ')}
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
{...props}
|
||||
>
|
||||
{src}
|
||||
</svg>
|
||||
)
|
||||
|
||||
return background ? (
|
||||
<div
|
||||
// circle coloured background
|
||||
className={__styles.container}
|
||||
>
|
||||
{Icon}
|
||||
</div>
|
||||
) : (
|
||||
Icon
|
||||
)
|
||||
}}
|
||||
</IconContext.Consumer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,144 @@
|
||||
import React from 'react'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { IconContext } from './IconContext'
|
||||
// @ts-ignore
|
||||
// import IconStyles from './Icon.module.css'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
size?:
|
||||
| 'tiny'
|
||||
| 'small'
|
||||
| 'medium'
|
||||
| 'large'
|
||||
| 'xlarge'
|
||||
| 'xxlarge'
|
||||
| 'xxxlarge'
|
||||
| number
|
||||
type?: string
|
||||
color?: string
|
||||
strokeWidth?: number
|
||||
fill?: string
|
||||
stroke?: string
|
||||
background?:
|
||||
| 'brand'
|
||||
| 'gray'
|
||||
| 'red'
|
||||
| 'yellow'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'indigo'
|
||||
| 'purple'
|
||||
| 'pink'
|
||||
src?: React.ReactNode
|
||||
icon?: any
|
||||
}
|
||||
|
||||
interface StringMap {
|
||||
[key: string]: number
|
||||
}
|
||||
|
||||
function IconBase({
|
||||
className,
|
||||
size,
|
||||
type = 'Mail',
|
||||
color,
|
||||
strokeWidth,
|
||||
fill = undefined,
|
||||
stroke = undefined,
|
||||
background,
|
||||
src,
|
||||
icon,
|
||||
...props
|
||||
}: Props) {
|
||||
const __styles = styleHandler('icon')
|
||||
|
||||
return (
|
||||
<IconContext.Consumer>
|
||||
{({ contextSize, className: contextClassName }) => {
|
||||
const defaultSizes: StringMap = {
|
||||
tiny: 14,
|
||||
small: 18,
|
||||
medium: 20,
|
||||
large: 20,
|
||||
xlarge: 24,
|
||||
xxlarge: 30,
|
||||
xxxlarge: 42,
|
||||
}
|
||||
|
||||
const defaultSize = defaultSizes['large']
|
||||
// @ts-ignore
|
||||
|
||||
const FeatherIcon = icon
|
||||
|
||||
// const iconSize = typeof size === 'string' ? defaultSizes[contextSize] : 21
|
||||
let iconSize: any = 21
|
||||
|
||||
// use contextSize of parent (via context hook) if one exists
|
||||
if (contextSize) {
|
||||
iconSize = contextSize
|
||||
? typeof contextSize === 'string'
|
||||
? defaultSizes[contextSize]
|
||||
: contextSize
|
||||
: defaultSize
|
||||
}
|
||||
|
||||
// use size prop of this component if one exists
|
||||
if (size) {
|
||||
iconSize = size
|
||||
? typeof size === 'string'
|
||||
? defaultSizes[size]
|
||||
: size
|
||||
: defaultSize
|
||||
}
|
||||
|
||||
// confitional used for Icons with no color settings
|
||||
// default these icons to use 'currentColor' ie, the text color
|
||||
const noColor = !color && !fill && !stroke
|
||||
|
||||
let classes = ['sbui-icon', className]
|
||||
if (contextClassName) {
|
||||
classes.push(contextClassName)
|
||||
}
|
||||
|
||||
const IconComponent = () => (
|
||||
<FeatherIcon
|
||||
color={!noColor ? color : 'currentColor'}
|
||||
stroke={!noColor ? stroke : 'currentColor'}
|
||||
className={classes.join(' ')}
|
||||
strokeWidth={strokeWidth}
|
||||
size={iconSize}
|
||||
fill={!noColor ? (fill ? fill : 'none') : 'none'}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
const Icon = src ? (
|
||||
// custom SVG file
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
color={!noColor ? color : 'currentColor'}
|
||||
fill={!noColor ? (fill ? fill : 'none') : 'none'}
|
||||
stroke={!noColor ? stroke : 'currentColor'}
|
||||
className={classes.join(' ')}
|
||||
width={iconSize}
|
||||
height={iconSize}
|
||||
>
|
||||
{src}
|
||||
</svg>
|
||||
) : (
|
||||
// feather icon
|
||||
<IconComponent />
|
||||
)
|
||||
|
||||
return background ? (
|
||||
<div className={__styles.container}>{Icon}</div>
|
||||
) : (
|
||||
Icon
|
||||
)
|
||||
}}
|
||||
</IconContext.Consumer>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconBase
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
type ContextValue = { contextSize?: string; className?: string }
|
||||
|
||||
// Make sure the shape of the default value passed to
|
||||
// createContext matches the shape that the consumers expect!
|
||||
export const IconContext = createContext<ContextValue>({
|
||||
contextSize: '',
|
||||
className: '',
|
||||
})
|
||||
@@ -0,0 +1,294 @@
|
||||
import { FC, SVGAttributes } from 'react'
|
||||
|
||||
export interface IconProps extends SVGAttributes<SVGElement> {
|
||||
color?: string
|
||||
size?: string | number
|
||||
}
|
||||
|
||||
export type Icon = FC<IconProps>
|
||||
export const Activity: Icon = require(`react-feather/dist/icons/activity`)
|
||||
export const Airplay: Icon = require(`react-feather/dist/icons/airplay`)
|
||||
export const AlertCircle: Icon = require(`react-feather/dist/icons/alert-circle`)
|
||||
export const AlertOctagon: Icon = require(`react-feather/dist/icons/alert-octagon`)
|
||||
export const AlertTriangle: Icon = require(`react-feather/dist/icons/alert-triangle`)
|
||||
export const AlignCenter: Icon = require(`react-feather/dist/icons/align-center`)
|
||||
export const AlignJustify: Icon = require(`react-feather/dist/icons/align-justify`)
|
||||
export const AlignLeft: Icon = require(`react-feather/dist/icons/align-left`)
|
||||
export const AlignRight: Icon = require(`react-feather/dist/icons/align-right`)
|
||||
export const Anchor: Icon = require(`react-feather/dist/icons/anchor`)
|
||||
export const Aperture: Icon = require(`react-feather/dist/icons/aperture`)
|
||||
export const Archive: Icon = require(`react-feather/dist/icons/archive`)
|
||||
export const ArrowDownCircle: Icon = require(`react-feather/dist/icons/arrow-down-circle`)
|
||||
export const ArrowDownLeft: Icon = require(`react-feather/dist/icons/arrow-down-left`)
|
||||
export const ArrowDownRight: Icon = require(`react-feather/dist/icons/arrow-down-right`)
|
||||
export const ArrowDown: Icon = require(`react-feather/dist/icons/arrow-down`)
|
||||
export const ArrowLeftCircle: Icon = require(`react-feather/dist/icons/arrow-left-circle`)
|
||||
export const ArrowLeft: Icon = require(`react-feather/dist/icons/arrow-left`)
|
||||
export const ArrowRightCircle: Icon = require(`react-feather/dist/icons/arrow-right-circle`)
|
||||
export const ArrowRight: Icon = require(`react-feather/dist/icons/arrow-right`)
|
||||
export const ArrowUpCircle: Icon = require(`react-feather/dist/icons/arrow-up-circle`)
|
||||
export const ArrowUpLeft: Icon = require(`react-feather/dist/icons/arrow-up-left`)
|
||||
export const ArrowUpRight: Icon = require(`react-feather/dist/icons/arrow-up-right`)
|
||||
export const ArrowUp: Icon = require(`react-feather/dist/icons/arrow-up`)
|
||||
export const AtSign: Icon = require(`react-feather/dist/icons/at-sign`)
|
||||
export const Award: Icon = require(`react-feather/dist/icons/award`)
|
||||
export const BarChart2: Icon = require(`react-feather/dist/icons/bar-chart-2`)
|
||||
export const BarChart: Icon = require(`react-feather/dist/icons/bar-chart`)
|
||||
export const BatteryCharging: Icon = require(`react-feather/dist/icons/battery-charging`)
|
||||
export const Battery: Icon = require(`react-feather/dist/icons/battery`)
|
||||
export const BellOff: Icon = require(`react-feather/dist/icons/bell-off`)
|
||||
export const Bell: Icon = require(`react-feather/dist/icons/bell`)
|
||||
export const Bluetooth: Icon = require(`react-feather/dist/icons/bluetooth`)
|
||||
export const Bold: Icon = require(`react-feather/dist/icons/bold`)
|
||||
export const BookOpen: Icon = require(`react-feather/dist/icons/book-open`)
|
||||
export const Book: Icon = require(`react-feather/dist/icons/book`)
|
||||
export const Bookmark: Icon = require(`react-feather/dist/icons/bookmark`)
|
||||
export const Box: Icon = require(`react-feather/dist/icons/box`)
|
||||
export const Briefcase: Icon = require(`react-feather/dist/icons/briefcase`)
|
||||
export const Calendar: Icon = require(`react-feather/dist/icons/calendar`)
|
||||
export const CameraOff: Icon = require(`react-feather/dist/icons/camera-off`)
|
||||
export const Camera: Icon = require(`react-feather/dist/icons/camera`)
|
||||
export const Cast: Icon = require(`react-feather/dist/icons/cast`)
|
||||
export const CheckCircle: Icon = require(`react-feather/dist/icons/check-circle`)
|
||||
export const CheckSquare: Icon = require(`react-feather/dist/icons/check-square`)
|
||||
export const Check: Icon = require(`react-feather/dist/icons/check`)
|
||||
export const ChevronDown: Icon = require(`react-feather/dist/icons/chevron-down`)
|
||||
export const ChevronLeft: Icon = require(`react-feather/dist/icons/chevron-left`)
|
||||
export const ChevronRight: Icon = require(`react-feather/dist/icons/chevron-right`)
|
||||
export const ChevronUp: Icon = require(`react-feather/dist/icons/chevron-up`)
|
||||
export const ChevronsDown: Icon = require(`react-feather/dist/icons/chevrons-down`)
|
||||
export const ChevronsLeft: Icon = require(`react-feather/dist/icons/chevrons-left`)
|
||||
export const ChevronsRight: Icon = require(`react-feather/dist/icons/chevrons-right`)
|
||||
export const ChevronsUp: Icon = require(`react-feather/dist/icons/chevrons-up`)
|
||||
export const Chrome: Icon = require(`react-feather/dist/icons/chrome`)
|
||||
export const Circle: Icon = require(`react-feather/dist/icons/circle`)
|
||||
export const Clipboard: Icon = require(`react-feather/dist/icons/clipboard`)
|
||||
export const Clock: Icon = require(`react-feather/dist/icons/clock`)
|
||||
export const CloudDrizzle: Icon = require(`react-feather/dist/icons/cloud-drizzle`)
|
||||
export const CloudLightning: Icon = require(`react-feather/dist/icons/cloud-lightning`)
|
||||
export const CloudOff: Icon = require(`react-feather/dist/icons/cloud-off`)
|
||||
export const CloudRain: Icon = require(`react-feather/dist/icons/cloud-rain`)
|
||||
export const CloudSnow: Icon = require(`react-feather/dist/icons/cloud-snow`)
|
||||
export const Cloud: Icon = require(`react-feather/dist/icons/cloud`)
|
||||
export const Code: Icon = require(`react-feather/dist/icons/code`)
|
||||
export const Codepen: Icon = require(`react-feather/dist/icons/codepen`)
|
||||
export const Codesandbox: Icon = require(`react-feather/dist/icons/codesandbox`)
|
||||
export const Coffee: Icon = require(`react-feather/dist/icons/coffee`)
|
||||
export const Columns: Icon = require(`react-feather/dist/icons/columns`)
|
||||
export const Command: Icon = require(`react-feather/dist/icons/command`)
|
||||
export const Compass: Icon = require(`react-feather/dist/icons/compass`)
|
||||
export const Copy: Icon = require(`react-feather/dist/icons/copy`)
|
||||
export const CornerDownLeft: Icon = require(`react-feather/dist/icons/corner-down-left`)
|
||||
export const CornerDownRight: Icon = require(`react-feather/dist/icons/corner-down-right`)
|
||||
export const CornerLeftDown: Icon = require(`react-feather/dist/icons/corner-left-down`)
|
||||
export const CornerLeftUp: Icon = require(`react-feather/dist/icons/corner-left-up`)
|
||||
export const CornerRightDown: Icon = require(`react-feather/dist/icons/corner-right-down`)
|
||||
export const CornerRightUp: Icon = require(`react-feather/dist/icons/corner-right-up`)
|
||||
export const CornerUpLeft: Icon = require(`react-feather/dist/icons/corner-up-left`)
|
||||
export const CornerUpRight: Icon = require(`react-feather/dist/icons/corner-up-right`)
|
||||
export const Cpu: Icon = require(`react-feather/dist/icons/cpu`)
|
||||
export const CreditCard: Icon = require(`react-feather/dist/icons/credit-card`)
|
||||
export const Crop: Icon = require(`react-feather/dist/icons/crop`)
|
||||
export const Crosshair: Icon = require(`react-feather/dist/icons/crosshair`)
|
||||
export const Database: Icon = require(`react-feather/dist/icons/database`)
|
||||
export const Delete: Icon = require(`react-feather/dist/icons/delete`)
|
||||
export const Disc: Icon = require(`react-feather/dist/icons/disc`)
|
||||
export const DivideCircle: Icon = require(`react-feather/dist/icons/divide-circle`)
|
||||
export const DivideSquare: Icon = require(`react-feather/dist/icons/divide-square`)
|
||||
export const Divide: Icon = require(`react-feather/dist/icons/divide`)
|
||||
export const DollarSign: Icon = require(`react-feather/dist/icons/dollar-sign`)
|
||||
export const DownloadCloud: Icon = require(`react-feather/dist/icons/download-cloud`)
|
||||
export const Download: Icon = require(`react-feather/dist/icons/download`)
|
||||
export const Dribbble: Icon = require(`react-feather/dist/icons/dribbble`)
|
||||
export const Droplet: Icon = require(`react-feather/dist/icons/droplet`)
|
||||
export const Edit2: Icon = require(`react-feather/dist/icons/edit-2`)
|
||||
export const Edit3: Icon = require(`react-feather/dist/icons/edit-3`)
|
||||
export const Edit: Icon = require(`react-feather/dist/icons/edit`)
|
||||
export const ExternalLink: Icon = require(`react-feather/dist/icons/external-link`)
|
||||
export const EyeOff: Icon = require(`react-feather/dist/icons/eye-off`)
|
||||
export const Eye: Icon = require(`react-feather/dist/icons/eye`)
|
||||
export const Facebook: Icon = require(`react-feather/dist/icons/facebook`)
|
||||
export const FastForward: Icon = require(`react-feather/dist/icons/fast-forward`)
|
||||
export const Feather: Icon = require(`react-feather/dist/icons/feather`)
|
||||
export const Figma: Icon = require(`react-feather/dist/icons/figma`)
|
||||
export const FileMinus: Icon = require(`react-feather/dist/icons/file-minus`)
|
||||
export const FilePlus: Icon = require(`react-feather/dist/icons/file-plus`)
|
||||
export const FileText: Icon = require(`react-feather/dist/icons/file-text`)
|
||||
export const File: Icon = require(`react-feather/dist/icons/file`)
|
||||
export const Film: Icon = require(`react-feather/dist/icons/film`)
|
||||
export const Filter: Icon = require(`react-feather/dist/icons/filter`)
|
||||
export const Flag: Icon = require(`react-feather/dist/icons/flag`)
|
||||
export const FolderMinus: Icon = require(`react-feather/dist/icons/folder-minus`)
|
||||
export const FolderPlus: Icon = require(`react-feather/dist/icons/folder-plus`)
|
||||
export const Folder: Icon = require(`react-feather/dist/icons/folder`)
|
||||
export const Framer: Icon = require(`react-feather/dist/icons/framer`)
|
||||
export const Frown: Icon = require(`react-feather/dist/icons/frown`)
|
||||
export const Gift: Icon = require(`react-feather/dist/icons/gift`)
|
||||
export const GitBranch: Icon = require(`react-feather/dist/icons/git-branch`)
|
||||
export const GitCommit: Icon = require(`react-feather/dist/icons/git-commit`)
|
||||
export const GitMerge: Icon = require(`react-feather/dist/icons/git-merge`)
|
||||
export const GitPullRequest: Icon = require(`react-feather/dist/icons/git-pull-request`)
|
||||
export const GitHub: Icon = require(`react-feather/dist/icons/github`)
|
||||
export const Gitlab: Icon = require(`react-feather/dist/icons/gitlab`)
|
||||
export const Globe: Icon = require(`react-feather/dist/icons/globe`)
|
||||
export const Grid: Icon = require(`react-feather/dist/icons/grid`)
|
||||
export const HardDrive: Icon = require(`react-feather/dist/icons/hard-drive`)
|
||||
export const Hash: Icon = require(`react-feather/dist/icons/hash`)
|
||||
export const Headphones: Icon = require(`react-feather/dist/icons/headphones`)
|
||||
export const Heart: Icon = require(`react-feather/dist/icons/heart`)
|
||||
export const HelpCircle: Icon = require(`react-feather/dist/icons/help-circle`)
|
||||
export const Hexagon: Icon = require(`react-feather/dist/icons/hexagon`)
|
||||
export const Home: Icon = require(`react-feather/dist/icons/home`)
|
||||
export const Image: Icon = require(`react-feather/dist/icons/image`)
|
||||
export const Inbox: Icon = require(`react-feather/dist/icons/inbox`)
|
||||
export const Info: Icon = require(`react-feather/dist/icons/info`)
|
||||
export const Instagram: Icon = require(`react-feather/dist/icons/instagram`)
|
||||
export const Italic: Icon = require(`react-feather/dist/icons/italic`)
|
||||
export const Key: Icon = require(`react-feather/dist/icons/key`)
|
||||
export const Layers: Icon = require(`react-feather/dist/icons/layers`)
|
||||
export const Layout: Icon = require(`react-feather/dist/icons/layout`)
|
||||
export const LifeBuoy: Icon = require(`react-feather/dist/icons/life-buoy`)
|
||||
export const Link2: Icon = require(`react-feather/dist/icons/link-2`)
|
||||
export const Link: Icon = require(`react-feather/dist/icons/link`)
|
||||
export const Linkedin: Icon = require(`react-feather/dist/icons/linkedin`)
|
||||
export const List: Icon = require(`react-feather/dist/icons/list`)
|
||||
export const Loader: Icon = require(`react-feather/dist/icons/loader`)
|
||||
export const Lock: Icon = require(`react-feather/dist/icons/lock`)
|
||||
export const LogIn: Icon = require(`react-feather/dist/icons/log-in`)
|
||||
export const LogOut: Icon = require(`react-feather/dist/icons/log-out`)
|
||||
export const Mail: Icon = require(`react-feather/dist/icons/mail`)
|
||||
export const MapPin: Icon = require(`react-feather/dist/icons/map-pin`)
|
||||
export const Map: Icon = require(`react-feather/dist/icons/map`)
|
||||
export const Maximize2: Icon = require(`react-feather/dist/icons/maximize-2`)
|
||||
export const Maximize: Icon = require(`react-feather/dist/icons/maximize`)
|
||||
export const Meh: Icon = require(`react-feather/dist/icons/meh`)
|
||||
export const Menu: Icon = require(`react-feather/dist/icons/menu`)
|
||||
export const MessageCircle: Icon = require(`react-feather/dist/icons/message-circle`)
|
||||
export const MessageSquare: Icon = require(`react-feather/dist/icons/message-square`)
|
||||
export const MicOff: Icon = require(`react-feather/dist/icons/mic-off`)
|
||||
export const Mic: Icon = require(`react-feather/dist/icons/mic`)
|
||||
export const Minimize2: Icon = require(`react-feather/dist/icons/minimize-2`)
|
||||
export const Minimize: Icon = require(`react-feather/dist/icons/minimize`)
|
||||
export const MinusCircle: Icon = require(`react-feather/dist/icons/minus-circle`)
|
||||
export const MinusSquare: Icon = require(`react-feather/dist/icons/minus-square`)
|
||||
export const Minus: Icon = require(`react-feather/dist/icons/minus`)
|
||||
export const Monitor: Icon = require(`react-feather/dist/icons/monitor`)
|
||||
export const Moon: Icon = require(`react-feather/dist/icons/moon`)
|
||||
export const MoreHorizontal: Icon = require(`react-feather/dist/icons/more-horizontal`)
|
||||
export const MoreVertical: Icon = require(`react-feather/dist/icons/more-vertical`)
|
||||
export const MousePointer: Icon = require(`react-feather/dist/icons/mouse-pointer`)
|
||||
export const Move: Icon = require(`react-feather/dist/icons/move`)
|
||||
export const Music: Icon = require(`react-feather/dist/icons/music`)
|
||||
export const Navigation2: Icon = require(`react-feather/dist/icons/navigation-2`)
|
||||
export const Navigation: Icon = require(`react-feather/dist/icons/navigation`)
|
||||
export const Octagon: Icon = require(`react-feather/dist/icons/octagon`)
|
||||
export const Package: Icon = require(`react-feather/dist/icons/package`)
|
||||
export const Paperclip: Icon = require(`react-feather/dist/icons/paperclip`)
|
||||
export const PauseCircle: Icon = require(`react-feather/dist/icons/pause-circle`)
|
||||
export const Pause: Icon = require(`react-feather/dist/icons/pause`)
|
||||
export const PenTool: Icon = require(`react-feather/dist/icons/pen-tool`)
|
||||
export const Percent: Icon = require(`react-feather/dist/icons/percent`)
|
||||
export const PhoneCall: Icon = require(`react-feather/dist/icons/phone-call`)
|
||||
export const PhoneForwarded: Icon = require(`react-feather/dist/icons/phone-forwarded`)
|
||||
export const PhoneIncoming: Icon = require(`react-feather/dist/icons/phone-incoming`)
|
||||
export const PhoneMissed: Icon = require(`react-feather/dist/icons/phone-missed`)
|
||||
export const PhoneOff: Icon = require(`react-feather/dist/icons/phone-off`)
|
||||
export const PhoneOutgoing: Icon = require(`react-feather/dist/icons/phone-outgoing`)
|
||||
export const Phone: Icon = require(`react-feather/dist/icons/phone`)
|
||||
export const PieChart: Icon = require(`react-feather/dist/icons/pie-chart`)
|
||||
export const PlayCircle: Icon = require(`react-feather/dist/icons/play-circle`)
|
||||
export const Play: Icon = require(`react-feather/dist/icons/play`)
|
||||
export const PlusCircle: Icon = require(`react-feather/dist/icons/plus-circle`)
|
||||
export const PlusSquare: Icon = require(`react-feather/dist/icons/plus-square`)
|
||||
export const Plus: Icon = require(`react-feather/dist/icons/plus`)
|
||||
export const Pocket: Icon = require(`react-feather/dist/icons/pocket`)
|
||||
export const Power: Icon = require(`react-feather/dist/icons/power`)
|
||||
export const Printer: Icon = require(`react-feather/dist/icons/printer`)
|
||||
export const Radio: Icon = require(`react-feather/dist/icons/radio`)
|
||||
export const RefreshCcw: Icon = require(`react-feather/dist/icons/refresh-ccw`)
|
||||
export const RefreshCw: Icon = require(`react-feather/dist/icons/refresh-cw`)
|
||||
export const Repeat: Icon = require(`react-feather/dist/icons/repeat`)
|
||||
export const Rewind: Icon = require(`react-feather/dist/icons/rewind`)
|
||||
export const RotateCcw: Icon = require(`react-feather/dist/icons/rotate-ccw`)
|
||||
export const RotateCw: Icon = require(`react-feather/dist/icons/rotate-cw`)
|
||||
export const Rss: Icon = require(`react-feather/dist/icons/rss`)
|
||||
export const Save: Icon = require(`react-feather/dist/icons/save`)
|
||||
export const Scissors: Icon = require(`react-feather/dist/icons/scissors`)
|
||||
export const Search: Icon = require(`react-feather/dist/icons/search`)
|
||||
export const Send: Icon = require(`react-feather/dist/icons/send`)
|
||||
export const Server: Icon = require(`react-feather/dist/icons/server`)
|
||||
export const Settings: Icon = require(`react-feather/dist/icons/settings`)
|
||||
export const Share2: Icon = require(`react-feather/dist/icons/share-2`)
|
||||
export const Share: Icon = require(`react-feather/dist/icons/share`)
|
||||
export const ShieldOff: Icon = require(`react-feather/dist/icons/shield-off`)
|
||||
export const Shield: Icon = require(`react-feather/dist/icons/shield`)
|
||||
export const ShoppingBag: Icon = require(`react-feather/dist/icons/shopping-bag`)
|
||||
export const ShoppingCart: Icon = require(`react-feather/dist/icons/shopping-cart`)
|
||||
export const Shuffle: Icon = require(`react-feather/dist/icons/shuffle`)
|
||||
export const Sidebar: Icon = require(`react-feather/dist/icons/sidebar`)
|
||||
export const SkipBack: Icon = require(`react-feather/dist/icons/skip-back`)
|
||||
export const SkipForward: Icon = require(`react-feather/dist/icons/skip-forward`)
|
||||
export const Slack: Icon = require(`react-feather/dist/icons/slack`)
|
||||
export const Slash: Icon = require(`react-feather/dist/icons/slash`)
|
||||
export const Sliders: Icon = require(`react-feather/dist/icons/sliders`)
|
||||
export const Smartphone: Icon = require(`react-feather/dist/icons/smartphone`)
|
||||
export const Smile: Icon = require(`react-feather/dist/icons/smile`)
|
||||
export const Speaker: Icon = require(`react-feather/dist/icons/speaker`)
|
||||
export const Square: Icon = require(`react-feather/dist/icons/square`)
|
||||
export const Star: Icon = require(`react-feather/dist/icons/star`)
|
||||
export const StopCircle: Icon = require(`react-feather/dist/icons/stop-circle`)
|
||||
export const Sun: Icon = require(`react-feather/dist/icons/sun`)
|
||||
export const Sunrise: Icon = require(`react-feather/dist/icons/sunrise`)
|
||||
export const Sunset: Icon = require(`react-feather/dist/icons/sunset`)
|
||||
export const Tablet: Icon = require(`react-feather/dist/icons/tablet`)
|
||||
export const Tag: Icon = require(`react-feather/dist/icons/tag`)
|
||||
export const Target: Icon = require(`react-feather/dist/icons/target`)
|
||||
export const Terminal: Icon = require(`react-feather/dist/icons/terminal`)
|
||||
export const Thermometer: Icon = require(`react-feather/dist/icons/thermometer`)
|
||||
export const ThumbsDown: Icon = require(`react-feather/dist/icons/thumbs-down`)
|
||||
export const ThumbsUp: Icon = require(`react-feather/dist/icons/thumbs-up`)
|
||||
export const ToggleLeft: Icon = require(`react-feather/dist/icons/toggle-left`)
|
||||
export const ToggleRight: Icon = require(`react-feather/dist/icons/toggle-right`)
|
||||
export const Tool: Icon = require(`react-feather/dist/icons/tool`)
|
||||
export const Trash2: Icon = require(`react-feather/dist/icons/trash-2`)
|
||||
export const Trash: Icon = require(`react-feather/dist/icons/trash`)
|
||||
export const Trello: Icon = require(`react-feather/dist/icons/trello`)
|
||||
export const TrendingDown: Icon = require(`react-feather/dist/icons/trending-down`)
|
||||
export const TrendingUp: Icon = require(`react-feather/dist/icons/trending-up`)
|
||||
export const Triangle: Icon = require(`react-feather/dist/icons/triangle`)
|
||||
export const Truck: Icon = require(`react-feather/dist/icons/truck`)
|
||||
export const Tv: Icon = require(`react-feather/dist/icons/tv`)
|
||||
export const Twitch: Icon = require(`react-feather/dist/icons/twitch`)
|
||||
export const Twitter: Icon = require(`react-feather/dist/icons/twitter`)
|
||||
export const Type: Icon = require(`react-feather/dist/icons/type`)
|
||||
export const Umbrella: Icon = require(`react-feather/dist/icons/umbrella`)
|
||||
export const Underline: Icon = require(`react-feather/dist/icons/underline`)
|
||||
export const Unlock: Icon = require(`react-feather/dist/icons/unlock`)
|
||||
export const UploadCloud: Icon = require(`react-feather/dist/icons/upload-cloud`)
|
||||
export const Upload: Icon = require(`react-feather/dist/icons/upload`)
|
||||
export const UserCheck: Icon = require(`react-feather/dist/icons/user-check`)
|
||||
export const UserMinus: Icon = require(`react-feather/dist/icons/user-minus`)
|
||||
export const UserPlus: Icon = require(`react-feather/dist/icons/user-plus`)
|
||||
export const UserX: Icon = require(`react-feather/dist/icons/user-x`)
|
||||
export const User: Icon = require(`react-feather/dist/icons/user`)
|
||||
export const Users: Icon = require(`react-feather/dist/icons/users`)
|
||||
export const VideoOff: Icon = require(`react-feather/dist/icons/video-off`)
|
||||
export const Video: Icon = require(`react-feather/dist/icons/video`)
|
||||
export const Voicemail: Icon = require(`react-feather/dist/icons/voicemail`)
|
||||
export const Volume1: Icon = require(`react-feather/dist/icons/volume-1`)
|
||||
export const Volume2: Icon = require(`react-feather/dist/icons/volume-2`)
|
||||
export const VolumeX: Icon = require(`react-feather/dist/icons/volume-x`)
|
||||
export const Volume: Icon = require(`react-feather/dist/icons/volume`)
|
||||
export const Watch: Icon = require(`react-feather/dist/icons/watch`)
|
||||
export const WifiOff: Icon = require(`react-feather/dist/icons/wifi-off`)
|
||||
export const Wifi: Icon = require(`react-feather/dist/icons/wifi`)
|
||||
export const Wind: Icon = require(`react-feather/dist/icons/wind`)
|
||||
export const XCircle: Icon = require(`react-feather/dist/icons/x-circle`)
|
||||
export const XOctagon: Icon = require(`react-feather/dist/icons/x-octagon`)
|
||||
export const XSquare: Icon = require(`react-feather/dist/icons/x-square`)
|
||||
export const X: Icon = require(`react-feather/dist/icons/x`)
|
||||
export const Youtube: Icon = require(`react-feather/dist/icons/youtube`)
|
||||
export const ZapOff: Icon = require(`react-feather/dist/icons/zap-off`)
|
||||
export const Zap: Icon = require(`react-feather/dist/icons/zap`)
|
||||
export const ZoomIn: Icon = require(`react-feather/dist/icons/zoom-in`)
|
||||
export const ZoomOut: Icon = require(`react-feather/dist/icons/zoom-out`)
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/activity'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconActivity(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconActivity
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IconActivity } from './IconActivity'
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/airplay'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconAirplay(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconAirplay
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IconAirplay } from './IconAirplay'
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/alert-circle'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconAlertCircle(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconAlertCircle
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IconAlertCircle } from './IconAlertCircle'
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/alert-octagon'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconAlertOctagon(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconAlertOctagon
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IconAlertOctagon } from './IconAlertOctagon'
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/alert-triangle'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconAlertTriangle(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconAlertTriangle
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as IconAlertTriangle } from './IconAlertTriangle'
|
||||
@@ -0,0 +1,13 @@
|
||||
// DO NOT EDIT
|
||||
// this file is generated by /internals/populate-icons script
|
||||
import React from 'react'
|
||||
// @ts-ignore
|
||||
import Icon from 'react-feather/dist/icons/align-center'
|
||||
import IconBase from './../../IconBase'
|
||||
|
||||
function IconAlignCenter(props: any) {
|
||||
return <IconBase icon={Icon} {...props} />
|
||||
}
|
||||
|
||||
export default IconAlignCenter
|
||||
|
||||
Loaded 100 of 818 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user