@crystallize/design-system 0.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/dist/index.css +926 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +1023 -0
- package/dist/index.mjs +981 -0
- package/package.json +73 -0
- package/src/action-menu/ActionMenu.stories.tsx +23 -0
- package/src/action-menu/action-item.tsx +24 -0
- package/src/action-menu/action-menu.tsx +37 -0
- package/src/action-menu/index.tsx +1 -0
- package/src/button/Button.stories.tsx +63 -0
- package/src/button/button.tsx +50 -0
- package/src/button/index.ts +3 -0
- package/src/button copy/ButtonCopy.stories.tsx +86 -0
- package/src/button copy/button.tsx +61 -0
- package/src/button copy/index.ts +3 -0
- package/src/card/card.stories.tsx +24 -0
- package/src/card/card.tsx +25 -0
- package/src/card/index.ts +1 -0
- package/src/colors/Colors.stories.mdx +33 -0
- package/src/dialog/Dialog.stories.tsx +165 -0
- package/src/dialog/config.tsx +134 -0
- package/src/dialog/confirm-dialog.tsx +59 -0
- package/src/dialog/destroyFns.ts +1 -0
- package/src/dialog/dialog.tsx +85 -0
- package/src/dialog/index.tsx +40 -0
- package/src/dialog/types.ts +38 -0
- package/src/dropdown-menu/DropdownMenu.stories.tsx +47 -0
- package/src/dropdown-menu/dropdown-menu-item.tsx +24 -0
- package/src/dropdown-menu/dropdown-menu-label.tsx +17 -0
- package/src/dropdown-menu/dropdown-menu-root.tsx +20 -0
- package/src/dropdown-menu/index.ts +9 -0
- package/src/icon-button/IconButton.stories.tsx +35 -0
- package/src/icon-button/icon-button.tsx +42 -0
- package/src/icon-button/index.ts +3 -0
- package/src/icons/Iconography.stories.mdx +45 -0
- package/src/icons/arrow.tsx +15 -0
- package/src/icons/cancel.tsx +26 -0
- package/src/icons/dots.tsx +24 -0
- package/src/icons/error.tsx +50 -0
- package/src/icons/glasses.tsx +62 -0
- package/src/icons/graphQL.tsx +90 -0
- package/src/icons/index.ts +21 -0
- package/src/icons/info.tsx +53 -0
- package/src/icons/nail-polish.tsx +84 -0
- package/src/icons/warning.tsx +62 -0
- package/src/index.css +3 -0
- package/src/index.ts +8 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { unmountComponentAtNode, render as DOMRender } from 'react-dom';
|
|
2
|
+
import { ConfirmDialog } from './confirm-dialog';
|
|
3
|
+
|
|
4
|
+
import { destroyFns } from './destroyFns';
|
|
5
|
+
import type { ConfigUpdate, DialogFuncProps } from './types';
|
|
6
|
+
|
|
7
|
+
export function confirm(config: DialogFuncProps) {
|
|
8
|
+
const container = document.createDocumentFragment();
|
|
9
|
+
let currentConfig = { ...config, close, open: true };
|
|
10
|
+
let timeoutId: NodeJS.Timeout;
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
function close(...args: any[]) {
|
|
14
|
+
currentConfig = {
|
|
15
|
+
...currentConfig,
|
|
16
|
+
open: false,
|
|
17
|
+
afterClose: () => {
|
|
18
|
+
if (typeof config.afterClose === 'function') {
|
|
19
|
+
config.afterClose();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
destroy.apply(this, args);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
render(currentConfig);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
function destroy(...args: any[]) {
|
|
31
|
+
const triggerCancel = args.some(param => param && param.triggerCancel);
|
|
32
|
+
|
|
33
|
+
if (config.onCancel && triggerCancel) {
|
|
34
|
+
config.onCancel(() => {}, ...args.slice(1));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < destroyFns.length; i++) {
|
|
38
|
+
const fn = destroyFns[i];
|
|
39
|
+
|
|
40
|
+
if (fn === close) {
|
|
41
|
+
destroyFns.splice(i, 1);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
unmountComponentAtNode(container);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function render({ okText, cancelText, ...delegated }: any) {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
|
|
52
|
+
timeoutId = setTimeout(() => {
|
|
53
|
+
DOMRender(<ConfirmDialog {...delegated} okText={okText} cancelText={cancelText} />, container);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function update(configUpdate: ConfigUpdate) {
|
|
58
|
+
if (typeof configUpdate === 'function') {
|
|
59
|
+
currentConfig = configUpdate(currentConfig);
|
|
60
|
+
} else {
|
|
61
|
+
currentConfig = {
|
|
62
|
+
...currentConfig,
|
|
63
|
+
...configUpdate,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render(currentConfig);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(currentConfig);
|
|
71
|
+
|
|
72
|
+
destroyFns.push(close);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
destroy: close,
|
|
76
|
+
update,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dumbFn = (event: Event) => {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function withDialog(delegated: DialogFuncProps): DialogFuncProps {
|
|
85
|
+
return {
|
|
86
|
+
...delegated,
|
|
87
|
+
open: true,
|
|
88
|
+
okCancel: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function withWarning(delegated: DialogFuncProps): DialogFuncProps {
|
|
93
|
+
return {
|
|
94
|
+
...delegated,
|
|
95
|
+
open: true,
|
|
96
|
+
okCancel: false,
|
|
97
|
+
type: 'warning',
|
|
98
|
+
onInteractOutside: dumbFn,
|
|
99
|
+
onPointerDownOutside: dumbFn,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function withError(delegated: DialogFuncProps): DialogFuncProps {
|
|
104
|
+
return {
|
|
105
|
+
...delegated,
|
|
106
|
+
open: true,
|
|
107
|
+
okCancel: false,
|
|
108
|
+
type: 'error',
|
|
109
|
+
onInteractOutside: dumbFn,
|
|
110
|
+
onPointerDownOutside: dumbFn,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function withInfo(delegated: DialogFuncProps): DialogFuncProps {
|
|
115
|
+
return {
|
|
116
|
+
...delegated,
|
|
117
|
+
open: true,
|
|
118
|
+
okCancel: false,
|
|
119
|
+
type: 'info',
|
|
120
|
+
onInteractOutside: dumbFn,
|
|
121
|
+
onPointerDownOutside: dumbFn,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function withConfirm(delegated: DialogFuncProps): DialogFuncProps {
|
|
126
|
+
return {
|
|
127
|
+
...delegated,
|
|
128
|
+
open: true,
|
|
129
|
+
okCancel: true,
|
|
130
|
+
type: 'info',
|
|
131
|
+
onInteractOutside: dumbFn,
|
|
132
|
+
onPointerDownOutside: dumbFn,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Dialog } from '.';
|
|
2
|
+
import { Button } from '../button';
|
|
3
|
+
import { DialogRoot } from './dialog';
|
|
4
|
+
import type { ConfirmDialogProps } from './types';
|
|
5
|
+
|
|
6
|
+
export function ConfirmDialog({
|
|
7
|
+
open,
|
|
8
|
+
close,
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
content,
|
|
12
|
+
okCancel,
|
|
13
|
+
okText = 'OK',
|
|
14
|
+
onOk,
|
|
15
|
+
onCancel,
|
|
16
|
+
cancelText = 'cancel',
|
|
17
|
+
onEscapeKeyDown,
|
|
18
|
+
onInteractOutside,
|
|
19
|
+
onPointerDownOutside,
|
|
20
|
+
type,
|
|
21
|
+
}: ConfirmDialogProps) {
|
|
22
|
+
const cancelButton = okCancel && (
|
|
23
|
+
<Button
|
|
24
|
+
variant="secondary"
|
|
25
|
+
onClick={() => {
|
|
26
|
+
onCancel?.();
|
|
27
|
+
close();
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{cancelText}
|
|
31
|
+
</Button>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<DialogRoot open={open} onOpenChange={() => close()}>
|
|
36
|
+
<Dialog.Content
|
|
37
|
+
onEscapeKeyDown={onEscapeKeyDown}
|
|
38
|
+
onInteractOutside={onInteractOutside}
|
|
39
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
40
|
+
type={type}
|
|
41
|
+
>
|
|
42
|
+
{title && <Dialog.Title>{title}</Dialog.Title>}
|
|
43
|
+
{description && <Dialog.Description>{description}</Dialog.Description>}
|
|
44
|
+
{content}
|
|
45
|
+
<div className="flex items-center gap-4 mt-4 justify-end">
|
|
46
|
+
{cancelButton}
|
|
47
|
+
<Button
|
|
48
|
+
onClick={() => {
|
|
49
|
+
onOk?.();
|
|
50
|
+
close();
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
{okText}
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
</Dialog.Content>
|
|
57
|
+
</DialogRoot>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const destroyFns: Array<() => void> = [];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
2
|
+
import type { ReactNode, RefAttributes } from 'react';
|
|
3
|
+
import { cva, VariantProps } from 'class-variance-authority';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../button';
|
|
6
|
+
import { Icon } from '../icons';
|
|
7
|
+
import type { DialogFuncProps } from './types';
|
|
8
|
+
|
|
9
|
+
const IconMap = {
|
|
10
|
+
error: Icon.Error,
|
|
11
|
+
info: Icon.Info,
|
|
12
|
+
warning: Icon.Warning,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type DialogContentStylesProps = VariantProps<typeof dialogContentStyles>;
|
|
16
|
+
const dialogContentStyles = cva(
|
|
17
|
+
'bg-white rounded shadow fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-w-xl w-auto p-6',
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
withIcon: {
|
|
21
|
+
true: 'flex gap-4 items-start',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
type DialogContentProps = DialogContentStylesProps & {
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
} & Pick<DialogFuncProps, 'onEscapeKeyDown' | 'onInteractOutside' | 'onPointerDownOutside' | 'closable' | 'type'>;
|
|
30
|
+
|
|
31
|
+
function DialogContent({ children, closable = true, type, ...delegated }: DialogContentProps) {
|
|
32
|
+
const withIcon = typeof type !== 'undefined';
|
|
33
|
+
|
|
34
|
+
const IconComponent = type && IconMap[type];
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<DialogPrimitive.Portal>
|
|
38
|
+
<DialogPrimitive.Overlay className="bg-black/30 fixed inset-0" />
|
|
39
|
+
<DialogPrimitive.Content className={dialogContentStyles({ withIcon })} {...delegated}>
|
|
40
|
+
{IconComponent && <IconComponent className="my-1 shrink-0" width={32} height={32} />}
|
|
41
|
+
{closable && (
|
|
42
|
+
<DialogClose asChild>
|
|
43
|
+
<Button className="absolute top-2.5 right-2.5 !rounded-full !p-0 h-6 w-6 inline-flex items-center justify-center !bg-transparent hover:!bg-gray-200 focus:!bg-gray-200 !drop-shadow-none">
|
|
44
|
+
<Icon.Cancel color="density" aria-label="Close" height={18} width={18} />
|
|
45
|
+
</Button>
|
|
46
|
+
</DialogClose>
|
|
47
|
+
)}
|
|
48
|
+
<div>{children}</div>
|
|
49
|
+
</DialogPrimitive.Content>
|
|
50
|
+
</DialogPrimitive.Portal>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type DialogTitleProps = JSX.IntrinsicAttributes & DialogPrimitive.DialogTitleProps & RefAttributes<HTMLHeadingElement>;
|
|
55
|
+
|
|
56
|
+
function DialogTitle(delegated: DialogTitleProps) {
|
|
57
|
+
return <DialogPrimitive.Title className="m-0 font-semibold text-2xl font-sans text-black-text" {...delegated} />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type DialogDescriptionProps = JSX.IntrinsicAttributes &
|
|
61
|
+
DialogPrimitive.DialogDescriptionProps &
|
|
62
|
+
RefAttributes<HTMLParagraphElement>;
|
|
63
|
+
|
|
64
|
+
function DialogDescription(delegated: DialogDescriptionProps) {
|
|
65
|
+
return <DialogPrimitive.Description className="mt-2 mb-5 text-gray-600" {...delegated} />;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
69
|
+
|
|
70
|
+
const DialogClose = DialogPrimitive.Close;
|
|
71
|
+
|
|
72
|
+
export const DialogRoot = DialogPrimitive.Root;
|
|
73
|
+
|
|
74
|
+
type DialogBaseProps = {
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export function DialogBase({ children }: DialogBaseProps) {
|
|
79
|
+
return <DialogRoot>{children}</DialogRoot>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
DialogBase.Title = DialogTitle;
|
|
83
|
+
DialogBase.Trigger = DialogTrigger;
|
|
84
|
+
DialogBase.Description = DialogDescription;
|
|
85
|
+
DialogBase.Content = DialogContent;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { destroyFns } from './destroyFns';
|
|
2
|
+
import { DialogBase } from './dialog';
|
|
3
|
+
import type { DialogFuncProps } from './types';
|
|
4
|
+
import { confirm, withWarning, withConfirm, withDialog, withError, withInfo } from './config';
|
|
5
|
+
|
|
6
|
+
type DialogType = typeof DialogBase & DialogFuncProps;
|
|
7
|
+
|
|
8
|
+
const Dialog = DialogBase as DialogType;
|
|
9
|
+
|
|
10
|
+
function showDialog(delegated: DialogFuncProps) {
|
|
11
|
+
return confirm(withDialog(delegated));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function showWarning(delegated: DialogFuncProps) {
|
|
15
|
+
return confirm(withWarning(delegated));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function showConfirm(delegated: DialogFuncProps) {
|
|
19
|
+
return confirm(withConfirm(delegated));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function showError(delegated: DialogFuncProps) {
|
|
23
|
+
return confirm(withError(delegated));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function showInfo(delegated: DialogFuncProps) {
|
|
27
|
+
return confirm(withInfo(delegated));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function destroyAll() {
|
|
31
|
+
while (destroyFns.length) {
|
|
32
|
+
const close = destroyFns.pop();
|
|
33
|
+
|
|
34
|
+
if (close) {
|
|
35
|
+
close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { Dialog, destroyAll, showWarning, showConfirm, showDialog, showError, showInfo };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type DialogFuncProps = {
|
|
4
|
+
afterClose?: () => void;
|
|
5
|
+
cancelText?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
closable?: boolean;
|
|
8
|
+
content?: ReactNode;
|
|
9
|
+
description?: ReactNode;
|
|
10
|
+
okCancel?: boolean;
|
|
11
|
+
okText?: ReactNode;
|
|
12
|
+
onCancel?: (...args: unknown[]) => void;
|
|
13
|
+
onEscapeKeyDown?: (event: Event) => void;
|
|
14
|
+
onInteractOutside?: (event: Event) => void;
|
|
15
|
+
onOk?: (...args: unknown[]) => void;
|
|
16
|
+
onPointerDownOutside?: (event: Event) => void;
|
|
17
|
+
open?: boolean;
|
|
18
|
+
prefixCls?: string;
|
|
19
|
+
title?: ReactNode;
|
|
20
|
+
type?: 'info' | 'error' | 'warning';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ConfigUpdate = DialogFuncProps | ((prevConfig: DialogFuncProps) => DialogFuncProps);
|
|
24
|
+
|
|
25
|
+
export type DialogFunc = (props: DialogFuncProps) => {
|
|
26
|
+
destroy: () => void;
|
|
27
|
+
update: (configUpdate: ConfigUpdate) => void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ModalStaticFunctions = Record<NonNullable<DialogFuncProps['type']>, DialogFunc>;
|
|
31
|
+
|
|
32
|
+
export interface ConfirmDialogProps extends DialogFuncProps {
|
|
33
|
+
afterClose?: () => void;
|
|
34
|
+
close: (...args: unknown[]) => void;
|
|
35
|
+
autoFocusButton?: null | 'ok' | 'cancel';
|
|
36
|
+
rootPrefixCls: string;
|
|
37
|
+
iconPrefixCls?: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta } from '@storybook/react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { DropdownMenu } from '.';
|
|
6
|
+
import { Button } from '../button';
|
|
7
|
+
import { Icon } from '../icons';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Components/DropdownMenu',
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
} as Meta<any>;
|
|
13
|
+
|
|
14
|
+
function ContentWithIcon() {
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<DropdownMenu.Label>View</DropdownMenu.Label>
|
|
18
|
+
<DropdownMenu.Item>
|
|
19
|
+
<div className="flex items-center">
|
|
20
|
+
<Icon.Glasses width={24} height={24} />
|
|
21
|
+
<span className="px-4">Nerdy</span>
|
|
22
|
+
</div>
|
|
23
|
+
</DropdownMenu.Item>
|
|
24
|
+
<DropdownMenu.Item>
|
|
25
|
+
<div className="flex items-center">
|
|
26
|
+
<Icon.GraphQL width={24} height={24} />
|
|
27
|
+
<span className="px-4">Developer</span>
|
|
28
|
+
</div>
|
|
29
|
+
</DropdownMenu.Item>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const WithOpenState = () => {
|
|
35
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<DropdownMenu.Root content={<ContentWithIcon />} onOpenChange={setIsOpen}>
|
|
39
|
+
<Button>
|
|
40
|
+
<div className="flex items-center">
|
|
41
|
+
<Icon.NailPolish width={20} height={20} /> <span className="pl-4 pr-8">Pretty</span>
|
|
42
|
+
<Icon.Arrow className={clsx({ 'rotate-180': isOpen })} />
|
|
43
|
+
</div>
|
|
44
|
+
</Button>
|
|
45
|
+
</DropdownMenu.Root>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
|
|
5
|
+
type DropdownMenuItemProps = DropdownMenuPrimitive.MenuItemProps & {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function DropdownMenuItem({ children, className, ...delegated }: DropdownMenuItemProps) {
|
|
10
|
+
return (
|
|
11
|
+
<DropdownMenuPrimitive.Item
|
|
12
|
+
{...delegated}
|
|
13
|
+
className={clsx(
|
|
14
|
+
'text-xs font-medium text-black-text',
|
|
15
|
+
'flex h-10 cursor-pointer items-center bg-white px-4 outline-asteroid',
|
|
16
|
+
'hover:bg-[#F8F8F9] hover:outline-none hover:focus-visible:outline-none',
|
|
17
|
+
'first:rounded-tr first:rounded-tl last:rounded-br last:rounded-bl',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</DropdownMenuPrimitive.Item>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
4
|
+
|
|
5
|
+
type DropdownMenuLabelProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function DropdownMenuLabel({ children }: DropdownMenuLabelProps) {
|
|
10
|
+
return (
|
|
11
|
+
<DropdownMenuPrimitive.Label
|
|
12
|
+
className={clsx('bg-white px-4 py-2 text-xs text-label', 'first:rounded-tl first:rounded-tr')}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</DropdownMenuPrimitive.Label>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
+
|
|
4
|
+
type DropdownMenuRootProps = {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
content: ReactNode;
|
|
7
|
+
alignContent?: 'start' | 'center' | 'end';
|
|
8
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function DropdownMenuRoot({ children, content, onOpenChange, alignContent = 'start' }: DropdownMenuRootProps) {
|
|
12
|
+
return (
|
|
13
|
+
<DropdownMenuPrimitive.Root onOpenChange={onOpenChange}>
|
|
14
|
+
<DropdownMenuPrimitive.Trigger asChild>{children}</DropdownMenuPrimitive.Trigger>
|
|
15
|
+
<DropdownMenuPrimitive.Content align={alignContent} sideOffset={5} className="shadow">
|
|
16
|
+
{content}
|
|
17
|
+
</DropdownMenuPrimitive.Content>
|
|
18
|
+
</DropdownMenuPrimitive.Root>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DropdownMenuRoot } from './dropdown-menu-root';
|
|
2
|
+
import { DropdownMenuItem } from './dropdown-menu-item';
|
|
3
|
+
import { DropdownMenuLabel } from './dropdown-menu-label';
|
|
4
|
+
|
|
5
|
+
export const DropdownMenu = {
|
|
6
|
+
Root: DropdownMenuRoot,
|
|
7
|
+
Item: DropdownMenuItem,
|
|
8
|
+
Label: DropdownMenuLabel,
|
|
9
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StoryObj, Meta } from '@storybook/react';
|
|
2
|
+
import { IconButton } from '.';
|
|
3
|
+
import { Icon } from '../icons';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof IconButton> = {
|
|
6
|
+
title: 'Components/IconButton',
|
|
7
|
+
component: IconButton,
|
|
8
|
+
argTypes: {},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof IconButton>;
|
|
13
|
+
|
|
14
|
+
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
|
|
15
|
+
|
|
16
|
+
export const AllButton: Story = {
|
|
17
|
+
name: 'All Buttons',
|
|
18
|
+
render: () => {
|
|
19
|
+
return (
|
|
20
|
+
<div className="grid grid-cols-5 gap-6 justify-items-center">
|
|
21
|
+
{sizes.map(size => (
|
|
22
|
+
<IconButton size={size}>
|
|
23
|
+
<Icon.Cancel width={16} height={16} />
|
|
24
|
+
</IconButton>
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
children: <Icon.Cancel width={16} height={16} />,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ComponentPropsWithRef, forwardRef } from 'react';
|
|
2
|
+
import { cva, VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
type IconButtonStylesProps = VariantProps<typeof buttonStyles>;
|
|
5
|
+
const buttonStyles = cva(
|
|
6
|
+
[
|
|
7
|
+
'flex items-center justify-center rounded-full font-medium cursor-pointer',
|
|
8
|
+
'disabled:cursor-default disabled:scale-100 disabled:shadow-none',
|
|
9
|
+
'active:scale-95',
|
|
10
|
+
'focus:shadow focus:bg-slate-200',
|
|
11
|
+
'hover:shadow hover:bg-slate-200',
|
|
12
|
+
'focus-visible:outline-inherit focus-visible:outline-offset-1 focus-visible:outline focus-visible:outline-1',
|
|
13
|
+
],
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
xs: 'h-8 w-8',
|
|
18
|
+
sm: 'h-9 w-9',
|
|
19
|
+
md: 'h-10 w-10',
|
|
20
|
+
lg: 'h-10 w-10',
|
|
21
|
+
xl: 'h-11 w-11',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
size: 'sm',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export type IconButtonProps = ComponentPropsWithRef<'button'> & IconButtonStylesProps;
|
|
31
|
+
|
|
32
|
+
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
33
|
+
({ children, className, type = 'button', size, ...delegated }, ref) => {
|
|
34
|
+
return (
|
|
35
|
+
<button ref={ref} type={type} className={buttonStyles({ size, className })} {...delegated}>
|
|
36
|
+
{children}
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
IconButton.displayName = 'Button';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Meta, Title, IconGallery, IconItem } from '@storybook/addon-docs/';
|
|
2
|
+
|
|
3
|
+
import { Icon as IconExample } from '.';
|
|
4
|
+
|
|
5
|
+
<Meta title="Iconography" />
|
|
6
|
+
|
|
7
|
+
# Iconography
|
|
8
|
+
|
|
9
|
+
<IconGallery>
|
|
10
|
+
<IconItem name="Arrow" >
|
|
11
|
+
<IconExample.Arrow />
|
|
12
|
+
</IconItem>
|
|
13
|
+
|
|
14
|
+
<IconItem name="Cancel" >
|
|
15
|
+
<IconExample.Cancel />
|
|
16
|
+
</IconItem>
|
|
17
|
+
|
|
18
|
+
<IconItem name="Dots">
|
|
19
|
+
<IconExample.Dots />
|
|
20
|
+
</IconItem>
|
|
21
|
+
|
|
22
|
+
<IconItem name="Glasses" >
|
|
23
|
+
<IconExample.Glasses />
|
|
24
|
+
</IconItem>
|
|
25
|
+
|
|
26
|
+
<IconItem name="GraphQL" >
|
|
27
|
+
<IconExample.GraphQL />
|
|
28
|
+
</IconItem>
|
|
29
|
+
|
|
30
|
+
<IconItem name="NailPolish" >
|
|
31
|
+
<IconExample.NailPolish />
|
|
32
|
+
</IconItem>
|
|
33
|
+
|
|
34
|
+
<IconItem name="Error" >
|
|
35
|
+
<IconExample.Error />
|
|
36
|
+
</IconItem>
|
|
37
|
+
|
|
38
|
+
<IconItem name="Info" >
|
|
39
|
+
<IconExample.Info />
|
|
40
|
+
</IconItem>
|
|
41
|
+
|
|
42
|
+
<IconItem name="Warning" >
|
|
43
|
+
<IconExample.Warning />
|
|
44
|
+
</IconItem>
|
|
45
|
+
</IconGallery>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { forwardRef, SVGProps } from 'react';
|
|
2
|
+
|
|
3
|
+
type ArrowProps = SVGProps<SVGSVGElement>;
|
|
4
|
+
|
|
5
|
+
type ArrowRef = SVGSVGElement;
|
|
6
|
+
|
|
7
|
+
export const Arrow = forwardRef<ArrowRef, ArrowProps>((delegated, ref) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg ref={ref} width="10" height="10" viewBox="0 0 10 10" fill="currentColor" {...delegated}>
|
|
10
|
+
<path d="M4.14995 9.85C4.24341 9.94161 4.36907 9.99293 4.49995 9.99293C4.63083 9.99293 4.75649 9.94161 4.84995 9.85L8.03995 6.67C8.09195 6.6248 8.13404 6.56934 8.16359 6.5071C8.19314 6.44486 8.20951 6.37719 8.21167 6.30832C8.21383 6.23946 8.20173 6.17089 8.17614 6.10693C8.15055 6.04296 8.11201 5.98497 8.06295 5.9366C8.01389 5.88823 7.95536 5.85052 7.89104 5.82584C7.82671 5.80116 7.75798 5.79003 7.68915 5.79317C7.62033 5.79631 7.55289 5.81363 7.49108 5.84406C7.42927 5.87449 7.37441 5.91737 7.32995 5.97L4.49995 8.78L1.66995 5.96C1.62475 5.908 1.56929 5.86591 1.50705 5.83636C1.44481 5.80681 1.37714 5.79044 1.30827 5.78828C1.23941 5.78612 1.17084 5.79822 1.10688 5.82381C1.04291 5.8494 0.98492 5.88794 0.936549 5.937C0.888179 5.98606 0.850469 6.04459 0.825787 6.10892C0.801105 6.17324 0.789983 6.24197 0.79312 6.3108C0.796256 6.37962 0.813582 6.44706 0.844012 6.50887C0.874442 6.57068 0.917318 6.62554 0.96995 6.67L4.14995 9.85ZM4.49995 -2.18557e-08C4.22381 -3.39261e-08 3.99995 0.223858 3.99995 0.5L3.99995 9.5L4.99995 9.5L4.99995 0.5C4.99995 0.223857 4.77609 -9.78527e-09 4.49995 -2.18557e-08Z" />
|
|
11
|
+
</svg>
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
Arrow.displayName = 'ArrowIcon';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { forwardRef, SVGProps } from 'react';
|
|
2
|
+
|
|
3
|
+
type CancelProps = SVGProps<SVGSVGElement>;
|
|
4
|
+
|
|
5
|
+
type CancelRef = SVGSVGElement;
|
|
6
|
+
|
|
7
|
+
export const Cancel = forwardRef<CancelRef, CancelProps>((delegated, ref) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
ref={ref}
|
|
11
|
+
width="34"
|
|
12
|
+
height="34"
|
|
13
|
+
viewBox="0 0 34 34"
|
|
14
|
+
fill="none"
|
|
15
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
16
|
+
{...delegated}
|
|
17
|
+
>
|
|
18
|
+
<path
|
|
19
|
+
d="m27.4301 24.4325-.1299.1299c-.17.15-.6497.5798-1.1695 1.0595l-1.7392 1.5993-.1499.05a.2783.2783 0 0 1-.2299-.14l-3.6183-3.7182-2.6088-2.6388a1.0488 1.0488 0 0 0-.7926-.3838 1.006 1.006 0 0 0-.7096.2899l-5.6474 5.6773-.7696.7496-.07.18h-.2599l-2.6987-2.7088a.3813.3813 0 0 1-.18-.2598l.02-.14 5.6674-5.6173 1.2394-1.2395a.2995.2995 0 0 0 .1-.2398.2903.2903 0 0 0-.1-.2299l-1.8591-1.8692-2.8487-2.8786-1.5692-1.5992-.6197-.6597-.1-.09v-.12a.3689.3689 0 0 1 .12-.2398l2.7786-2.6588.11-.09h.14l.1099.06 5.7273 5.9272.8696.8796a.6998.6998 0 0 0 .9995 0l5.9672-6.0071.1799-.18a4.5467 4.5467 0 0 1 .5597-.5197.5585.5585 0 0 1 .19-.1c.1099 0 .2099.13.2998.22l.08.0899 2.039 2.109s.1899.2.2299.2499a.864.864 0 0 0 .1399.13.7704.7704 0 0 1 .13.1199c.0899.1499 0 .2598-.1799.4298l-6.1172 6.0871a1.3994 1.3994 0 0 0-.5197.7696.9287.9287 0 0 0 .2899.7997l3.0185 2.9786 3.4284 3.1785a2.001 2.001 0 0 1 .1999.2398.2564.2564 0 0 1 .0853.1524.2564.2564 0 0 1-.0323.1715Z"
|
|
20
|
+
fill="currentColor"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
Cancel.displayName = 'CancelIcon';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { forwardRef, SVGProps } from 'react';
|
|
2
|
+
|
|
3
|
+
type DotsProps = SVGProps<SVGSVGElement>;
|
|
4
|
+
type DotsRef = SVGSVGElement;
|
|
5
|
+
|
|
6
|
+
export const Dots = forwardRef<DotsRef, DotsProps>((delegated, ref) => {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
ref={ref}
|
|
10
|
+
width="20"
|
|
11
|
+
height="20"
|
|
12
|
+
viewBox="0 0 20 20"
|
|
13
|
+
fill="none"
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
{...delegated}
|
|
16
|
+
>
|
|
17
|
+
<circle cx="10" cy="5" r="1.7857" fill="#9095A8" />
|
|
18
|
+
<circle cx="10" cy="10" r="1.7857" fill="#9095A8" />
|
|
19
|
+
<circle cx="10" cy="15" r="1.7857" fill="#9095A8" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Dots.displayName = 'DotsIcon';
|