@koide-labs/ui 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/.husky/pre-commit +1 -0
- package/.storybook/main.ts +25 -0
- package/.storybook/preview-head.html +6 -0
- package/.storybook/preview.tsx +48 -0
- package/.storybook/vitest.setup.ts +8 -0
- package/README.md +11 -0
- package/eslint.config.mjs +29 -0
- package/lint-staged.config.js +15 -0
- package/package.json +95 -0
- package/pnpm-workspace.yaml +2 -0
- package/postcss.config.mjs +7 -0
- package/prettier.config.mjs +24 -0
- package/scripts/build-icon-types.ts +38 -0
- package/src/-types.ts +8 -0
- package/src/-utils.tsx +64 -0
- package/src/components/accordion/accordion.module.css +44 -0
- package/src/components/accordion/accordion.stories.tsx +36 -0
- package/src/components/accordion/index.tsx +67 -0
- package/src/components/alert-dialog/alert-dialog.module.css +5 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +53 -0
- package/src/components/alert-dialog/index.tsx +138 -0
- package/src/components/anchor/anchor.module.css +18 -0
- package/src/components/anchor/anchor.stories.tsx +28 -0
- package/src/components/anchor/index.tsx +45 -0
- package/src/components/avatar/avatar.module.css +56 -0
- package/src/components/avatar/avatar.stories.tsx +61 -0
- package/src/components/avatar/index.tsx +82 -0
- package/src/components/badge/badge.module.css +35 -0
- package/src/components/badge/badge.stories.tsx +60 -0
- package/src/components/badge/index.tsx +71 -0
- package/src/components/button/button.module.css +42 -0
- package/src/components/button/button.stories.tsx +108 -0
- package/src/components/button/index.tsx +63 -0
- package/src/components/checkbox/checkbox.module.css +36 -0
- package/src/components/checkbox/checkbox.stories.tsx +21 -0
- package/src/components/checkbox/index.tsx +41 -0
- package/src/components/code/code.module.css +20 -0
- package/src/components/code/code.stories.tsx +42 -0
- package/src/components/code/index.tsx +73 -0
- package/src/components/collapse/collapse.module.css +27 -0
- package/src/components/collapse/collapse.stories.tsx +27 -0
- package/src/components/collapse/index.tsx +59 -0
- package/src/components/command/command.module.css +95 -0
- package/src/components/command/command.stories.tsx +38 -0
- package/src/components/command/index.tsx +108 -0
- package/src/components/context-menu/context-menu.module.css +36 -0
- package/src/components/context-menu/context-menu.stories.tsx +99 -0
- package/src/components/context-menu/index.tsx +242 -0
- package/src/components/dialog/dialog.module.css +71 -0
- package/src/components/dialog/dialog.stories.tsx +29 -0
- package/src/components/dialog/index.tsx +148 -0
- package/src/components/heading/heading.module.css +3 -0
- package/src/components/heading/heading.stories.tsx +52 -0
- package/src/components/heading/index.tsx +112 -0
- package/src/components/icon/icon-names.ts +3189 -0
- package/src/components/icon/icon.module.css +36 -0
- package/src/components/icon/icon.stories.tsx +40 -0
- package/src/components/icon/index.tsx +60 -0
- package/src/components/icon-button/icon-button.module.css +33 -0
- package/src/components/icon-button/icon-button.stories.tsx +59 -0
- package/src/components/icon-button/index.tsx +48 -0
- package/src/components/inline-code/index.tsx +29 -0
- package/src/components/inline-code/inline-code.module.css +13 -0
- package/src/components/inline-code/inline-code.stories.tsx +31 -0
- package/src/components/input/index.tsx +22 -0
- package/src/components/input/input.module.css +23 -0
- package/src/components/input/input.stories.tsx +52 -0
- package/src/components/meter/index.tsx +55 -0
- package/src/components/meter/meter.module.css +23 -0
- package/src/components/meter/meter.stories.tsx +31 -0
- package/src/components/multiline-input/index.tsx +58 -0
- package/src/components/multiline-input/multiline-input.stories.tsx +26 -0
- package/src/components/number-input/index.tsx +74 -0
- package/src/components/number-input/number-input.module.css +41 -0
- package/src/components/number-input/number-input.stories.tsx +24 -0
- package/src/components/password-input/index.tsx +24 -0
- package/src/components/password-input/password-input.module.css +10 -0
- package/src/components/password-input/password-input.stories.tsx +24 -0
- package/src/components/pill/index.tsx +45 -0
- package/src/components/pill/pill.module.css +22 -0
- package/src/components/pill/pill.stories.tsx +83 -0
- package/src/components/popover/index.tsx +94 -0
- package/src/components/popover/popover.module.css +8 -0
- package/src/components/popover/popover.stories.tsx +53 -0
- package/src/components/preview-card/index.tsx +68 -0
- package/src/components/preview-card/preview-card.module.css +5 -0
- package/src/components/preview-card/preview-card.stories.tsx +58 -0
- package/src/components/radio/index.tsx +67 -0
- package/src/components/radio/radio-group.module.css +5 -0
- package/src/components/radio/radio.module.css +36 -0
- package/src/components/radio/radio.stories.tsx +27 -0
- package/src/components/search-bar/index.tsx +60 -0
- package/src/components/search-bar/search-bar.module.css +29 -0
- package/src/components/search-bar/search-bar.stories.tsx +37 -0
- package/src/components/select/index.tsx +132 -0
- package/src/components/select/select.module.css +63 -0
- package/src/components/select/select.stories.tsx +49 -0
- package/src/components/separator/index.tsx +28 -0
- package/src/components/separator/separator.module.css +24 -0
- package/src/components/separator/separator.stories.tsx +40 -0
- package/src/components/slider/index.tsx +28 -0
- package/src/components/slider/slider.module.css +52 -0
- package/src/components/slider/slider.stories.tsx +53 -0
- package/src/components/spinner/index.tsx +14 -0
- package/src/components/spinner/spinner.module.css +13 -0
- package/src/components/spinner/spinner.stories.tsx +17 -0
- package/src/components/stacked-avatars/index.tsx +88 -0
- package/src/components/stacked-avatars/stacked-avatars.module.css +79 -0
- package/src/components/stacked-avatars/stacked-avatars.stories.tsx +48 -0
- package/src/components/status-banner/index.tsx +96 -0
- package/src/components/status-banner/status-banner.module.css +52 -0
- package/src/components/status-banner/status-banner.stories.tsx +44 -0
- package/src/components/surface/index.tsx +83 -0
- package/src/components/surface/surface.module.css +35 -0
- package/src/components/surface/surface.stories.tsx +84 -0
- package/src/components/switch/index.tsx +23 -0
- package/src/components/switch/switch.module.css +45 -0
- package/src/components/switch/switch.stories.tsx +48 -0
- package/src/components/tabs/index.tsx +126 -0
- package/src/components/tabs/tabs.module.css +134 -0
- package/src/components/tabs/tabs.stories.tsx +88 -0
- package/src/components/text/index.tsx +69 -0
- package/src/components/text/text.module.css +76 -0
- package/src/components/text/text.stories.tsx +107 -0
- package/src/components/theme-provider/index.ts +2 -0
- package/src/components/theme-provider/theme-context.tsx +18 -0
- package/src/components/theme-provider/theme-provider.stories.tsx +47 -0
- package/src/components/theme-provider/theme-provider.tsx +77 -0
- package/src/components/timestamp/index.tsx +131 -0
- package/src/components/timestamp/timestamp.module.css +8 -0
- package/src/components/timestamp/timestamp.stories.tsx +37 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast.module.css +163 -0
- package/src/components/toast/toast.stories.tsx +53 -0
- package/src/components/toast/toast.tsx +104 -0
- package/src/components/toast/use-toast-manager.ts +63 -0
- package/src/components/tooltip/index.tsx +61 -0
- package/src/components/tooltip/tooltip-arrow.tsx +17 -0
- package/src/components/tooltip/tooltip.module.css +44 -0
- package/src/components/tooltip/tooltip.stories.tsx +76 -0
- package/src/components/view/index.tsx +137 -0
- package/src/components/view/view.module.css +11 -0
- package/src/components/view/view.stories.tsx +131 -0
- package/src/components/view/view_colorway.module.css +280 -0
- package/src/components/view/view_interactive.module.css +127 -0
- package/src/components/view/view_loading.module.css +58 -0
- package/src/components/visually-hidden/index.ts +1 -0
- package/src/index.ts +49 -0
- package/src/integrations/react-markdown/index.tsx +134 -0
- package/src/integrations/react-markdown/react-markdown.module.css +62 -0
- package/src/integrations/react-markdown/react-markdown.stories.tsx +31 -0
- package/src/integrations/remix.ts +12 -0
- package/src/integrations/tailwind.css +173 -0
- package/src/integrations/twemoij/index.tsx +13 -0
- package/src/integrations/twemoij/twemoji.module.css +7 -0
- package/src/integrations/twemoij/twemoji.stories.tsx +40 -0
- package/src/stories/components/all-variants.tsx +40 -0
- package/src/stories/data.ts +72 -0
- package/src/stories/utils.ts +20 -0
- package/src/styles/core.css +153 -0
- package/src/styles/themes/dark.css +86 -0
- package/src/styles/themes/light.css +86 -0
- package/src/styles/tokens.ts +282 -0
- package/src/styles/transitions.module.css +31 -0
- package/stylelint.config.mjs +29 -0
- package/tsconfig.app.json +35 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +103 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
3
|
+
import type { ComponentProps } from "react";
|
|
4
|
+
|
|
5
|
+
import { textify } from "~/-utils";
|
|
6
|
+
|
|
7
|
+
import { Dialog, type BaseDialogProps } from "../dialog";
|
|
8
|
+
import { Icon } from "../icon";
|
|
9
|
+
import type { IconName } from "../icon/icon-names";
|
|
10
|
+
import { Separator } from "../separator";
|
|
11
|
+
import { Text } from "../text";
|
|
12
|
+
import { View } from "../view";
|
|
13
|
+
|
|
14
|
+
import styles from "./command.module.css";
|
|
15
|
+
|
|
16
|
+
// TODO put background in dialog base props
|
|
17
|
+
export type CommandDialogProps = Omit<
|
|
18
|
+
BaseDialogProps,
|
|
19
|
+
"title" | "description" | "background"
|
|
20
|
+
> & {
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function CommandDialog({
|
|
25
|
+
placeholder,
|
|
26
|
+
children,
|
|
27
|
+
...props
|
|
28
|
+
}: CommandDialogProps) {
|
|
29
|
+
return (
|
|
30
|
+
<Dialog
|
|
31
|
+
width="sm"
|
|
32
|
+
background="default"
|
|
33
|
+
className={styles["command"]}
|
|
34
|
+
{...props}
|
|
35
|
+
closable
|
|
36
|
+
>
|
|
37
|
+
<CommandPrimitive>
|
|
38
|
+
<View className={styles["command__input-root"]}>
|
|
39
|
+
<View
|
|
40
|
+
interactive="fill-outline"
|
|
41
|
+
className={styles["command__input"]}
|
|
42
|
+
render={<CommandPrimitive.Input placeholder={placeholder} />}
|
|
43
|
+
/>
|
|
44
|
+
<Icon name="search-line" className={styles["command__input-icon"]} />
|
|
45
|
+
</View>
|
|
46
|
+
|
|
47
|
+
<CommandPrimitive.List className={styles["command__list"]}>
|
|
48
|
+
<CommandPrimitive.Empty>
|
|
49
|
+
<View className={styles["command__empty"]}>
|
|
50
|
+
<Text color="dimmer">No results found.</Text>
|
|
51
|
+
</View>
|
|
52
|
+
</CommandPrimitive.Empty>
|
|
53
|
+
|
|
54
|
+
{children}
|
|
55
|
+
</CommandPrimitive.List>
|
|
56
|
+
</CommandPrimitive>
|
|
57
|
+
</Dialog>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function CommandGroup({
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}: ComponentProps<typeof CommandPrimitive.Group>) {
|
|
65
|
+
return (
|
|
66
|
+
<CommandPrimitive.Group
|
|
67
|
+
className={clsx(styles["command__group"], className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function CommandSeparator(
|
|
74
|
+
props: ComponentProps<typeof CommandPrimitive.Separator>,
|
|
75
|
+
) {
|
|
76
|
+
return <Separator render={<CommandPrimitive.Separator {...props} />} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function CommandItem({
|
|
80
|
+
shortcut,
|
|
81
|
+
icon,
|
|
82
|
+
className,
|
|
83
|
+
children,
|
|
84
|
+
...props
|
|
85
|
+
}: ComponentProps<typeof CommandPrimitive.Item> & {
|
|
86
|
+
shortcut?: string;
|
|
87
|
+
icon?: IconName;
|
|
88
|
+
}) {
|
|
89
|
+
return (
|
|
90
|
+
<View
|
|
91
|
+
interactive="list-item"
|
|
92
|
+
className={clsx(styles["command__item"], className)}
|
|
93
|
+
render={<CommandPrimitive.Item {...props} />}
|
|
94
|
+
>
|
|
95
|
+
{icon ? <Icon name={icon} /> : undefined}
|
|
96
|
+
{textify(children)}
|
|
97
|
+
{shortcut ? (
|
|
98
|
+
<Text
|
|
99
|
+
className={clsx(styles["command__shortcut"])}
|
|
100
|
+
color="dimmer"
|
|
101
|
+
size="sm"
|
|
102
|
+
>
|
|
103
|
+
{shortcut}
|
|
104
|
+
</Text>
|
|
105
|
+
) : null}
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.context-menu {
|
|
2
|
+
gap: var(--space-4);
|
|
3
|
+
box-shadow: var(--shadow-1);
|
|
4
|
+
border: 1px solid var(--outline-dimmest);
|
|
5
|
+
border-radius: var(--border-radius-default);
|
|
6
|
+
padding: var(--space-4);
|
|
7
|
+
user-select: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.context-menu__item {
|
|
11
|
+
position: relative;
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--space-4);
|
|
15
|
+
outline: none;
|
|
16
|
+
border: none;
|
|
17
|
+
border-radius: var(--border-radius-6);
|
|
18
|
+
padding: var(--space-2) var(--space-4);
|
|
19
|
+
padding-left: var(--space-24);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.context-menu__item-icon {
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 50%;
|
|
25
|
+
left: var(--space-4);
|
|
26
|
+
transform: translateY(-50%);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.context-menu__item-shortcut {
|
|
30
|
+
margin-left: auto;
|
|
31
|
+
letter-spacing: 0.1em;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.context-menu__label {
|
|
35
|
+
padding: var(--space-2) var(--space-4);
|
|
36
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ContextMenu,
|
|
7
|
+
ContextMenuCheckboxItem,
|
|
8
|
+
ContextMenuGroup,
|
|
9
|
+
ContextMenuItem,
|
|
10
|
+
ContextMenuMore,
|
|
11
|
+
ContextMenuRadioGroup,
|
|
12
|
+
ContextMenuRadioItem,
|
|
13
|
+
ContextMenuSeparator,
|
|
14
|
+
} from ".";
|
|
15
|
+
import { Text } from "../text";
|
|
16
|
+
import { View } from "../view";
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: "ContextMenu",
|
|
20
|
+
component: ContextMenu,
|
|
21
|
+
parameters: { layout: "centered" },
|
|
22
|
+
} satisfies Meta<typeof ContextMenu>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<>
|
|
31
|
+
<ContextMenu
|
|
32
|
+
style={{ width: "200px" }}
|
|
33
|
+
trigger={
|
|
34
|
+
<View
|
|
35
|
+
style={{
|
|
36
|
+
height: tokens.space128,
|
|
37
|
+
width: tokens.space256,
|
|
38
|
+
border: `1px dashed ${tokens.primaryDefault}`,
|
|
39
|
+
backgroundColor: tokens.primaryDimmest,
|
|
40
|
+
borderRadius: tokens.borderRadiusDefault,
|
|
41
|
+
color: tokens.primaryDefault,
|
|
42
|
+
alignItems: "center",
|
|
43
|
+
justifyContent: "center",
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<Text color="inherit">Right click here</Text>
|
|
47
|
+
</View>
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
<ContextMenuGroup>
|
|
51
|
+
<ContextMenuItem icon="arrow-left-line" disabled shortcut="⌘+[">
|
|
52
|
+
Back
|
|
53
|
+
</ContextMenuItem>
|
|
54
|
+
<ContextMenuItem icon="arrow-right-line" shortcut="⌘+]">
|
|
55
|
+
Forward
|
|
56
|
+
</ContextMenuItem>
|
|
57
|
+
<ContextMenuItem icon="refresh-line">Reload</ContextMenuItem>
|
|
58
|
+
<ContextMenuMore icon="tools-line" label="More Tools">
|
|
59
|
+
<ContextMenuGroup>
|
|
60
|
+
<ContextMenuItem icon="download-line">
|
|
61
|
+
Save Page As
|
|
62
|
+
</ContextMenuItem>
|
|
63
|
+
<ContextMenuItem icon="links-line">
|
|
64
|
+
Create Shortcut
|
|
65
|
+
</ContextMenuItem>
|
|
66
|
+
<ContextMenuItem icon="window-2-line">
|
|
67
|
+
Name Window
|
|
68
|
+
</ContextMenuItem>
|
|
69
|
+
</ContextMenuGroup>
|
|
70
|
+
<ContextMenuSeparator />
|
|
71
|
+
<ContextMenuItem icon="terminal-box-line">
|
|
72
|
+
Developer Tools
|
|
73
|
+
</ContextMenuItem>
|
|
74
|
+
</ContextMenuMore>
|
|
75
|
+
</ContextMenuGroup>
|
|
76
|
+
|
|
77
|
+
<ContextMenuSeparator />
|
|
78
|
+
|
|
79
|
+
<ContextMenuGroup>
|
|
80
|
+
<ContextMenuCheckboxItem value="bookmarks">
|
|
81
|
+
Show Bookmarks
|
|
82
|
+
</ContextMenuCheckboxItem>
|
|
83
|
+
<ContextMenuCheckboxItem value="show_full_urls">
|
|
84
|
+
Show Full URLs
|
|
85
|
+
</ContextMenuCheckboxItem>
|
|
86
|
+
</ContextMenuGroup>
|
|
87
|
+
|
|
88
|
+
<ContextMenuSeparator />
|
|
89
|
+
|
|
90
|
+
<ContextMenuGroup label="People">
|
|
91
|
+
<ContextMenuRadioGroup defaultValue="a">
|
|
92
|
+
<ContextMenuRadioItem value="a">Pedro Duarte</ContextMenuRadioItem>
|
|
93
|
+
<ContextMenuRadioItem value="b">Colm Tuite</ContextMenuRadioItem>
|
|
94
|
+
</ContextMenuRadioGroup>
|
|
95
|
+
</ContextMenuGroup>
|
|
96
|
+
</ContextMenu>
|
|
97
|
+
</>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { ContextMenu as ContextMenuPrimitive } from "@base-ui/react/context-menu";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
type CSSProperties,
|
|
7
|
+
type ReactElement,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
import { textify } from "~/-utils";
|
|
12
|
+
import { tokens } from "~/styles/tokens";
|
|
13
|
+
|
|
14
|
+
import { Icon } from "../icon";
|
|
15
|
+
import type { IconName } from "../icon/icon-names";
|
|
16
|
+
import { Separator, type SeparatorProps } from "../separator";
|
|
17
|
+
import { Surface, type Background } from "../surface";
|
|
18
|
+
import { Text } from "../text";
|
|
19
|
+
import { View } from "../view";
|
|
20
|
+
|
|
21
|
+
import transitionStyles from "../../styles/transitions.module.css";
|
|
22
|
+
import styles from "./context-menu.module.css";
|
|
23
|
+
|
|
24
|
+
export type ContextMenuProps = ContextMenuPrimitive.Root.Props & {
|
|
25
|
+
/** Specify trigger to open context menu. */
|
|
26
|
+
trigger?: ReactElement;
|
|
27
|
+
|
|
28
|
+
/** Surface background popup should use. Defaults to "root". Propogates to submenus. */
|
|
29
|
+
background?: Background;
|
|
30
|
+
|
|
31
|
+
/** Context menu content */
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
|
|
34
|
+
/** Apply className to ContextMenuPrimitive.Popup */
|
|
35
|
+
className?: string;
|
|
36
|
+
|
|
37
|
+
/** Apply styles to ContextMenuPrimitive.Popup */
|
|
38
|
+
style?: CSSProperties;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ContextMenuContext = createContext<Background>("root");
|
|
42
|
+
|
|
43
|
+
export function ContextMenu({
|
|
44
|
+
trigger,
|
|
45
|
+
background = "root",
|
|
46
|
+
className,
|
|
47
|
+
children,
|
|
48
|
+
style,
|
|
49
|
+
...props
|
|
50
|
+
}: ContextMenuProps) {
|
|
51
|
+
return (
|
|
52
|
+
<ContextMenuContext.Provider value={background}>
|
|
53
|
+
<ContextMenuPrimitive.Root {...props}>
|
|
54
|
+
{trigger ? <ContextMenuPrimitive.Trigger render={trigger} /> : null}
|
|
55
|
+
<ContextMenuPrimitive.Portal>
|
|
56
|
+
<ContextMenuPrimitive.Positioner>
|
|
57
|
+
<ContextMenuPrimitive.Popup
|
|
58
|
+
render={<Surface background={background} />}
|
|
59
|
+
className={clsx(
|
|
60
|
+
styles["context-menu"],
|
|
61
|
+
transitionStyles["transition_fade-out"],
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
style={style}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</ContextMenuPrimitive.Popup>
|
|
68
|
+
</ContextMenuPrimitive.Positioner>
|
|
69
|
+
</ContextMenuPrimitive.Portal>
|
|
70
|
+
</ContextMenuPrimitive.Root>
|
|
71
|
+
</ContextMenuContext.Provider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ContextMenuItem({
|
|
76
|
+
icon,
|
|
77
|
+
shortcut,
|
|
78
|
+
className,
|
|
79
|
+
children,
|
|
80
|
+
...props
|
|
81
|
+
}: ContextMenuPrimitive.Item.Props & {
|
|
82
|
+
/** Optional item icon to communicate purpose */
|
|
83
|
+
icon?: IconName;
|
|
84
|
+
|
|
85
|
+
/** Indicate that this action can be triggered by a keyboard shortcut */
|
|
86
|
+
shortcut?: string;
|
|
87
|
+
}) {
|
|
88
|
+
return (
|
|
89
|
+
<View
|
|
90
|
+
render={<ContextMenuPrimitive.Item {...props} />}
|
|
91
|
+
interactive="list-item"
|
|
92
|
+
className={clsx(styles["context-menu__item"], className)}
|
|
93
|
+
>
|
|
94
|
+
{icon ? (
|
|
95
|
+
<Icon name={icon} className={styles["context-menu__item-icon"]} />
|
|
96
|
+
) : null}
|
|
97
|
+
{textify(children)}
|
|
98
|
+
{shortcut ? (
|
|
99
|
+
<Text
|
|
100
|
+
className={styles["context-menu__item-shortcut"]}
|
|
101
|
+
color="dimmest"
|
|
102
|
+
size="sm"
|
|
103
|
+
>
|
|
104
|
+
{shortcut}
|
|
105
|
+
</Text>
|
|
106
|
+
) : null}
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function ContextMenuSeparator(
|
|
112
|
+
props: ContextMenuPrimitive.Separator.Props & SeparatorProps,
|
|
113
|
+
) {
|
|
114
|
+
return <Separator render={<ContextMenuPrimitive.Separator />} {...props} />;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function ContextMenuGroup({
|
|
118
|
+
label,
|
|
119
|
+
children,
|
|
120
|
+
...props
|
|
121
|
+
}: ContextMenuPrimitive.Group.Props & {
|
|
122
|
+
label?: string;
|
|
123
|
+
}) {
|
|
124
|
+
return (
|
|
125
|
+
<ContextMenuPrimitive.Group {...props}>
|
|
126
|
+
{label ? (
|
|
127
|
+
<Text
|
|
128
|
+
render={<ContextMenuPrimitive.GroupLabel />}
|
|
129
|
+
className={styles["context-menu__label"]}
|
|
130
|
+
color="dimmest"
|
|
131
|
+
size="sm"
|
|
132
|
+
>
|
|
133
|
+
{label}
|
|
134
|
+
</Text>
|
|
135
|
+
) : null}
|
|
136
|
+
{children}
|
|
137
|
+
</ContextMenuPrimitive.Group>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
142
|
+
|
|
143
|
+
export function ContextMenuRadioItem({
|
|
144
|
+
className,
|
|
145
|
+
children,
|
|
146
|
+
...props
|
|
147
|
+
}: ContextMenuPrimitive.RadioItem.Props) {
|
|
148
|
+
return (
|
|
149
|
+
<View
|
|
150
|
+
interactive="list-item"
|
|
151
|
+
render={<ContextMenuPrimitive.RadioItem {...props} />}
|
|
152
|
+
className={clsx(styles["context-menu__item"], className)}
|
|
153
|
+
>
|
|
154
|
+
<ContextMenuPrimitive.RadioItemIndicator
|
|
155
|
+
className={styles["context-menu__item-icon"]}
|
|
156
|
+
>
|
|
157
|
+
<Icon name="checkbox-circle-line" color={tokens.primaryDefault} />
|
|
158
|
+
</ContextMenuPrimitive.RadioItemIndicator>
|
|
159
|
+
{textify(children)}
|
|
160
|
+
</View>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function ContextMenuCheckboxItem({
|
|
165
|
+
className,
|
|
166
|
+
children,
|
|
167
|
+
...props
|
|
168
|
+
}: ContextMenuPrimitive.RadioItem.Props) {
|
|
169
|
+
return (
|
|
170
|
+
<View
|
|
171
|
+
interactive="list-item"
|
|
172
|
+
render={<ContextMenuPrimitive.CheckboxItem {...props} />}
|
|
173
|
+
className={clsx(styles["context-menu__item"], className)}
|
|
174
|
+
>
|
|
175
|
+
<ContextMenuPrimitive.CheckboxItemIndicator
|
|
176
|
+
className={styles["context-menu__item-icon"]}
|
|
177
|
+
>
|
|
178
|
+
<Icon name="check-line" color={tokens.primaryDefault} />
|
|
179
|
+
</ContextMenuPrimitive.CheckboxItemIndicator>
|
|
180
|
+
{textify(children)}
|
|
181
|
+
</View>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function ContextMenuMore({
|
|
186
|
+
label,
|
|
187
|
+
icon,
|
|
188
|
+
children,
|
|
189
|
+
className,
|
|
190
|
+
style,
|
|
191
|
+
...props
|
|
192
|
+
}: ContextMenuPrimitive.SubmenuRoot.Props & {
|
|
193
|
+
label: string;
|
|
194
|
+
|
|
195
|
+
/** Optional item icon to communicate purpose */
|
|
196
|
+
icon?: IconName;
|
|
197
|
+
|
|
198
|
+
/** Context menu content */
|
|
199
|
+
children?: ReactNode;
|
|
200
|
+
|
|
201
|
+
/** Apply className to ContextMenuPrimitive.Popup */
|
|
202
|
+
className?: string;
|
|
203
|
+
|
|
204
|
+
/** Apply styles to ContextMenuPrimitive.Popup */
|
|
205
|
+
style?: CSSProperties;
|
|
206
|
+
}) {
|
|
207
|
+
const background = useContext(ContextMenuContext);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<ContextMenuPrimitive.SubmenuRoot {...props}>
|
|
211
|
+
<View
|
|
212
|
+
render={<ContextMenuPrimitive.SubmenuTrigger />}
|
|
213
|
+
className={styles["context-menu__item"]}
|
|
214
|
+
>
|
|
215
|
+
{icon ? (
|
|
216
|
+
<Icon name={icon} className={styles["context-menu__item-icon"]} />
|
|
217
|
+
) : null}
|
|
218
|
+
{textify(label)}
|
|
219
|
+
<Icon
|
|
220
|
+
name="arrow-right-line"
|
|
221
|
+
className={styles["context-menu__item-shortcut"]}
|
|
222
|
+
/>
|
|
223
|
+
</View>
|
|
224
|
+
|
|
225
|
+
<ContextMenuPrimitive.Portal>
|
|
226
|
+
<ContextMenuPrimitive.Positioner alignOffset={-4} sideOffset={-4}>
|
|
227
|
+
<ContextMenuPrimitive.Popup
|
|
228
|
+
render={<Surface background={background} />}
|
|
229
|
+
className={clsx(
|
|
230
|
+
styles["context-menu"],
|
|
231
|
+
transitionStyles["transition_scale"],
|
|
232
|
+
className,
|
|
233
|
+
)}
|
|
234
|
+
style={style}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</ContextMenuPrimitive.Popup>
|
|
238
|
+
</ContextMenuPrimitive.Positioner>
|
|
239
|
+
</ContextMenuPrimitive.Portal>
|
|
240
|
+
</ContextMenuPrimitive.SubmenuRoot>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
.dialog__backdrop {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 50;
|
|
4
|
+
inset: 0;
|
|
5
|
+
background-color: var(--background-overlay);
|
|
6
|
+
|
|
7
|
+
/* iOS 26+: Ensure the backdrop covers the entire visible viewport. */
|
|
8
|
+
@supports (-webkit-touch-callout: none) {
|
|
9
|
+
position: absolute;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dialog__popup {
|
|
14
|
+
display: block;
|
|
15
|
+
position: fixed;
|
|
16
|
+
z-index: 51;
|
|
17
|
+
inset: 0;
|
|
18
|
+
isolation: isolate;
|
|
19
|
+
padding: var(--space-16);
|
|
20
|
+
height: 100%;
|
|
21
|
+
overflow: auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dialog__popup_centered {
|
|
25
|
+
display: flex;
|
|
26
|
+
place-items: center;
|
|
27
|
+
|
|
28
|
+
@media screen and (width >= 512px) {
|
|
29
|
+
padding: var(--space-32);
|
|
30
|
+
padding-top: var(--space-64);
|
|
31
|
+
padding-bottom: var(--space-64);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dialog__content {
|
|
36
|
+
position: relative;
|
|
37
|
+
flex-grow: 1;
|
|
38
|
+
flex-shrink: 1;
|
|
39
|
+
gap: var(--space-16);
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
outline: none;
|
|
42
|
+
box-shadow: var(--shadow-2);
|
|
43
|
+
border: 1px solid var(--outline-dimmest);
|
|
44
|
+
border-radius: var(--border-radius-default);
|
|
45
|
+
padding: var(--space-16);
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: auto;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.dialog__content_width_sm {
|
|
52
|
+
max-width: 384px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dialog__content_width_md {
|
|
56
|
+
max-width: 512px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.dialog__content_width_lg {
|
|
60
|
+
max-width: 872px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.dialog__header {
|
|
64
|
+
gap: var(--space-4);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.dialog__close {
|
|
68
|
+
position: absolute;
|
|
69
|
+
top: var(--space-8);
|
|
70
|
+
right: var(--space-8);
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { Dialog } from ".";
|
|
4
|
+
import { Button } from "../button";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Dialog/Standard Dialog",
|
|
8
|
+
component: Dialog,
|
|
9
|
+
parameters: { layout: "centered" },
|
|
10
|
+
} satisfies Meta<typeof Dialog>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
args: {
|
|
18
|
+
title: "On color",
|
|
19
|
+
description:
|
|
20
|
+
"Lots of different, solid, highly saturated colors all over the UI. This makes it hard to tell what's important, and means users will be easily distracted.",
|
|
21
|
+
},
|
|
22
|
+
render: (args) => (
|
|
23
|
+
<Dialog
|
|
24
|
+
closable
|
|
25
|
+
trigger={<Button leftIcon="palette-line" children="Open Dialog" />}
|
|
26
|
+
{...args}
|
|
27
|
+
/>
|
|
28
|
+
),
|
|
29
|
+
};
|