@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.
Files changed (170) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/.storybook/main.ts +25 -0
  3. package/.storybook/preview-head.html +6 -0
  4. package/.storybook/preview.tsx +48 -0
  5. package/.storybook/vitest.setup.ts +8 -0
  6. package/README.md +11 -0
  7. package/eslint.config.mjs +29 -0
  8. package/lint-staged.config.js +15 -0
  9. package/package.json +95 -0
  10. package/pnpm-workspace.yaml +2 -0
  11. package/postcss.config.mjs +7 -0
  12. package/prettier.config.mjs +24 -0
  13. package/scripts/build-icon-types.ts +38 -0
  14. package/src/-types.ts +8 -0
  15. package/src/-utils.tsx +64 -0
  16. package/src/components/accordion/accordion.module.css +44 -0
  17. package/src/components/accordion/accordion.stories.tsx +36 -0
  18. package/src/components/accordion/index.tsx +67 -0
  19. package/src/components/alert-dialog/alert-dialog.module.css +5 -0
  20. package/src/components/alert-dialog/alert-dialog.stories.tsx +53 -0
  21. package/src/components/alert-dialog/index.tsx +138 -0
  22. package/src/components/anchor/anchor.module.css +18 -0
  23. package/src/components/anchor/anchor.stories.tsx +28 -0
  24. package/src/components/anchor/index.tsx +45 -0
  25. package/src/components/avatar/avatar.module.css +56 -0
  26. package/src/components/avatar/avatar.stories.tsx +61 -0
  27. package/src/components/avatar/index.tsx +82 -0
  28. package/src/components/badge/badge.module.css +35 -0
  29. package/src/components/badge/badge.stories.tsx +60 -0
  30. package/src/components/badge/index.tsx +71 -0
  31. package/src/components/button/button.module.css +42 -0
  32. package/src/components/button/button.stories.tsx +108 -0
  33. package/src/components/button/index.tsx +63 -0
  34. package/src/components/checkbox/checkbox.module.css +36 -0
  35. package/src/components/checkbox/checkbox.stories.tsx +21 -0
  36. package/src/components/checkbox/index.tsx +41 -0
  37. package/src/components/code/code.module.css +20 -0
  38. package/src/components/code/code.stories.tsx +42 -0
  39. package/src/components/code/index.tsx +73 -0
  40. package/src/components/collapse/collapse.module.css +27 -0
  41. package/src/components/collapse/collapse.stories.tsx +27 -0
  42. package/src/components/collapse/index.tsx +59 -0
  43. package/src/components/command/command.module.css +95 -0
  44. package/src/components/command/command.stories.tsx +38 -0
  45. package/src/components/command/index.tsx +108 -0
  46. package/src/components/context-menu/context-menu.module.css +36 -0
  47. package/src/components/context-menu/context-menu.stories.tsx +99 -0
  48. package/src/components/context-menu/index.tsx +242 -0
  49. package/src/components/dialog/dialog.module.css +71 -0
  50. package/src/components/dialog/dialog.stories.tsx +29 -0
  51. package/src/components/dialog/index.tsx +148 -0
  52. package/src/components/heading/heading.module.css +3 -0
  53. package/src/components/heading/heading.stories.tsx +52 -0
  54. package/src/components/heading/index.tsx +112 -0
  55. package/src/components/icon/icon-names.ts +3189 -0
  56. package/src/components/icon/icon.module.css +36 -0
  57. package/src/components/icon/icon.stories.tsx +40 -0
  58. package/src/components/icon/index.tsx +60 -0
  59. package/src/components/icon-button/icon-button.module.css +33 -0
  60. package/src/components/icon-button/icon-button.stories.tsx +59 -0
  61. package/src/components/icon-button/index.tsx +48 -0
  62. package/src/components/inline-code/index.tsx +29 -0
  63. package/src/components/inline-code/inline-code.module.css +13 -0
  64. package/src/components/inline-code/inline-code.stories.tsx +31 -0
  65. package/src/components/input/index.tsx +22 -0
  66. package/src/components/input/input.module.css +23 -0
  67. package/src/components/input/input.stories.tsx +52 -0
  68. package/src/components/meter/index.tsx +55 -0
  69. package/src/components/meter/meter.module.css +23 -0
  70. package/src/components/meter/meter.stories.tsx +31 -0
  71. package/src/components/multiline-input/index.tsx +58 -0
  72. package/src/components/multiline-input/multiline-input.stories.tsx +26 -0
  73. package/src/components/number-input/index.tsx +74 -0
  74. package/src/components/number-input/number-input.module.css +41 -0
  75. package/src/components/number-input/number-input.stories.tsx +24 -0
  76. package/src/components/password-input/index.tsx +24 -0
  77. package/src/components/password-input/password-input.module.css +10 -0
  78. package/src/components/password-input/password-input.stories.tsx +24 -0
  79. package/src/components/pill/index.tsx +45 -0
  80. package/src/components/pill/pill.module.css +22 -0
  81. package/src/components/pill/pill.stories.tsx +83 -0
  82. package/src/components/popover/index.tsx +94 -0
  83. package/src/components/popover/popover.module.css +8 -0
  84. package/src/components/popover/popover.stories.tsx +53 -0
  85. package/src/components/preview-card/index.tsx +68 -0
  86. package/src/components/preview-card/preview-card.module.css +5 -0
  87. package/src/components/preview-card/preview-card.stories.tsx +58 -0
  88. package/src/components/radio/index.tsx +67 -0
  89. package/src/components/radio/radio-group.module.css +5 -0
  90. package/src/components/radio/radio.module.css +36 -0
  91. package/src/components/radio/radio.stories.tsx +27 -0
  92. package/src/components/search-bar/index.tsx +60 -0
  93. package/src/components/search-bar/search-bar.module.css +29 -0
  94. package/src/components/search-bar/search-bar.stories.tsx +37 -0
  95. package/src/components/select/index.tsx +132 -0
  96. package/src/components/select/select.module.css +63 -0
  97. package/src/components/select/select.stories.tsx +49 -0
  98. package/src/components/separator/index.tsx +28 -0
  99. package/src/components/separator/separator.module.css +24 -0
  100. package/src/components/separator/separator.stories.tsx +40 -0
  101. package/src/components/slider/index.tsx +28 -0
  102. package/src/components/slider/slider.module.css +52 -0
  103. package/src/components/slider/slider.stories.tsx +53 -0
  104. package/src/components/spinner/index.tsx +14 -0
  105. package/src/components/spinner/spinner.module.css +13 -0
  106. package/src/components/spinner/spinner.stories.tsx +17 -0
  107. package/src/components/stacked-avatars/index.tsx +88 -0
  108. package/src/components/stacked-avatars/stacked-avatars.module.css +79 -0
  109. package/src/components/stacked-avatars/stacked-avatars.stories.tsx +48 -0
  110. package/src/components/status-banner/index.tsx +96 -0
  111. package/src/components/status-banner/status-banner.module.css +52 -0
  112. package/src/components/status-banner/status-banner.stories.tsx +44 -0
  113. package/src/components/surface/index.tsx +83 -0
  114. package/src/components/surface/surface.module.css +35 -0
  115. package/src/components/surface/surface.stories.tsx +84 -0
  116. package/src/components/switch/index.tsx +23 -0
  117. package/src/components/switch/switch.module.css +45 -0
  118. package/src/components/switch/switch.stories.tsx +48 -0
  119. package/src/components/tabs/index.tsx +126 -0
  120. package/src/components/tabs/tabs.module.css +134 -0
  121. package/src/components/tabs/tabs.stories.tsx +88 -0
  122. package/src/components/text/index.tsx +69 -0
  123. package/src/components/text/text.module.css +76 -0
  124. package/src/components/text/text.stories.tsx +107 -0
  125. package/src/components/theme-provider/index.ts +2 -0
  126. package/src/components/theme-provider/theme-context.tsx +18 -0
  127. package/src/components/theme-provider/theme-provider.stories.tsx +47 -0
  128. package/src/components/theme-provider/theme-provider.tsx +77 -0
  129. package/src/components/timestamp/index.tsx +131 -0
  130. package/src/components/timestamp/timestamp.module.css +8 -0
  131. package/src/components/timestamp/timestamp.stories.tsx +37 -0
  132. package/src/components/toast/index.ts +2 -0
  133. package/src/components/toast/toast.module.css +163 -0
  134. package/src/components/toast/toast.stories.tsx +53 -0
  135. package/src/components/toast/toast.tsx +104 -0
  136. package/src/components/toast/use-toast-manager.ts +63 -0
  137. package/src/components/tooltip/index.tsx +61 -0
  138. package/src/components/tooltip/tooltip-arrow.tsx +17 -0
  139. package/src/components/tooltip/tooltip.module.css +44 -0
  140. package/src/components/tooltip/tooltip.stories.tsx +76 -0
  141. package/src/components/view/index.tsx +137 -0
  142. package/src/components/view/view.module.css +11 -0
  143. package/src/components/view/view.stories.tsx +131 -0
  144. package/src/components/view/view_colorway.module.css +280 -0
  145. package/src/components/view/view_interactive.module.css +127 -0
  146. package/src/components/view/view_loading.module.css +58 -0
  147. package/src/components/visually-hidden/index.ts +1 -0
  148. package/src/index.ts +49 -0
  149. package/src/integrations/react-markdown/index.tsx +134 -0
  150. package/src/integrations/react-markdown/react-markdown.module.css +62 -0
  151. package/src/integrations/react-markdown/react-markdown.stories.tsx +31 -0
  152. package/src/integrations/remix.ts +12 -0
  153. package/src/integrations/tailwind.css +173 -0
  154. package/src/integrations/twemoij/index.tsx +13 -0
  155. package/src/integrations/twemoij/twemoji.module.css +7 -0
  156. package/src/integrations/twemoij/twemoji.stories.tsx +40 -0
  157. package/src/stories/components/all-variants.tsx +40 -0
  158. package/src/stories/data.ts +72 -0
  159. package/src/stories/utils.ts +20 -0
  160. package/src/styles/core.css +153 -0
  161. package/src/styles/themes/dark.css +86 -0
  162. package/src/styles/themes/light.css +86 -0
  163. package/src/styles/tokens.ts +282 -0
  164. package/src/styles/transitions.module.css +31 -0
  165. package/stylelint.config.mjs +29 -0
  166. package/tsconfig.app.json +35 -0
  167. package/tsconfig.json +7 -0
  168. package/tsconfig.node.json +26 -0
  169. package/vite.config.ts +103 -0
  170. package/vitest.shims.d.ts +1 -0
@@ -0,0 +1,108 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { AllVariants } from "~/stories/components/all-variants";
4
+ import { colorVariants, sizes } from "~/stories/data";
5
+ import { tokens } from "~/styles/tokens";
6
+
7
+ import { Button } from ".";
8
+ import { Surface } from "../surface";
9
+ import { Text } from "../text";
10
+ import { View } from "../view";
11
+
12
+ const meta = {
13
+ title: "Button",
14
+ component: Button,
15
+ parameters: { layout: "centered" },
16
+ argTypes: {
17
+ size: {
18
+ control: "select",
19
+ options: sizes,
20
+ },
21
+ loading: {
22
+ control: "boolean",
23
+ },
24
+ },
25
+ } satisfies Meta<typeof Button>;
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ interactive: "primary_fill",
34
+ leftIcon: "square-line",
35
+ },
36
+ render: (props) => <Button {...props}>Hello World</Button>,
37
+ };
38
+
39
+ export const Link: Story = {
40
+ args: {
41
+ size: "md",
42
+ leftIcon: "external-link-line",
43
+ interactive: "primary_fill",
44
+ loading: false,
45
+ },
46
+ render: (args) => (
47
+ <Button
48
+ {...args}
49
+ render={<a href="https://natmfat.com" target="_blank" rel="noreferrer" />}
50
+ nativeButton={false}
51
+ >
52
+ Open Website
53
+ </Button>
54
+ ),
55
+ };
56
+
57
+ export const AllSizes: Story = {
58
+ args: {
59
+ interactive: "fill",
60
+ leftIcon: "square-line",
61
+ },
62
+ render: (args) => (
63
+ <AllVariants
64
+ variantName="size"
65
+ variants={sizes}
66
+ element={
67
+ <Button style={{ width: "fit-content" }} {...args}>
68
+ Hello World
69
+ </Button>
70
+ }
71
+ />
72
+ ),
73
+ };
74
+
75
+ <Surface elevated style={{ gap: tokens.space16, padding: tokens.space16 }}>
76
+ {colorVariants.map((variant) => (
77
+ <View key={variant} style={{ gap: tokens.space2 }}>
78
+ <Text color="dimmest">{variant}</Text>
79
+ </View>
80
+ ))}
81
+ </Surface>;
82
+
83
+ export const AllPrimaryVariants: Story = {
84
+ argTypes: {
85
+ interactive: {
86
+ table: {
87
+ disable: true,
88
+ },
89
+ },
90
+ },
91
+ args: {
92
+ size: "md",
93
+ leftIcon: "square-line",
94
+ loading: false,
95
+ disabled: false,
96
+ },
97
+ render: (args) => (
98
+ <AllVariants
99
+ variantName="interactive"
100
+ variants={colorVariants.map((variant) => `primary_${variant}`)}
101
+ element={
102
+ <Button style={{ width: "fit-content" }} {...args}>
103
+ Hello World
104
+ </Button>
105
+ }
106
+ />
107
+ ),
108
+ };
@@ -0,0 +1,63 @@
1
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
+ import clsx from "clsx";
3
+ import type { ComponentProps } from "react";
4
+
5
+ import { textify } from "~/-utils";
6
+ import type { Size } from "~/styles/tokens";
7
+
8
+ import { Icon } from "../icon";
9
+ import type { IconName } from "../icon/icon-names";
10
+ import { View, type ViewProps } from "../view";
11
+
12
+ import styles from "./button.module.css";
13
+
14
+ export type ButtonProps = ComponentProps<"button"> &
15
+ ViewProps<"button"> & {
16
+ /**
17
+ * Size of the button
18
+ */
19
+ size?: Size;
20
+
21
+ /**
22
+ * Icon to display on the left side of the button content
23
+ */
24
+ leftIcon?: IconName;
25
+
26
+ /**
27
+ * Icon to display on the right side of the button content
28
+ */
29
+ rightIcon?: IconName;
30
+
31
+ /**
32
+ * The button can remain keyboard accessible while being rendered as another tag
33
+ * {@link https://base-ui.com/react/components/button}
34
+ */
35
+ nativeButton?: boolean;
36
+ };
37
+
38
+ export function Button({
39
+ size = "md",
40
+ leftIcon,
41
+ rightIcon,
42
+ children,
43
+ render,
44
+ ...props
45
+ }: ButtonProps) {
46
+ return (
47
+ <View<"button">
48
+ interactive={true}
49
+ render={
50
+ <ButtonPrimitive
51
+ className={clsx(styles["button"], styles[`button_height_${size}`])}
52
+ focusableWhenDisabled={!!props.loading}
53
+ render={render}
54
+ />
55
+ }
56
+ {...props}
57
+ >
58
+ {leftIcon ? <Icon name={leftIcon} size={size} /> : null}
59
+ {textify(children, { size, color: "inherit" })}
60
+ {rightIcon ? <Icon name={rightIcon} size={size} /> : null}
61
+ </View>
62
+ );
63
+ }
@@ -0,0 +1,36 @@
1
+ .checkbox__label {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: center;
5
+ gap: var(--space-8);
6
+ }
7
+
8
+ .checkbox {
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ border-radius: var(--border-radius-4);
13
+ width: 20px;
14
+ height: 20px;
15
+
16
+ &[data-checked] {
17
+ background: var(--view-colorway-dimmer);
18
+ }
19
+
20
+ &[data-checked]:not([disabled], [data-disabled]) {
21
+ background: var(--view-colorway-default);
22
+
23
+ &:hover {
24
+ border-color: var(--view-colorway-stronger);
25
+ }
26
+ }
27
+ }
28
+
29
+ .checkbox__indicator {
30
+ pointer-events: none;
31
+ color: var(--white);
32
+
33
+ &[data-unchecked] {
34
+ display: none;
35
+ }
36
+ }
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Checkbox } from ".";
4
+
5
+ const meta = {
6
+ title: "Input/Checkbox",
7
+ component: Checkbox,
8
+ parameters: { layout: "centered" },
9
+ } satisfies Meta<typeof Checkbox>;
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ args: {
17
+ disabled: false,
18
+ label: "Enable notifications",
19
+ },
20
+ render: (args) => <Checkbox {...args} />,
21
+ };
@@ -0,0 +1,41 @@
1
+ import { Checkbox as CheckboxPrimitive } from "@base-ui/react/checkbox";
2
+
3
+ import { Icon } from "../icon";
4
+ import { Text } from "../text";
5
+ import { View, type Color } from "../view";
6
+
7
+ import styles from "./checkbox.module.css";
8
+
9
+ export interface CheckboxProps extends CheckboxPrimitive.Root.Props {
10
+ /** Optional color for checkbox. Defaults to "primary". */
11
+ color?: Color;
12
+
13
+ /** Optional label. If one is provided, we wrap radio in a label element. Otherwise, we just return a standard radio. */
14
+ label?: string;
15
+ }
16
+
17
+ export function Checkbox({ color, label, ...props }: CheckboxProps) {
18
+ const checkbox = (
19
+ <View
20
+ color={color}
21
+ interactive="fill-outline"
22
+ render={<CheckboxPrimitive.Root aria-label={label} {...props} />}
23
+ className={styles["checkbox"]}
24
+ >
25
+ <CheckboxPrimitive.Indicator className={styles["checkbox__indicator"]}>
26
+ <Icon name="check-line" size="sm" />
27
+ </CheckboxPrimitive.Indicator>
28
+ </View>
29
+ );
30
+
31
+ if (label) {
32
+ return (
33
+ <label className={styles["checkbox__label"]}>
34
+ {checkbox}
35
+ <Text>{label}</Text>
36
+ </label>
37
+ );
38
+ }
39
+
40
+ return checkbox;
41
+ }
@@ -0,0 +1,20 @@
1
+ .code {
2
+ border-radius: var(--border-radius-default);
3
+ padding: var(--space-8);
4
+ }
5
+
6
+ .code_title {
7
+ border-top-left-radius: 0;
8
+ }
9
+
10
+ .code__value {
11
+ font-size: var(--font-size-default);
12
+ font-family: var(--font-family-code);
13
+ }
14
+
15
+ .code__title {
16
+ border-top-right-radius: var(--border-radius-default);
17
+ border-top-left-radius: var(--border-radius-default);
18
+ padding: var(--space-4) var(--space-8);
19
+ width: fit-content;
20
+ }
@@ -0,0 +1,42 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Code } from ".";
4
+
5
+ const meta = {
6
+ title: "Code/Code",
7
+ component: Code,
8
+ parameters: { layout: "centered" },
9
+ } satisfies Meta<typeof Code>;
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ args: {
17
+ children: `import type { Meta, StoryObj } from "@storybook/react-vite";
18
+
19
+ import { Code } from ".";
20
+
21
+ const meta = {
22
+ title: "Code/Code",
23
+ component: Code,
24
+ parameters: { layout: "centered" },
25
+ } satisfies Meta<typeof Code>;
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ value: \`console.log("hello world")\`,
34
+ language: "javascript",
35
+ },
36
+ render: (args) => <Code {...args} />,
37
+ };`,
38
+ language: "javascript",
39
+ title: "code.stories.tsx",
40
+ },
41
+ render: (args) => <Code {...args} />,
42
+ };
@@ -0,0 +1,73 @@
1
+ import clsx from "clsx";
2
+ import Prism from "prismjs";
3
+ import { useMemo } from "react";
4
+
5
+ import { Surface } from "../surface";
6
+ import { Text } from "../text";
7
+ import { View } from "../view";
8
+
9
+ /**
10
+ * Are you looking for themes?
11
+ * You should define theme in [data-theme].
12
+ *
13
+ * I highly recommend using "postcss-nested-import", so you can do stuff like
14
+ * [data-theme="light"] {
15
+ * @nested-import "prismjs/themes/prism.css";
16
+ * }
17
+ */
18
+
19
+ import styles from "./code.module.css";
20
+
21
+ export interface CodeProps {
22
+ /** Use a specific language (you must import the themes you need) */
23
+ language?: string;
24
+
25
+ /** Optional file name or title */
26
+ title?: string;
27
+
28
+ className?: string;
29
+
30
+ children?: string;
31
+ }
32
+
33
+ export function Code({
34
+ language = "javascript",
35
+ children = "",
36
+ title,
37
+ className,
38
+ }: CodeProps) {
39
+ const html = useMemo(
40
+ () =>
41
+ Prism.highlight(
42
+ children,
43
+ Prism.languages[language] ?? Prism.languages.javascript,
44
+ language,
45
+ ),
46
+ [children, language],
47
+ );
48
+
49
+ return (
50
+ <View className={className}>
51
+ {title ? (
52
+ <Surface elevated className={styles["code__title"]}>
53
+ <Text size="sm" color="dimmer">
54
+ {title}
55
+ </Text>
56
+ </Surface>
57
+ ) : null}
58
+ <Surface
59
+ elevated
60
+ className={clsx(styles["code"], title && styles["code_title"])}
61
+ >
62
+ <pre>
63
+ <code
64
+ className={styles["code__value"]}
65
+ dangerouslySetInnerHTML={{
66
+ __html: html,
67
+ }}
68
+ ></code>
69
+ </pre>
70
+ </Surface>
71
+ </View>
72
+ );
73
+ }
@@ -0,0 +1,27 @@
1
+ .collapse {
2
+ opacity: 0;
3
+ transition:
4
+ height var(--transition-duration-chill)
5
+ var(--transition-timing-function-chill),
6
+ opacity var(--transition-duration-chill)
7
+ var(--transition-timing-function-chill);
8
+ height: 0;
9
+ overflow-y: hidden;
10
+ }
11
+
12
+ .collapse_expand {
13
+ opacity: 1;
14
+ height: var(--collapse-height);
15
+ }
16
+
17
+ .collapse_hidden {
18
+ position: fixed;
19
+ top: 0;
20
+ left: 0;
21
+ visibility: hidden;
22
+ opacity: 0;
23
+ z-index: 0;
24
+ height: fit-content;
25
+ pointer-events: none;
26
+ user-select: none;
27
+ }
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Collapse } from ".";
4
+ import { Text } from "../text";
5
+
6
+ const meta = {
7
+ title: "Collapse",
8
+ component: Collapse,
9
+ parameters: { layout: "centered" },
10
+ } satisfies Meta<typeof Collapse>;
11
+
12
+ export default meta;
13
+
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ expand: true,
19
+ },
20
+ render: (args) => (
21
+ <Collapse {...args}>
22
+ <Text color="inherit" multiline>
23
+ Hello world
24
+ </Text>
25
+ </Collapse>
26
+ ),
27
+ };
@@ -0,0 +1,59 @@
1
+ import { mergeProps, useRender } from "@base-ui/react";
2
+ import clsx from "clsx";
3
+ import { useLayoutEffect, useRef, useState, type ReactNode } from "react";
4
+
5
+ import styles from "./collapse.module.css";
6
+
7
+ interface CollapseProps extends useRender.ComponentProps<"div"> {
8
+ /** Shrink or expand the div. Defaults to true. */
9
+ expand?: boolean;
10
+
11
+ children?: ReactNode;
12
+
13
+ /** Supplied as a dependency to recalculate the height of the children */
14
+ recalculate?: unknown;
15
+ }
16
+
17
+ export function Collapse({
18
+ expand = true,
19
+ render,
20
+ recalculate,
21
+ ...props
22
+ }: CollapseProps) {
23
+ const ref = useRef<HTMLDivElement>(null);
24
+ const [height, setHeight] = useState(0);
25
+
26
+ // calculate height of children
27
+ useLayoutEffect(() => {
28
+ if (!ref.current) {
29
+ return;
30
+ }
31
+
32
+ const clonedRef = ref.current.cloneNode(true) as HTMLElement;
33
+ clonedRef.removeAttribute("style");
34
+ clonedRef.style.width = `${ref.current.offsetWidth}px`;
35
+ clonedRef.className = styles["collapse_hidden"];
36
+ document.body.appendChild(clonedRef);
37
+ setHeight(clonedRef.offsetHeight);
38
+ clonedRef.remove();
39
+ }, [recalculate]);
40
+
41
+ const element = useRender({
42
+ defaultTagName: "div",
43
+ render,
44
+ ref,
45
+ props: mergeProps(
46
+ {
47
+ "aria-hidden": !expand,
48
+ className: clsx(
49
+ styles["collapse"],
50
+ expand && styles["collapse_expand"],
51
+ ),
52
+ style: { "--collapse-height": `${height}px` },
53
+ },
54
+ props,
55
+ ),
56
+ });
57
+
58
+ return element;
59
+ }
@@ -0,0 +1,95 @@
1
+ .command {
2
+ --command-padding: var(--space-6);
3
+
4
+ border: none;
5
+ padding: 0;
6
+
7
+ /* stylelint-disable-next-line */
8
+ [data-framework-close] {
9
+ top: var(--space-4);
10
+ right: var(--space-4);
11
+ }
12
+ }
13
+
14
+ .command__empty {
15
+ padding: var(--space-28) var(--space-16);
16
+ padding-top: calc(var(--space-28) + var(--command-padding));
17
+ text-align: center;
18
+ }
19
+
20
+ .command__input-root {
21
+ position: relative;
22
+ height: var(--space-32);
23
+ }
24
+
25
+ .command__input {
26
+ flex-grow: 1;
27
+ flex-shrink: 1;
28
+ border-bottom-right-radius: 0 !important;
29
+ border-bottom-left-radius: 0 !important;
30
+ background-color: transparent;
31
+ padding: var(--space-4) var(--space-32);
32
+ width: 100%;
33
+ height: 100%;
34
+ line-height: var(--line-height-input);
35
+
36
+ &::placeholder {
37
+ color: var(--foreground-dimmest);
38
+ }
39
+
40
+ &:not([disabled], [data-disabled]) {
41
+ cursor: text;
42
+ }
43
+ }
44
+
45
+ .command__input-icon {
46
+ position: absolute;
47
+ top: var(--space-8);
48
+ left: var(--space-8);
49
+ }
50
+
51
+ .command__list {
52
+ border: 1px solid var(--surface-interactive-border);
53
+ border-top: 0;
54
+ border-bottom-right-radius: var(--border-radius-default) !important;
55
+ border-bottom-left-radius: var(--border-radius-default) !important;
56
+ padding-bottom: var(--command-padding);
57
+ max-height: var(--space-256);
58
+ overflow: hidden auto;
59
+ }
60
+
61
+ .command__group {
62
+ padding-top: var(--space-6);
63
+
64
+ /* stylelint-disable-next-line */
65
+ [cmdk-group-heading] {
66
+ border: 1px solid transparent;
67
+ padding: 0 var(--space-12) var(--space-2) var(--space-12);
68
+ color: var(--foreground-dimmer);
69
+ font-size: var(--font-size-small);
70
+ line-height: var(--line-height-small);
71
+ }
72
+ }
73
+
74
+ .command__item {
75
+ flex-direction: row;
76
+ align-items: center;
77
+ gap: var(--space-8);
78
+ padding: var(--space-4) var(--space-12);
79
+ height: var(--space-32);
80
+ user-select: none;
81
+
82
+ &[data-selected="true"] {
83
+ background-color: var(--interactive-background);
84
+ }
85
+ }
86
+
87
+ .command__shortcut {
88
+ margin-left: auto;
89
+
90
+ /*
91
+ https://tailwindcss.com/docs/letter-spacing
92
+ tracking widest
93
+ */
94
+ letter-spacing: 0.1em;
95
+ }
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { CommandSeparator } from "cmdk";
3
+
4
+ import { CommandDialog, CommandGroup, CommandItem } from ".";
5
+ import { Button } from "../button";
6
+
7
+ const meta = {
8
+ title: "Dialog/Command Dialog",
9
+ component: CommandDialog,
10
+ parameters: { layout: "centered" },
11
+ } satisfies Meta<typeof CommandDialog>;
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = {
18
+ args: {},
19
+ render: (args) => (
20
+ <CommandDialog
21
+ trigger={<Button children="Open Command Dialog" />}
22
+ placeholder="Search and run commands."
23
+ {...args}
24
+ >
25
+ <CommandGroup heading="Suggestions">
26
+ <CommandItem icon="calendar-line" children="Calendar" shortcut="⌘+C" />
27
+ <CommandItem icon="search-line" children="Search Emoji" />
28
+ <CommandItem icon="calculator-line" children="Calculator" />
29
+ </CommandGroup>
30
+ <CommandSeparator />
31
+ <CommandGroup heading="Settings">
32
+ <CommandItem icon="user-line" children="Profile" />
33
+ <CommandItem icon="mail-line" children="Mail" />
34
+ <CommandItem icon="settings-4-line" children="Settings" />
35
+ </CommandGroup>
36
+ </CommandDialog>
37
+ ),
38
+ };