@crystallize/design-system 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/index.css +1362 -633
- package/dist/index.d.ts +171 -25
- package/dist/index.js +1124 -390
- package/dist/index.mjs +1108 -389
- package/package.json +47 -27
- package/readme.md +9 -0
- package/src/Tokens.stories.tsx +18 -0
- package/src/action-menu/ActionMenu.stories.tsx +3 -1
- package/src/action-menu/action-item.tsx +2 -10
- package/src/action-menu/action-menu.css +38 -0
- package/src/action-menu/action-menu.tsx +4 -13
- package/src/action-menu/index.tsx +2 -0
- package/src/avatar/Avatar.stories.tsx +20 -0
- package/src/avatar/avatar.css +23 -0
- package/src/avatar/avatar.tsx +34 -0
- package/src/avatar/get-initials.ts +5 -0
- package/src/avatar/index.ts +1 -0
- package/src/button/Button.stories.tsx +64 -22
- package/src/button/button.css +116 -0
- package/src/button/button.tsx +119 -33
- package/src/button/index.ts +1 -1
- package/src/card/card.css +7 -0
- package/src/card/card.stories.tsx +2 -2
- package/src/card/card.tsx +6 -4
- package/src/card/index.ts +2 -0
- package/src/checkbox/checkbox.css +30 -0
- package/src/checkbox/checkbox.stories.tsx +62 -0
- package/src/checkbox/checkbox.tsx +28 -0
- package/src/checkbox/index.ts +1 -0
- package/src/colors/Colors.stories.tsx +127 -0
- package/src/colors/color-defaults.json +15 -0
- package/src/colors/color-pairing.json +12 -0
- package/src/colors/colors.json +158 -0
- package/src/colors/index.ts +1 -0
- package/src/colors/old-to-new.txt +19 -0
- package/src/colors/types.ts +29 -0
- package/src/dialog/Dialog.stories.tsx +9 -6
- package/src/dialog/confirm-dialog.tsx +5 -2
- package/src/dialog/dialog.css +27 -0
- package/src/dialog/dialog.tsx +23 -25
- package/src/dialog/types.ts +4 -1
- package/src/dropdown-menu/DropdownMenu.stories.tsx +6 -15
- package/src/dropdown-menu/dropdown-menu-item.tsx +3 -12
- package/src/dropdown-menu/dropdown-menu-label.tsx +2 -9
- package/src/dropdown-menu/dropdown-menu-root.tsx +9 -5
- package/src/dropdown-menu/dropdown-menu.css +20 -0
- package/src/dropdown-menu/index.ts +2 -0
- package/src/icon-button/IconButton.stories.tsx +9 -6
- package/src/icon-button/icon-button.css +40 -0
- package/src/icon-button/icon-button.tsx +14 -22
- package/src/iconography/Icon.stories.tsx +47 -0
- package/src/{icons → iconography}/arrow.tsx +0 -0
- package/src/iconography/atom.tsx +59 -0
- package/src/{icons → iconography}/cancel.tsx +0 -0
- package/src/iconography/copy.tsx +24 -0
- package/src/iconography/crystal.tsx +93 -0
- package/src/iconography/edit.tsx +30 -0
- package/src/iconography/error.tsx +40 -0
- package/src/{icons → iconography}/glasses.tsx +0 -0
- package/src/{icons → iconography}/graphQL.tsx +0 -0
- package/src/{icons → iconography}/index.ts +10 -2
- package/src/iconography/info.tsx +41 -0
- package/src/{icons → iconography}/nail-polish.tsx +0 -0
- package/src/iconography/particle.tsx +88 -0
- package/src/iconography/triangle.tsx +27 -0
- package/src/iconography/warning.tsx +51 -0
- package/src/index.css +11 -0
- package/src/index.ts +23 -1
- package/src/inline-radio/index.ts +1 -0
- package/src/inline-radio/inline-radio.css +20 -0
- package/src/inline-radio/inline-radio.stories.tsx +62 -0
- package/src/inline-radio/inline-radio.tsx +26 -0
- package/src/input/Input.stories.tsx +26 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.css +7 -0
- package/src/input/input.tsx +20 -0
- package/src/input-with-label/InputWithLabel.stories.tsx +98 -0
- package/src/input-with-label/index.ts +3 -0
- package/src/input-with-label/input-with-label.css +35 -0
- package/src/input-with-label/input-with-label.tsx +59 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.css +3 -0
- package/src/label/label.stories.tsx +19 -0
- package/src/label/label.tsx +13 -0
- package/src/progress/Progress.stories.tsx +26 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.css +7 -0
- package/src/progress/progress.tsx +17 -0
- package/src/radio/index.ts +1 -0
- package/src/radio/radio.css +20 -0
- package/src/radio/radio.stories.tsx +142 -0
- package/src/radio/radio.tsx +19 -0
- package/src/select/index.ts +1 -0
- package/src/select/select-item.tsx +18 -0
- package/src/select/select-root.tsx +34 -0
- package/src/select/select.css +28 -0
- package/src/select/select.stories.tsx +74 -0
- package/src/select/select.ts +9 -0
- package/src/spinner/Spinner.stories.tsx +19 -0
- package/src/spinner/index.tsx +48 -0
- package/src/spinner/spinner.css +11 -0
- package/tailwind.config.cjs +51 -0
- package/src/button copy/ButtonCopy.stories.tsx +0 -86
- package/src/button copy/button.tsx +0 -61
- package/src/button copy/index.ts +0 -3
- package/src/colors/Colors.stories.mdx +0 -33
- package/src/icons/Iconography.stories.mdx +0 -45
- package/src/icons/dots.tsx +0 -24
- package/src/icons/error.tsx +0 -50
- package/src/icons/info.tsx +0 -53
- package/src/icons/warning.tsx +0 -62
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cva, VariantProps } from 'class-variance-authority';
|
|
2
|
+
import { ComponentPropsWithRef, forwardRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import './input.css';
|
|
5
|
+
|
|
6
|
+
type InputStylesProps = VariantProps<typeof inputStyles>;
|
|
7
|
+
const inputStyles = cva(['c-input'], {
|
|
8
|
+
variants: {},
|
|
9
|
+
defaultVariants: {},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type InputProps = InputStylesProps & ComponentPropsWithRef<'input'>;
|
|
13
|
+
|
|
14
|
+
export type InputRef = HTMLInputElement;
|
|
15
|
+
|
|
16
|
+
export const Input = forwardRef<InputRef, InputProps>(({ className, ...delegated }, ref) => {
|
|
17
|
+
return <input ref={ref} className={inputStyles({ className })} type="text" {...delegated} />;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
Input.displayName = 'Input';
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { InputWithLabel } from '.';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof InputWithLabel> = {
|
|
6
|
+
title: 'Components/InputWithLabel',
|
|
7
|
+
component: InputWithLabel,
|
|
8
|
+
argTypes: {
|
|
9
|
+
variant: {
|
|
10
|
+
defaultValue: '',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof InputWithLabel>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
placeholder: 'Luke Skywalker',
|
|
22
|
+
label: 'Name',
|
|
23
|
+
value: 'Luke Skywalker',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const Error: Story = {
|
|
28
|
+
args: {
|
|
29
|
+
placeholder: 'Luke Skywalker',
|
|
30
|
+
label: 'Name',
|
|
31
|
+
status: 'error',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const ErrorWithMessage: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
placeholder: 'Luke Skywalker',
|
|
38
|
+
label: 'Name',
|
|
39
|
+
status: 'error',
|
|
40
|
+
errorMessage: 'You are not my father',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Elevated: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
placeholder: 'Luke Skywalker',
|
|
47
|
+
label: 'Name',
|
|
48
|
+
variant: 'elevated',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const ElevatedError: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
placeholder: 'Luke Skywalker',
|
|
55
|
+
label: 'Name',
|
|
56
|
+
variant: 'elevated',
|
|
57
|
+
status: 'error',
|
|
58
|
+
errorMessage: 'Something went no no',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const WithAppend: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
placeholder: 'Luke Skywalker',
|
|
65
|
+
label: 'Name',
|
|
66
|
+
variant: 'elevated',
|
|
67
|
+
append: <div className="self-end">🍩</div>,
|
|
68
|
+
id: 'my-id',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Disabled: Story = {
|
|
73
|
+
args: {
|
|
74
|
+
placeholder: 'Luke Skywalker',
|
|
75
|
+
label: 'Name',
|
|
76
|
+
disabled: true,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Readonly: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
placeholder: 'Luke Skywalker',
|
|
83
|
+
label: 'Name',
|
|
84
|
+
readOnly: true,
|
|
85
|
+
value: 'Darth Vader',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const PassingPropsToLabel: Story = {
|
|
90
|
+
args: {
|
|
91
|
+
placeholder: 'Luke Skywalker',
|
|
92
|
+
label: 'Name',
|
|
93
|
+
value: 'Darth Vader',
|
|
94
|
+
labelProps: {
|
|
95
|
+
className: 'bg-elevated',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.c-input-with-label {
|
|
2
|
+
@apply relative block rounded border border-solid border-transparent p-3;
|
|
3
|
+
|
|
4
|
+
&-input-wrap {
|
|
5
|
+
@apply mt-3 flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
&-input {
|
|
9
|
+
@apply flex-1 overflow-hidden text-ellipsis text-base;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&-append {
|
|
13
|
+
@apply -mr-3 flex flex-shrink-0 items-center px-3;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&-elevated {
|
|
17
|
+
@apply bg-elevate shadow;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.c-input-with-label--error {
|
|
21
|
+
@apply text-error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&-error {
|
|
25
|
+
@apply !mt-1 text-xs text-error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&-sm {
|
|
29
|
+
@apply text-sm;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&-lg {
|
|
33
|
+
@apply text-lg;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ComponentPropsWithRef, forwardRef, ReactNode } from 'react';
|
|
2
|
+
import { cva, cx, VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { Input } from '../input';
|
|
5
|
+
import { Label } from '../label';
|
|
6
|
+
import { Triangle } from '../iconography/triangle';
|
|
7
|
+
|
|
8
|
+
const inputWithLabelStyles = cva(['c-input-with-label'], {
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: '',
|
|
12
|
+
elevated: 'c-input-with-label-elevated',
|
|
13
|
+
outlined: 'c-input-with-label-outlined',
|
|
14
|
+
},
|
|
15
|
+
status: {
|
|
16
|
+
error: 'c-input-with-label--error',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type InputWithLabelStylesProps = VariantProps<typeof inputWithLabelStyles>;
|
|
23
|
+
|
|
24
|
+
export type InputWithLabelProps = InputWithLabelStylesProps & {
|
|
25
|
+
label: string;
|
|
26
|
+
append?: ReactNode;
|
|
27
|
+
errorMessage?: string;
|
|
28
|
+
labelProps?: ComponentPropsWithoutRef<'label'>;
|
|
29
|
+
} & ComponentPropsWithRef<'input'>;
|
|
30
|
+
|
|
31
|
+
export type InputWithLabelRef = HTMLInputElement;
|
|
32
|
+
|
|
33
|
+
export const InputWithLabel = forwardRef<InputWithLabelRef, InputWithLabelProps>(
|
|
34
|
+
({ className, label, append, errorMessage, status, variant, id, labelProps, ...delegated }, ref) => {
|
|
35
|
+
const { className: labelClassName, ...labelPropsRest } = labelProps ?? {};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Label
|
|
40
|
+
className={inputWithLabelStyles({ status, variant, className: labelClassName })}
|
|
41
|
+
htmlFor={id}
|
|
42
|
+
{...labelPropsRest}
|
|
43
|
+
>
|
|
44
|
+
{label}
|
|
45
|
+
<span className="c-input-with-label-input-wrap">
|
|
46
|
+
<Input className={cx('c-input-with-label-input', className)} ref={ref} id={id} {...delegated} />
|
|
47
|
+
{!append && !errorMessage ? null : (
|
|
48
|
+
<div className="c-input-with-label-append" title={errorMessage}>
|
|
49
|
+
{errorMessage ? <Triangle /> : append}
|
|
50
|
+
</div>
|
|
51
|
+
)}
|
|
52
|
+
</span>
|
|
53
|
+
</Label>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
InputWithLabel.displayName = 'InputWithLabel';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './label';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Label } from './label';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Label> = {
|
|
5
|
+
title: 'Components/Label',
|
|
6
|
+
component: Label,
|
|
7
|
+
argTypes: {},
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof Label>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
args: {
|
|
16
|
+
htmlFor: 'id',
|
|
17
|
+
children: 'Hello, World!',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { forwardRef, LabelHTMLAttributes } from 'react';
|
|
2
|
+
import { cx } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import './label.css';
|
|
5
|
+
|
|
6
|
+
type LabelRef = HTMLLabelElement;
|
|
7
|
+
type LabelProps = LabelHTMLAttributes<LabelRef>;
|
|
8
|
+
|
|
9
|
+
export const Label = forwardRef<LabelRef, LabelProps>(({ className, ...delegated }, ref) => {
|
|
10
|
+
return <label ref={ref} className={cx('c-label', className)} {...delegated} />;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
Label.displayName = 'Label';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Progress } from '.';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Progress> = {
|
|
6
|
+
title: 'Components/Progress',
|
|
7
|
+
component: Progress,
|
|
8
|
+
argTypes: {},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof Progress>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
name: 'Default',
|
|
16
|
+
render: () => {
|
|
17
|
+
const [progress, setProgress] = useState(13);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const timer = setTimeout(() => setProgress(100), 1500);
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return <Progress value={progress} />;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Progress } from './progress';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as ProgressPrimitives from '@radix-ui/react-progress';
|
|
2
|
+
import { cx } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import './progress.css';
|
|
5
|
+
|
|
6
|
+
type ProgressProps = ProgressPrimitives.ProgressProps;
|
|
7
|
+
|
|
8
|
+
export function Progress({ className, value }: ProgressProps) {
|
|
9
|
+
return (
|
|
10
|
+
<ProgressPrimitives.Root className={cx(className, 'c-progress-root')} value={value}>
|
|
11
|
+
<ProgressPrimitives.Indicator
|
|
12
|
+
className="c-progress-indicator"
|
|
13
|
+
style={{ transform: `translateX(-${100 - (value ?? 0)}%)` }}
|
|
14
|
+
/>
|
|
15
|
+
</ProgressPrimitives.Root>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './radio';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.c-radio-item {
|
|
2
|
+
@apply flex h-3 w-3 flex-shrink-0 items-center justify-center rounded-full border border-solid border-gray-200-700 bg-gray-50-900 p-0;
|
|
3
|
+
|
|
4
|
+
&:focus,
|
|
5
|
+
&:focus-visible {
|
|
6
|
+
@apply outline-none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&:disabled {
|
|
10
|
+
@apply bg-gray-100-800 opacity-40;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&[aria-checked='true'] {
|
|
14
|
+
@apply border-cyan-700-200 bg-cyan-200-700;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.c-radio-indicator {
|
|
19
|
+
@apply block h-[6px] w-[6px] rounded-full bg-cyan-700-200;
|
|
20
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import { Radio } from './radio';
|
|
5
|
+
import { Label } from '../label';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Radio.Group> = {
|
|
8
|
+
title: 'Components/Radio',
|
|
9
|
+
component: Radio.Group,
|
|
10
|
+
argTypes: {},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof Radio.Group>;
|
|
16
|
+
|
|
17
|
+
export const Example: Story = {
|
|
18
|
+
render: () => {
|
|
19
|
+
return (
|
|
20
|
+
<Radio.Group defaultValue="default">
|
|
21
|
+
<div className="flex items-center space-x-2">
|
|
22
|
+
<Radio.Item value="default" id="example-r1" />
|
|
23
|
+
<Label htmlFor="example-r1">Default</Label>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="flex items-center space-x-2">
|
|
26
|
+
<Radio.Item value="comfortable" id="example-r2" />
|
|
27
|
+
<Label htmlFor="example-r2">Comfortable</Label>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex items-center space-x-2">
|
|
30
|
+
<Radio.Item value="compact" id="example-r3" />
|
|
31
|
+
<Label htmlFor="example-r3">Compact</Label>
|
|
32
|
+
</div>
|
|
33
|
+
</Radio.Group>
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Uncontrolled: Story = {
|
|
39
|
+
render: () => {
|
|
40
|
+
return (
|
|
41
|
+
<Radio.Group defaultValue="default">
|
|
42
|
+
<div className="flex items-center space-x-2">
|
|
43
|
+
<Radio.Item value="default" id="uncontrolled-r1" />
|
|
44
|
+
<Label htmlFor="uncontrolled-r1">Default</Label>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex items-center space-x-2">
|
|
47
|
+
<Radio.Item value="comfortable" id="uncontrolled-r2" />
|
|
48
|
+
<Label htmlFor="uncontrolled-r2">Comfortable</Label>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="flex items-center space-x-2">
|
|
51
|
+
<Radio.Item value="compact" id="uncontrolled-r3" />
|
|
52
|
+
<Label htmlFor="uncontrolled-r3">Compact</Label>
|
|
53
|
+
</div>
|
|
54
|
+
</Radio.Group>
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Controlled: Story = {
|
|
60
|
+
render: () => {
|
|
61
|
+
const [value, setValue] = useState('default');
|
|
62
|
+
return (
|
|
63
|
+
<Radio.Group value={value} onValueChange={v => setValue(v)}>
|
|
64
|
+
<div className="flex items-center space-x-2">
|
|
65
|
+
<Radio.Item value="default" id="controlled-r1" />
|
|
66
|
+
<Label htmlFor="controlled-r1">Default</Label>
|
|
67
|
+
</div>
|
|
68
|
+
<div className="flex items-center space-x-2">
|
|
69
|
+
<Radio.Item value="comfortable" id="controlled-r2" />
|
|
70
|
+
<Label htmlFor="controlled-r2">Comfortable</Label>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="flex items-center space-x-2">
|
|
73
|
+
<Radio.Item value="compact" id="controlled-r3" />
|
|
74
|
+
<Label htmlFor="controlled-r3">Compact</Label>
|
|
75
|
+
</div>
|
|
76
|
+
</Radio.Group>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Horizontal: Story = {
|
|
82
|
+
render: () => {
|
|
83
|
+
return (
|
|
84
|
+
<Radio.Group defaultValue="default" orientation="horizontal" className="flex space-x-6">
|
|
85
|
+
<div className="flex items-center space-x-2">
|
|
86
|
+
<Radio.Item value="default" id="horizontal-r1" />
|
|
87
|
+
<Label htmlFor="horizontal-r1">Default</Label>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="flex items-center space-x-2">
|
|
90
|
+
<Radio.Item value="comfortable" id="horizontal-r2" />
|
|
91
|
+
<Label htmlFor="horizontal-r2">Comfortable</Label>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex items-center space-x-2">
|
|
94
|
+
<Radio.Item value="compact" id="horizontal-r3" />
|
|
95
|
+
<Label htmlFor="horizontal-r3">Compact</Label>
|
|
96
|
+
</div>
|
|
97
|
+
</Radio.Group>
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const NoLoop: Story = {
|
|
103
|
+
render: () => {
|
|
104
|
+
return (
|
|
105
|
+
<Radio.Group defaultValue="default" loop={false}>
|
|
106
|
+
<div className="flex items-center space-x-2">
|
|
107
|
+
<Radio.Item value="default" id="noloop-r1" />
|
|
108
|
+
<Label htmlFor="noloop-r1">Default</Label>
|
|
109
|
+
</div>
|
|
110
|
+
<div className="flex items-center space-x-2">
|
|
111
|
+
<Radio.Item value="comfortable" id="noloop-r2" />
|
|
112
|
+
<Label htmlFor="noloop-r2">Comfortable</Label>
|
|
113
|
+
</div>
|
|
114
|
+
<div className="flex items-center space-x-2">
|
|
115
|
+
<Radio.Item value="compact" id="noloop-r3" />
|
|
116
|
+
<Label htmlFor="noloop-r3">Compact</Label>
|
|
117
|
+
</div>
|
|
118
|
+
</Radio.Group>
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const Disabled: Story = {
|
|
124
|
+
render: () => {
|
|
125
|
+
return (
|
|
126
|
+
<Radio.Group defaultValue="default">
|
|
127
|
+
<div className="flex items-center space-x-2">
|
|
128
|
+
<Radio.Item value="default" id="disabled-r1" />
|
|
129
|
+
<Label htmlFor="disabled-r1">Default</Label>
|
|
130
|
+
</div>
|
|
131
|
+
<div className="flex items-center space-x-2">
|
|
132
|
+
<Radio.Item value="comfortable" id="disabled-r2" disabled />
|
|
133
|
+
<Label htmlFor="disabled-r2">Comfortable</Label>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="flex items-center space-x-2">
|
|
136
|
+
<Radio.Item value="compact" id="disabled-r3" />
|
|
137
|
+
<Label htmlFor="disabled-r3">Compact</Label>
|
|
138
|
+
</div>
|
|
139
|
+
</Radio.Group>
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
import './radio.css';
|
|
5
|
+
|
|
6
|
+
type RadioGroupItemProps = ComponentProps<typeof RadioGroupPrimitive.Item>;
|
|
7
|
+
|
|
8
|
+
function RadioGroupItem(props: RadioGroupItemProps) {
|
|
9
|
+
return (
|
|
10
|
+
<RadioGroupPrimitive.Item {...props} className="c-radio-item">
|
|
11
|
+
<RadioGroupPrimitive.Indicator className="c-radio-indicator" />
|
|
12
|
+
</RadioGroupPrimitive.Item>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Radio = {
|
|
17
|
+
Group: RadioGroupPrimitive.Root,
|
|
18
|
+
Item: RadioGroupItem,
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './select';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ComponentProps, forwardRef } from 'react';
|
|
2
|
+
import * as SelectPrimitives from '@radix-ui/react-select';
|
|
3
|
+
|
|
4
|
+
export type SelectItemRef = HTMLDivElement;
|
|
5
|
+
export type SelectItemProps = ComponentProps<typeof SelectPrimitives.Item>;
|
|
6
|
+
|
|
7
|
+
export const SelectItem = forwardRef<SelectItemRef, SelectItemProps>((props, ref) => {
|
|
8
|
+
const { children, ...delegated } = props;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<SelectPrimitives.Item className="c-select-item" ref={ref} {...delegated}>
|
|
12
|
+
<SelectPrimitives.ItemText>{children}</SelectPrimitives.ItemText>
|
|
13
|
+
<SelectPrimitives.ItemIndicator />
|
|
14
|
+
</SelectPrimitives.Item>
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
SelectItem.displayName = 'SelectItem';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ComponentProps, forwardRef } from 'react';
|
|
2
|
+
import * as SelectPrimitives from '@radix-ui/react-select';
|
|
3
|
+
|
|
4
|
+
import { Icon } from '../iconography';
|
|
5
|
+
|
|
6
|
+
export type SelectRef = HTMLButtonElement;
|
|
7
|
+
export type SelectProps = ComponentProps<typeof SelectPrimitives.Root> & {
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
id?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const SelectContainer = forwardRef<SelectRef, SelectProps>(
|
|
14
|
+
({ children, id, placeholder, disabled, ...delegated }, ref) => {
|
|
15
|
+
return (
|
|
16
|
+
<SelectPrimitives.Root {...delegated}>
|
|
17
|
+
<SelectPrimitives.Trigger ref={ref} className="c-select-trigger" disabled={disabled} id={id}>
|
|
18
|
+
<SelectPrimitives.Value placeholder={<span className="c-select-value">{placeholder ?? 'Select...'}</span>} />
|
|
19
|
+
<Icon.Arrow />
|
|
20
|
+
</SelectPrimitives.Trigger>
|
|
21
|
+
|
|
22
|
+
<SelectPrimitives.Portal>
|
|
23
|
+
<SelectPrimitives.Content className="c-select-content">
|
|
24
|
+
<SelectPrimitives.ScrollUpButton />
|
|
25
|
+
<SelectPrimitives.Viewport className="c-select-viewport">{children}</SelectPrimitives.Viewport>
|
|
26
|
+
<SelectPrimitives.ScrollDownButton />
|
|
27
|
+
</SelectPrimitives.Content>
|
|
28
|
+
</SelectPrimitives.Portal>
|
|
29
|
+
</SelectPrimitives.Root>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
SelectContainer.displayName = 'Select';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.c-select-trigger {
|
|
2
|
+
@apply inline-flex cursor-pointer items-center justify-center gap-4 whitespace-nowrap rounded border-0 bg-elevate px-4 py-3 text-sm font-medium text-gray shadow;
|
|
3
|
+
|
|
4
|
+
&:disabled {
|
|
5
|
+
@apply cursor-default opacity-50;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.c-select-value {
|
|
10
|
+
@apply italic text-gray-500-400;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.c-select-content {
|
|
14
|
+
@apply overflow-hidden rounded bg-elevate text-gray shadow;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.c-select-viewport {
|
|
18
|
+
@apply py-2;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.c-select-item {
|
|
22
|
+
@apply flex cursor-pointer items-center whitespace-nowrap px-6 py-2 font-medium text-gray;
|
|
23
|
+
|
|
24
|
+
&:hover,
|
|
25
|
+
&:focus {
|
|
26
|
+
@apply bg-gray-50-900 outline-none;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { Meta } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import { Select } from './select';
|
|
5
|
+
import { Label } from '../label';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/Select',
|
|
9
|
+
parameters: {
|
|
10
|
+
backgrounds: { default: 'gray' },
|
|
11
|
+
},
|
|
12
|
+
} as Meta<typeof Select.Container>;
|
|
13
|
+
|
|
14
|
+
export const Example = () => {
|
|
15
|
+
return (
|
|
16
|
+
<Select.Container>
|
|
17
|
+
<Select.Item value="react">React</Select.Item>
|
|
18
|
+
<Select.Item value="vue">Vue</Select.Item>
|
|
19
|
+
<Select.Item value="svelte">Svelte</Select.Item>
|
|
20
|
+
<Select.Item value="angular">Angular</Select.Item>
|
|
21
|
+
</Select.Container>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Uncontrolled = () => {
|
|
26
|
+
return (
|
|
27
|
+
<Select.Container>
|
|
28
|
+
<Select.Item value="react">React</Select.Item>
|
|
29
|
+
<Select.Item value="vue">Vue</Select.Item>
|
|
30
|
+
<Select.Item value="svelte">Svelte</Select.Item>
|
|
31
|
+
<Select.Item value="angular">Angular</Select.Item>
|
|
32
|
+
</Select.Container>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Controlled = () => {
|
|
37
|
+
const [value, setValue] = useState('react');
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Select.Container value={value} onValueChange={setValue}>
|
|
41
|
+
<Select.Item value="react">React</Select.Item>
|
|
42
|
+
<Select.Item value="vue">Vue</Select.Item>
|
|
43
|
+
<Select.Item value="svelte">Svelte</Select.Item>
|
|
44
|
+
<Select.Item value="angular">Angular</Select.Item>
|
|
45
|
+
</Select.Container>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const WithLabel = () => {
|
|
50
|
+
const [value, setValue] = useState('react');
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex flex-col items-start space-y-2">
|
|
54
|
+
<Label htmlFor="with-label-c1">Select framework</Label>
|
|
55
|
+
<Select.Container id="with-label-c1" value={value} onValueChange={setValue}>
|
|
56
|
+
<Select.Item value="react">React</Select.Item>
|
|
57
|
+
<Select.Item value="vue">Vue</Select.Item>
|
|
58
|
+
<Select.Item value="svelte">Svelte</Select.Item>
|
|
59
|
+
<Select.Item value="angular">Angular</Select.Item>
|
|
60
|
+
</Select.Container>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Disabled = () => {
|
|
66
|
+
return (
|
|
67
|
+
<Select.Container disabled>
|
|
68
|
+
<Select.Item value="react">React</Select.Item>
|
|
69
|
+
<Select.Item value="vue">Vue</Select.Item>
|
|
70
|
+
<Select.Item value="svelte">Svelte</Select.Item>
|
|
71
|
+
<Select.Item value="angular">Angular</Select.Item>
|
|
72
|
+
</Select.Container>
|
|
73
|
+
);
|
|
74
|
+
};
|