@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,53 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { AllVariants } from "~/stories/components/all-variants";
4
+ import { surfaceBackgrounds } from "~/stories/data";
5
+ import { tokens } from "~/styles/tokens";
6
+
7
+ import { Slider } from ".";
8
+ import { Surface } from "../surface";
9
+
10
+ const meta = {
11
+ title: "Input/Slider",
12
+ component: Slider,
13
+ parameters: { layout: "centered" },
14
+ } satisfies Meta<typeof Slider>;
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: {
22
+ defaultValue: 25,
23
+ disabled: false,
24
+ },
25
+ render: (args) => <Slider style={{ width: tokens.space256 }} {...args} />,
26
+ };
27
+
28
+ export const AllInteractiveStyles: Story = {
29
+ args: {
30
+ color: "primary",
31
+ defaultValue: 25,
32
+ disabled: false,
33
+ },
34
+ render: (args) => (
35
+ <AllVariants
36
+ variantName="background"
37
+ variants={surfaceBackgrounds}
38
+ style={{ backgroundColor: "transparent" }}
39
+ element={
40
+ <Surface
41
+ style={{
42
+ padding: tokens.space16,
43
+ gap: tokens.space8,
44
+ borderWidth: "1px",
45
+ borderColor: tokens.outlineDimmest,
46
+ }}
47
+ >
48
+ <Slider style={{ width: tokens.space256 }} {...args} />{" "}
49
+ </Surface>
50
+ }
51
+ />
52
+ ),
53
+ };
@@ -0,0 +1,14 @@
1
+ import clsx from "clsx";
2
+
3
+ import { Icon } from "../icon";
4
+ import { View, type ViewProps } from "../view";
5
+
6
+ import styles from "./spinner.module.css";
7
+
8
+ export function Spinner({ className, ...props }: ViewProps) {
9
+ return (
10
+ <View className={clsx(styles["spinner"], className)} {...props}>
11
+ <Icon name="loader-2-line" />
12
+ </View>
13
+ );
14
+ }
@@ -0,0 +1,13 @@
1
+ .spinner {
2
+ animation: loading 3s linear infinite;
3
+ }
4
+
5
+ @keyframes loading {
6
+ from {
7
+ rotate: 0deg;
8
+ }
9
+
10
+ to {
11
+ rotate: 360deg;
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Spinner } from ".";
4
+
5
+ const meta = {
6
+ title: "Spinner",
7
+ component: Spinner,
8
+ parameters: { layout: "centered" },
9
+ } satisfies Meta<typeof Spinner>;
10
+
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Default: Story = {
16
+ render: (args) => <Spinner {...args} />,
17
+ };
@@ -0,0 +1,88 @@
1
+ import clsx from "clsx";
2
+
3
+ import { Avatar, type AvatarProps } from "../avatar";
4
+ import { Text } from "../text";
5
+ import { Tooltip } from "../tooltip";
6
+ import { View, type Color } from "../view";
7
+
8
+ import styles from "./stacked-avatars.module.css";
9
+
10
+ export interface StackedAvatarsProps {
11
+ /** User objects */
12
+ users: Array<
13
+ AvatarProps & {
14
+ /** Ring color around the avatar */
15
+ color: Color;
16
+ }
17
+ >;
18
+
19
+ /** The maximum number of user to display. Defaults to 3. */
20
+ visibleUsers?: number;
21
+
22
+ /** The size of the avatars. Defaults to "md". */
23
+ size?: AvatarProps["size"];
24
+ }
25
+
26
+ export function StackedAvatars({
27
+ users,
28
+ visibleUsers = 3,
29
+ size = "md",
30
+ }: StackedAvatarsProps) {
31
+ return (
32
+ <Tooltip
33
+ className={styles["stacked-avatars__tooltip-content"]}
34
+ trigger={
35
+ <View className={styles["stacked-avatars"]}>
36
+ {users
37
+ .slice(0, visibleUsers)
38
+ .map(({ color, className, ...user }, index) => (
39
+ <View color={color} key={user.username}>
40
+ <Avatar
41
+ size={size}
42
+ className={clsx(
43
+ styles["stacked-avatars__avatar"],
44
+ styles["stacked-avatars__avatar_color"],
45
+ className,
46
+ )}
47
+ style={{ zIndex: index }}
48
+ {...user}
49
+ />
50
+ </View>
51
+ ))}
52
+ {users.length > visibleUsers && (
53
+ <View
54
+ className={clsx(
55
+ styles["stacked-avatars__visible"],
56
+ styles[`stacked-avatars__visible_size_${size}`],
57
+ )}
58
+ style={{ zIndex: visibleUsers + 1 }}
59
+ >
60
+ <Text size="sm">+{users.length - visibleUsers}</Text>
61
+ </View>
62
+ )}
63
+ </View>
64
+ }
65
+ >
66
+ {users.map(({ color, className, ...user }) => (
67
+ <View
68
+ key={user.username}
69
+ color={color}
70
+ className={styles["stacked-avatars__tooltip-user"]}
71
+ >
72
+ <Avatar
73
+ key={user.username}
74
+ className={clsx(styles["stacked-avatars__avatar_color"], className)}
75
+ {...user}
76
+ size="sm"
77
+ />
78
+ <Text
79
+ color="dimmer"
80
+ className={styles["stacked-avatars__tooltip-text"]}
81
+ >
82
+ {user.username}
83
+ </Text>
84
+ </View>
85
+ ))}
86
+ </Tooltip>
87
+ );
88
+ }
@@ -0,0 +1,79 @@
1
+ .stacked-avatars {
2
+ flex-direction: row;
3
+ transform: translateX(0);
4
+ padding-left: 10px;
5
+ }
6
+
7
+ .stacked-avatars__avatar {
8
+ cursor: default;
9
+ margin-left: -10px;
10
+ }
11
+
12
+ .stacked-avatars__avatar_color {
13
+ border-color: var(--view-colorway-default);
14
+ }
15
+
16
+ .stacked-avatars__visible {
17
+ justify-content: center;
18
+ align-items: center;
19
+ cursor: pointer;
20
+ margin-left: -10px;
21
+ border: 1px solid var(--outline-dimmest);
22
+ border-radius: var(--border-radius-round);
23
+ background-color: var(--background-default);
24
+ aspect-ratio: 1 / 1;
25
+ width: var(--stacked-avatars-size);
26
+ height: var(--stacked-avatars-size);
27
+ overflow: hidden;
28
+ color: var(--foreground-dimmer);
29
+ }
30
+
31
+ /* TODO maybe copy text size as well because rn +1 doesn't move */
32
+
33
+ /* Size variants copied from Avatar component */
34
+ .stacked-avatars__visible_size_sm {
35
+ --stacked-avatars-size: var(--space-24);
36
+ }
37
+
38
+ .stacked-avatars__visible_size_md {
39
+ --stacked-avatars-size: var(--space-32);
40
+ }
41
+
42
+ .stacked-avatars__visible_size_lg {
43
+ --stacked-avatars-size: var(--space-40);
44
+ }
45
+
46
+ .stacked-avatars__visible_size_xl {
47
+ --stacked-avatars-size: var(--space-48);
48
+ }
49
+
50
+ .stacked-avatars__visible_size_2xl {
51
+ --stacked-avatars-size: var(--space-56);
52
+ }
53
+
54
+ .stacked-avatars__visible_size_3xl {
55
+ --stacked-avatars-size: var(--space-64);
56
+ }
57
+
58
+ /* Tooltip content */
59
+ .stacked-avatars__tooltip-content {
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: start;
63
+ gap: var(--space-4);
64
+ padding: var(--space-8);
65
+ width: 100%;
66
+ max-width: var(--space-128);
67
+ }
68
+
69
+ .stacked-avatars__tooltip-user {
70
+ flex-direction: row;
71
+ justify-content: center;
72
+ align-items: center;
73
+ gap: var(--space-6);
74
+ width: 100%;
75
+ }
76
+
77
+ .stacked-avatars__tooltip-text {
78
+ flex: 1 1 auto;
79
+ }
@@ -0,0 +1,48 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { sizes } from "~/stories/data";
4
+
5
+ import { StackedAvatars } from ".";
6
+
7
+ const meta = {
8
+ title: "Stacked Avatars",
9
+ component: StackedAvatars,
10
+ parameters: { layout: "centered" },
11
+ argTypes: {
12
+ size: {
13
+ control: "select",
14
+ options: sizes,
15
+ },
16
+ },
17
+ } satisfies Meta<typeof StackedAvatars>;
18
+
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {
24
+ args: {
25
+ users: [
26
+ {
27
+ color: "red",
28
+ image: "https://natmfat.com/logo.png",
29
+ username: "natmfat",
30
+ },
31
+ {
32
+ color: "green",
33
+ username: "green",
34
+ },
35
+ {
36
+ color: "blue",
37
+ username: "blue",
38
+ },
39
+ {
40
+ color: "yellow",
41
+ username: "yellow",
42
+ },
43
+ ],
44
+ visibleUsers: 3,
45
+ size: "md",
46
+ },
47
+ render: (args) => <StackedAvatars {...args} />,
48
+ };
@@ -0,0 +1,96 @@
1
+ import { useRender } from "@base-ui/react";
2
+ import clsx from "clsx";
3
+
4
+ import type { Size } from "~/styles/tokens";
5
+
6
+ import { Heading } from "../heading";
7
+ import { Icon } from "../icon";
8
+ import type { IconName } from "../icon/icon-names";
9
+ import { Text } from "../text";
10
+ import { View, type Color } from "../view";
11
+
12
+ import styles from "./status-banner.module.css";
13
+
14
+ export interface StatusBannerProps extends Omit<
15
+ useRender.ComponentProps<"div">,
16
+ "children"
17
+ > {
18
+ /** Communicate purpose with color */
19
+ color?: Color;
20
+
21
+ /** Icon on the left side */
22
+ icon: IconName;
23
+
24
+ /** Optional title */
25
+ title?: string;
26
+
27
+ /** Actual content of the status banner */
28
+ description: string;
29
+
30
+ /** Size, defaults to lg */
31
+ size?: Extract<Size, "md" | "lg">;
32
+ }
33
+
34
+ // map status banner size to size of indivdual elements
35
+ const sizeMap: Record<
36
+ NonNullable<StatusBannerProps["size"]>,
37
+ {
38
+ icon: Size;
39
+ title: Size;
40
+ description: Size;
41
+ }
42
+ > = {
43
+ md: {
44
+ icon: "md",
45
+ title: "md",
46
+ description: "sm",
47
+ },
48
+ lg: {
49
+ icon: "xl",
50
+ title: "lg",
51
+ description: "md",
52
+ },
53
+ };
54
+
55
+ // TODO figure out if you can really just pass on render like this or if useRender is required
56
+
57
+ export function StatusBanner({
58
+ color,
59
+ icon,
60
+ title,
61
+ description,
62
+ size = "lg",
63
+ render,
64
+ ...props
65
+ }: StatusBannerProps) {
66
+ return (
67
+ <View
68
+ render={render}
69
+ {...props}
70
+ color={color}
71
+ className={clsx(
72
+ styles["status-banner"],
73
+ styles[`status-banner_size_${size}`],
74
+ color
75
+ ? styles["status-banner_variant_colorway"]
76
+ : styles["status-banner_variant_neutral"],
77
+ )}
78
+ >
79
+ {icon ? <Icon name={icon} size={sizeMap[size].icon} /> : null}
80
+
81
+ <View className={styles["status-banner__content"]}>
82
+ <Heading level={1} size={sizeMap[size].title} color="inherit">
83
+ {title}
84
+ </Heading>
85
+ <Text
86
+ className={styles["status-banner__description"]}
87
+ multiline
88
+ size={sizeMap[size].description}
89
+ color="inherit"
90
+ >
91
+ {description}
92
+ </Text>
93
+ </View>
94
+ </View>
95
+ );
96
+ }
@@ -0,0 +1,52 @@
1
+ .status-banner {
2
+ flex-grow: 1;
3
+ flex-shrink: 1;
4
+ flex-direction: row;
5
+ align-items: center;
6
+ gap: var(--status-banner-gap);
7
+ border-width: 1px;
8
+ border-style: solid;
9
+ border-radius: var(--border-radius-default);
10
+ padding: var(--status-banner-padding);
11
+ width: fit-content;
12
+ }
13
+
14
+ .status-banner_variant_neutral {
15
+ border-color: var(--surface-interactive-border);
16
+ background: var(--surface-interactive-background);
17
+ color: var(--foreground-default);
18
+ }
19
+
20
+ .status-banner_variant_colorway {
21
+ border-color: var(--view-colorway-stronger);
22
+ background: var(--view-colorway-dimmest);
23
+ color: var(--view-colorway-strongest);
24
+ }
25
+
26
+ .status-banner_size_md {
27
+ --status-banner-gap: var(--space-8);
28
+ --status-banner-padding: var(--space-8);
29
+ --status-banner-content-gap: 0;
30
+ }
31
+
32
+ .status-banner_size_lg {
33
+ --status-banner-gap: var(--space-12);
34
+ --status-banner-padding: var(--space-12);
35
+ --status-banner-content-gap: var(--space-2);
36
+ }
37
+
38
+ .status-banner__content {
39
+ flex-grow: 1;
40
+ flex-shrink: 1;
41
+ gap: var(--status-banner-content-gap);
42
+ }
43
+
44
+ .status-banner__description {
45
+ flex-grow: 1;
46
+ flex-shrink: 1;
47
+ }
48
+
49
+ .status-banner__close {
50
+ flex-grow: 0;
51
+ background: transparent !important;
52
+ }
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { colors } from "~/stories/data";
4
+
5
+ import { StatusBanner } from ".";
6
+ import { View } from "../view";
7
+
8
+ const meta = {
9
+ title: "Block/Status Banner",
10
+ component: StatusBanner,
11
+ parameters: { layout: "centered" },
12
+ argTypes: {
13
+ size: {
14
+ control: "select",
15
+ options: ["md", "lg"],
16
+ },
17
+ color: {
18
+ control: "select",
19
+ options: colors,
20
+ },
21
+ },
22
+ } satisfies Meta<typeof StatusBanner>;
23
+
24
+ export default meta;
25
+
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ icon: "alarm-warning-line",
31
+ title: "This plan has been deprecated.",
32
+ description:
33
+ "You can continue to use these organizations, but if you want to add users, you’ll have to update to a current plan.",
34
+ size: "lg",
35
+ color: "warning",
36
+ },
37
+ render: (args) => {
38
+ return (
39
+ <View style={{ width: "512px" }}>
40
+ <StatusBanner {...args} />
41
+ </View>
42
+ );
43
+ },
44
+ };
@@ -0,0 +1,83 @@
1
+ import type { useRender } from "@base-ui/react";
2
+ import clsx from "clsx";
3
+ import { createContext, useContext } from "react";
4
+
5
+ import { View, type Color } from "../view";
6
+
7
+ import styles from "./surface.module.css";
8
+
9
+ export interface SurfaceProps extends useRender.ComponentProps<"div"> {
10
+ /**
11
+ * Explicitly sets the background color of the surface.
12
+ * If this prop is set, the `elevated` prop will be ignored.
13
+ */
14
+ background?: Background;
15
+
16
+ /**
17
+ * If true, the surface will be elevated by one level from the current
18
+ * context. If false, the surface will be at the same level as before.
19
+ *
20
+ * If the `background` prop is set, this prop will be ignored.
21
+ */
22
+ elevated?: boolean;
23
+
24
+ /**
25
+ * Add CSS color variables.
26
+ */
27
+ color?: Color;
28
+ }
29
+
30
+ const ElevationContext = createContext(0);
31
+
32
+ export const Surface = ({
33
+ background,
34
+ elevated,
35
+ className,
36
+ ...props
37
+ }: SurfaceProps) => {
38
+ // automatically manage elevation
39
+ let elevation = useContext(ElevationContext);
40
+ if (background) {
41
+ elevation = backgroundToElevation(background);
42
+ } else if (elevated) {
43
+ elevation++;
44
+ }
45
+
46
+ return (
47
+ <ElevationContext.Provider value={elevation}>
48
+ <View
49
+ className={clsx(
50
+ styles["surface"],
51
+ styles[`surface--${elevationToBackground(elevation)}`],
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ </ElevationContext.Provider>
57
+ );
58
+ };
59
+
60
+ Surface.displayName = "Surface";
61
+
62
+ const backgrounds = ["root", "default", "higher", "highest"] as const;
63
+
64
+ export type Background = (typeof backgrounds)[number];
65
+
66
+ /**
67
+ * Convert a background string into a numbered elevation level
68
+ * @param background Background provided as a string
69
+ * @returns Background as a number
70
+ */
71
+ function backgroundToElevation(background: Background = "root") {
72
+ const elevation = backgrounds.indexOf(background);
73
+ return Math.min(Math.max(elevation, 0), backgrounds.length - 1);
74
+ }
75
+
76
+ /**
77
+ * Convert a numbered elevation level into a background string
78
+ * @param elevation Background as a number
79
+ * @returns Background as a string
80
+ */
81
+ function elevationToBackground(elevation: number = 0): Background {
82
+ return backgrounds[elevation] || "root";
83
+ }
@@ -0,0 +1,35 @@
1
+ .surface {
2
+ background-color: var(--surface-background);
3
+ }
4
+
5
+ .surface--root {
6
+ --surface-background: var(--background-root);
7
+ --surface-interactive-background: var(--background-default);
8
+ --surface-interactive-background-active: var(--background-higher);
9
+ --surface-interactive-border: var(--outline-dimmest);
10
+ --surface-interactive-border-hover: var(--outline-default);
11
+ }
12
+
13
+ .surface--default {
14
+ --surface-background: var(--background-default);
15
+ --surface-interactive-background: var(--background-higher);
16
+ --surface-interactive-background-active: var(--background-highest);
17
+ --surface-interactive-border: var(--outline-dimmer);
18
+ --surface-interactive-border-hover: var(--outline-stronger);
19
+ }
20
+
21
+ .surface--higher {
22
+ --surface-background: var(--background-higher);
23
+ --surface-interactive-background: var(--background-highest);
24
+ --surface-interactive-background-active: var(--background-default);
25
+ --surface-interactive-border: var(--outline-default);
26
+ --surface-interactive-border-hover: var(--outline-strongest);
27
+ }
28
+
29
+ .surface--highest {
30
+ --surface-background: var(--background-highest);
31
+ --surface-interactive-background: var(--background-higher);
32
+ --surface-interactive-background-active: var(--background-default);
33
+ --surface-interactive-border: var(--outline-stronger);
34
+ --surface-interactive-border-hover: var(--outline-strongest);
35
+ }