@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,104 @@
|
|
|
1
|
+
import { Toast as ToastPrimitive } from "@base-ui/react/toast";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../button";
|
|
5
|
+
import { Heading } from "../heading";
|
|
6
|
+
import { IconButton } from "../icon-button";
|
|
7
|
+
import { Surface } from "../surface";
|
|
8
|
+
import { Text } from "../text";
|
|
9
|
+
import { View, type Color } from "../view";
|
|
10
|
+
import { useToastManager } from "./use-toast-manager";
|
|
11
|
+
|
|
12
|
+
import styles from "./toast.module.css";
|
|
13
|
+
|
|
14
|
+
export const ToastProvider = ToastPrimitive.Provider;
|
|
15
|
+
|
|
16
|
+
const toastTypeColor: Record<string, Color> = {
|
|
17
|
+
info: "blue",
|
|
18
|
+
error: "red",
|
|
19
|
+
warning: "yellow",
|
|
20
|
+
success: "green",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function ToastViewport() {
|
|
24
|
+
return (
|
|
25
|
+
<ToastPrimitive.Portal>
|
|
26
|
+
<ToastPrimitive.Viewport className={styles["toast__viewport"]}>
|
|
27
|
+
<ToastList />
|
|
28
|
+
</ToastPrimitive.Viewport>
|
|
29
|
+
</ToastPrimitive.Portal>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// TODO we're not using the toast primitives here...
|
|
34
|
+
// TODO specify toast background in viewport?
|
|
35
|
+
|
|
36
|
+
function ToastList() {
|
|
37
|
+
const toastManager = useToastManager();
|
|
38
|
+
return toastManager.toasts.map((toast) => {
|
|
39
|
+
const color =
|
|
40
|
+
toast.type && toast.type in toastTypeColor
|
|
41
|
+
? toastTypeColor[toast.type]
|
|
42
|
+
: undefined;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<ToastPrimitive.Root
|
|
46
|
+
key={toast.id}
|
|
47
|
+
toast={toast}
|
|
48
|
+
className={clsx(
|
|
49
|
+
styles["toast"],
|
|
50
|
+
color
|
|
51
|
+
? styles[`toast_variant_colorway`]
|
|
52
|
+
: styles["toast_variant_neutral"],
|
|
53
|
+
)}
|
|
54
|
+
render={<Surface background="default" color={color} />}
|
|
55
|
+
>
|
|
56
|
+
<View
|
|
57
|
+
render={<ToastPrimitive.Content />}
|
|
58
|
+
className={styles["toast__content"]}
|
|
59
|
+
>
|
|
60
|
+
<View className={styles["toast__row"]}>
|
|
61
|
+
<View className={styles["toast__header"]}>
|
|
62
|
+
{toast.title ? (
|
|
63
|
+
<Heading
|
|
64
|
+
level={2}
|
|
65
|
+
size="lg"
|
|
66
|
+
color="inherit"
|
|
67
|
+
className={styles["toast__title"]}
|
|
68
|
+
>
|
|
69
|
+
{toast.title}
|
|
70
|
+
</Heading>
|
|
71
|
+
) : null}
|
|
72
|
+
|
|
73
|
+
<ToastPrimitive.Description
|
|
74
|
+
render={<Text multiline color={color ? "inherit" : "dimmer"} />}
|
|
75
|
+
className={styles["toast__description"]}
|
|
76
|
+
/>
|
|
77
|
+
</View>
|
|
78
|
+
|
|
79
|
+
{/* close icon will appear over content UNLESS we disable aboslute positioning */}
|
|
80
|
+
<ToastPrimitive.Close
|
|
81
|
+
render={
|
|
82
|
+
<IconButton
|
|
83
|
+
className={toast.title ? styles["toast__close"] : undefined}
|
|
84
|
+
interactive={color ? `${color}_no-fill` : "no-fill"}
|
|
85
|
+
icon="close-line"
|
|
86
|
+
size="sm"
|
|
87
|
+
alt="Close"
|
|
88
|
+
/>
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
</View>
|
|
92
|
+
{toast.action ? (
|
|
93
|
+
<Button
|
|
94
|
+
{...toast.action}
|
|
95
|
+
interactive={color ? `${color}_fill` : true}
|
|
96
|
+
>
|
|
97
|
+
{toast.action.children}
|
|
98
|
+
</Button>
|
|
99
|
+
) : null}
|
|
100
|
+
</View>
|
|
101
|
+
</ToastPrimitive.Root>
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Toast,
|
|
3
|
+
type ToastManagerAddOptions,
|
|
4
|
+
type UseToastManagerReturnValue,
|
|
5
|
+
} from "@base-ui/react/toast";
|
|
6
|
+
|
|
7
|
+
import type { Override } from "~/-types";
|
|
8
|
+
|
|
9
|
+
import type { ButtonProps } from "../button";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A whole lot of useless effort
|
|
13
|
+
* Basically I want "actionProps" to be "action"
|
|
14
|
+
* And I want "action" to use ButtonProps with text
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type ActionProps = {
|
|
18
|
+
type?: "info" | "error" | "warning" | "success" | (string & {});
|
|
19
|
+
action?: ButtonProps;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type FrameworkAddOptions<Data extends object> = Omit<
|
|
23
|
+
ToastManagerAddOptions<Data>,
|
|
24
|
+
"actionProps"
|
|
25
|
+
> &
|
|
26
|
+
ActionProps;
|
|
27
|
+
|
|
28
|
+
type FrameworkUpdateOptions<Data extends object> = Partial<
|
|
29
|
+
FrameworkAddOptions<Data>
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
type FrameworkPromiseOptions<Value, Data extends object> = {
|
|
33
|
+
loading: string | FrameworkUpdateOptions<Data>;
|
|
34
|
+
success:
|
|
35
|
+
| string
|
|
36
|
+
| FrameworkUpdateOptions<Data>
|
|
37
|
+
| ((result: Value) => string | FrameworkUpdateOptions<Data>);
|
|
38
|
+
error:
|
|
39
|
+
| string
|
|
40
|
+
| FrameworkUpdateOptions<Data>
|
|
41
|
+
| ((error: unknown) => string | FrameworkUpdateOptions<Data>);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type FrameworkToastManager = Override<
|
|
45
|
+
UseToastManagerReturnValue,
|
|
46
|
+
{
|
|
47
|
+
toasts: Array<UseToastManagerReturnValue["toasts"][number] & ActionProps>;
|
|
48
|
+
add<Data extends object>(options: FrameworkAddOptions<Data>): string;
|
|
49
|
+
update<Data extends object>(
|
|
50
|
+
id: string,
|
|
51
|
+
updates: FrameworkUpdateOptions<Data>,
|
|
52
|
+
): void;
|
|
53
|
+
promise<Value, Data extends object>(
|
|
54
|
+
promise: Promise<Value>,
|
|
55
|
+
options: FrameworkPromiseOptions<Value, Data>,
|
|
56
|
+
): Promise<Value>;
|
|
57
|
+
}
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
export function useToastManager() {
|
|
61
|
+
const toastManager = Toast.useToastManager();
|
|
62
|
+
return toastManager as unknown as FrameworkToastManager;
|
|
63
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ReactElement, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { textify } from "~/-utils";
|
|
6
|
+
|
|
7
|
+
import { Surface } from "../surface";
|
|
8
|
+
import { TooltipArrow } from "./tooltip-arrow";
|
|
9
|
+
|
|
10
|
+
import transitionStyles from "../../styles/transitions.module.css";
|
|
11
|
+
import styles from "./tooltip.module.css";
|
|
12
|
+
|
|
13
|
+
export const TooltipProvider = TooltipPrimitive.Provider;
|
|
14
|
+
|
|
15
|
+
type TooltipRootProps = Omit<TooltipPrimitive.Root.Props, "children">;
|
|
16
|
+
type TooltipPositionerProps = Pick<
|
|
17
|
+
TooltipPrimitive.Positioner.Props,
|
|
18
|
+
"align" | "side"
|
|
19
|
+
>;
|
|
20
|
+
export type TooltipProps = TooltipRootProps &
|
|
21
|
+
TooltipPositionerProps & {
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
|
|
24
|
+
/** Specify trigger to open tooltip. */
|
|
25
|
+
trigger?: ReactElement;
|
|
26
|
+
|
|
27
|
+
/** Apply className to tooltip content */
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function Tooltip({
|
|
32
|
+
children,
|
|
33
|
+
trigger,
|
|
34
|
+
align,
|
|
35
|
+
side,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: TooltipProps) {
|
|
39
|
+
return (
|
|
40
|
+
<TooltipPrimitive.Root {...props}>
|
|
41
|
+
{trigger ? <TooltipPrimitive.Trigger render={trigger} /> : null}
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<TooltipPrimitive.Positioner align={align} side={side} sideOffset={8}>
|
|
44
|
+
<TooltipPrimitive.Popup
|
|
45
|
+
render={<Surface background="highest" />}
|
|
46
|
+
className={clsx(
|
|
47
|
+
styles["tooltip"],
|
|
48
|
+
transitionStyles["transition_scale"],
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<TooltipPrimitive.Arrow className={styles["tooltip__arrow"]}>
|
|
53
|
+
<TooltipArrow />
|
|
54
|
+
</TooltipPrimitive.Arrow>
|
|
55
|
+
{textify(children)}
|
|
56
|
+
</TooltipPrimitive.Popup>
|
|
57
|
+
</TooltipPrimitive.Positioner>
|
|
58
|
+
</TooltipPrimitive.Portal>
|
|
59
|
+
</TooltipPrimitive.Root>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
|
|
3
|
+
import styles from "./tooltip.module.css";
|
|
4
|
+
|
|
5
|
+
export function TooltipArrow(props: { className?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
width="10"
|
|
9
|
+
height="5"
|
|
10
|
+
viewBox="0 0 30 10"
|
|
11
|
+
preserveAspectRatio="none"
|
|
12
|
+
className={clsx(styles["tooltip__arrow-icon"], props.className)}
|
|
13
|
+
>
|
|
14
|
+
<polygon points="0,0 30,0 15,10"></polygon>
|
|
15
|
+
</svg>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
.tooltip {
|
|
2
|
+
z-index: 50;
|
|
3
|
+
box-shadow: var(--shadow-1);
|
|
4
|
+
border: 1px solid var(--outline-dimmer);
|
|
5
|
+
border-radius: var(--border-radius-default);
|
|
6
|
+
background-color: var(--surface-background);
|
|
7
|
+
padding: var(--space-4) var(--space-8);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.tooltip__arrow {
|
|
11
|
+
&[data-instant] {
|
|
12
|
+
transition: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&[data-side="top"] {
|
|
16
|
+
bottom: -4px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&[data-side="bottom"] {
|
|
20
|
+
top: -4px;
|
|
21
|
+
rotate: 180deg;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&[data-side="left"] {
|
|
25
|
+
right: -7px;
|
|
26
|
+
rotate: -90deg;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&[data-side="right"] {
|
|
30
|
+
left: -7px;
|
|
31
|
+
rotate: 90deg;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tooltip__arrow-icon {
|
|
36
|
+
display: block;
|
|
37
|
+
position: relative;
|
|
38
|
+
fill: var(--surface-background) !important;
|
|
39
|
+
stroke: var(--outline-dimmer);
|
|
40
|
+
stroke-dasharray: 0 30 22 0 22;
|
|
41
|
+
stroke-linejoin: round;
|
|
42
|
+
stroke-width: 2;
|
|
43
|
+
border-top: 1px solid var(--surface-background);
|
|
44
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { disable } from "~/stories/utils";
|
|
4
|
+
import { tokens } from "~/styles/tokens";
|
|
5
|
+
|
|
6
|
+
import { Tooltip, TooltipProvider } from ".";
|
|
7
|
+
import { IconButton } from "../icon-button";
|
|
8
|
+
import { View } from "../view";
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Tooltip",
|
|
12
|
+
component: Tooltip,
|
|
13
|
+
parameters: { layout: "centered" },
|
|
14
|
+
argTypes: {
|
|
15
|
+
children: {
|
|
16
|
+
control: "text",
|
|
17
|
+
},
|
|
18
|
+
align: {
|
|
19
|
+
control: "inline-radio",
|
|
20
|
+
options: ["start", "center", "end"],
|
|
21
|
+
},
|
|
22
|
+
open: {
|
|
23
|
+
control: "boolean",
|
|
24
|
+
},
|
|
25
|
+
...disable(["className"]),
|
|
26
|
+
},
|
|
27
|
+
} satisfies Meta<typeof Tooltip>;
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
argTypes: {
|
|
35
|
+
trigger: {
|
|
36
|
+
table: {
|
|
37
|
+
disable: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
children: "Bold",
|
|
43
|
+
align: "center",
|
|
44
|
+
open: true,
|
|
45
|
+
},
|
|
46
|
+
render: (args) => (
|
|
47
|
+
<Tooltip
|
|
48
|
+
trigger={<IconButton icon="bold" alt="Bold" interactive />}
|
|
49
|
+
{...args}
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Provider: Story = {
|
|
55
|
+
argTypes: {
|
|
56
|
+
...disable(["message", "trigger", "open", "children"]),
|
|
57
|
+
},
|
|
58
|
+
render: () => (
|
|
59
|
+
<TooltipProvider>
|
|
60
|
+
<View style={{ flexDirection: "row", gap: tokens.space4 }}>
|
|
61
|
+
<Tooltip
|
|
62
|
+
children="Bold"
|
|
63
|
+
trigger={<IconButton icon="bold" alt="Bold" interactive />}
|
|
64
|
+
/>
|
|
65
|
+
<Tooltip
|
|
66
|
+
children="Italic"
|
|
67
|
+
trigger={<IconButton icon="italic" alt="Italic" interactive />}
|
|
68
|
+
/>
|
|
69
|
+
<Tooltip
|
|
70
|
+
children="Underline"
|
|
71
|
+
trigger={<IconButton icon="underline" alt="Underline" interactive />}
|
|
72
|
+
/>
|
|
73
|
+
</View>
|
|
74
|
+
</TooltipProvider>
|
|
75
|
+
),
|
|
76
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { mergeProps, useRender } from "@base-ui/react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ElementType } from "react";
|
|
4
|
+
|
|
5
|
+
import colorwayStyles from "./view_colorway.module.css";
|
|
6
|
+
import interactiveStyles from "./view_interactive.module.css";
|
|
7
|
+
import loadingStyles from "./view_loading.module.css";
|
|
8
|
+
import styles from "./view.module.css";
|
|
9
|
+
|
|
10
|
+
export type InteractiveStyle =
|
|
11
|
+
| "fill"
|
|
12
|
+
| "no-fill"
|
|
13
|
+
| "outline"
|
|
14
|
+
| "fill-outline"
|
|
15
|
+
| "list-item";
|
|
16
|
+
|
|
17
|
+
export type Color =
|
|
18
|
+
| "primary"
|
|
19
|
+
| "positive"
|
|
20
|
+
| "negative"
|
|
21
|
+
| "warning"
|
|
22
|
+
| "red"
|
|
23
|
+
| "orange"
|
|
24
|
+
| "yellow"
|
|
25
|
+
| "green"
|
|
26
|
+
| "teal"
|
|
27
|
+
| "blue"
|
|
28
|
+
| "blurple"
|
|
29
|
+
| "purple"
|
|
30
|
+
| "magenta"
|
|
31
|
+
| "pink"
|
|
32
|
+
| "grey";
|
|
33
|
+
|
|
34
|
+
export type ColorStyle =
|
|
35
|
+
| "outline"
|
|
36
|
+
| "outline-static"
|
|
37
|
+
| "mute-static"
|
|
38
|
+
| "fill"
|
|
39
|
+
| "no-fill"
|
|
40
|
+
| "fill-static"
|
|
41
|
+
| "fill-outline";
|
|
42
|
+
|
|
43
|
+
export type ColorVariant = `${Color}_${ColorStyle}`;
|
|
44
|
+
|
|
45
|
+
export type LoadingVariant = "background" | "foreground";
|
|
46
|
+
|
|
47
|
+
export type ViewProps<T extends ElementType = "div"> = Omit<
|
|
48
|
+
useRender.ComponentProps<T>,
|
|
49
|
+
"color"
|
|
50
|
+
> & {
|
|
51
|
+
/**
|
|
52
|
+
* Centralized property to define either an interactive variant or colorway.
|
|
53
|
+
* We know which is which because colorways have an underscore (Color_ColorVariant, like primary_fill).
|
|
54
|
+
*
|
|
55
|
+
* Static variants will not apply transitions or cursor effects! Do not use them for interactive elements.
|
|
56
|
+
*
|
|
57
|
+
* Setting `interactive` to true will use "fill".
|
|
58
|
+
*/
|
|
59
|
+
interactive?: boolean | InteractiveStyle | ColorVariant;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Add CSS color variables but do nothing else.
|
|
63
|
+
* Useful for using View as a sort of "Color Provider" so children can use colors and create custom variants.
|
|
64
|
+
*/
|
|
65
|
+
color?: Color;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Make this element look like it's loading.
|
|
69
|
+
* Setting `loading` to true will use "foreground".
|
|
70
|
+
* It'll use sensible defaults based on the colorway or interactive prop.
|
|
71
|
+
*/
|
|
72
|
+
loading?: boolean | LoadingVariant;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const View = <T extends ElementType = "div">({
|
|
76
|
+
interactive,
|
|
77
|
+
loading,
|
|
78
|
+
color,
|
|
79
|
+
render,
|
|
80
|
+
...props
|
|
81
|
+
}: ViewProps<T>) => {
|
|
82
|
+
const normalized = normalize({ interactive, loading });
|
|
83
|
+
const element = useRender({
|
|
84
|
+
defaultTagName: "div",
|
|
85
|
+
render,
|
|
86
|
+
props: mergeProps(
|
|
87
|
+
{
|
|
88
|
+
className: clsx(
|
|
89
|
+
styles["view"],
|
|
90
|
+
color && colorwayStyles[`view_colorway_color-${color}`],
|
|
91
|
+
normalized.interactive && [
|
|
92
|
+
interactiveStyles["view_interactive"],
|
|
93
|
+
interactiveStyles[`view_interactive_${normalized.interactive}`],
|
|
94
|
+
],
|
|
95
|
+
normalized.colorway && [
|
|
96
|
+
normalized.colorway[1].endsWith("static")
|
|
97
|
+
? colorwayStyles["view_colorway_static"]
|
|
98
|
+
: colorwayStyles["view_colorway"],
|
|
99
|
+
colorwayStyles[`view_colorway_${normalized.colorway[1]}`],
|
|
100
|
+
colorwayStyles[`view_colorway_color-${normalized.colorway[0]}`],
|
|
101
|
+
],
|
|
102
|
+
normalized.loading && [
|
|
103
|
+
loadingStyles["view_loading"],
|
|
104
|
+
loadingStyles[`view_loading_${normalized.loading}`],
|
|
105
|
+
],
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
props,
|
|
109
|
+
),
|
|
110
|
+
});
|
|
111
|
+
return element;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const normalize = (props: Pick<ViewProps, "interactive" | "loading">) => {
|
|
115
|
+
let interactive: InteractiveStyle | null = null;
|
|
116
|
+
let colorway: [Color, ColorStyle] | null = null;
|
|
117
|
+
|
|
118
|
+
if (props.interactive && typeof props.interactive === "boolean") {
|
|
119
|
+
interactive = "fill";
|
|
120
|
+
} else if (typeof props.interactive === "string") {
|
|
121
|
+
if (
|
|
122
|
+
typeof props.interactive === "string" &&
|
|
123
|
+
props.interactive.includes("_")
|
|
124
|
+
) {
|
|
125
|
+
colorway = props.interactive.split("_") as [Color, ColorStyle];
|
|
126
|
+
} else {
|
|
127
|
+
interactive = props.interactive as InteractiveStyle;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let loading: LoadingVariant | null = null;
|
|
132
|
+
if (props.loading) {
|
|
133
|
+
loading = typeof props.loading === "boolean" ? "foreground" : props.loading;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { interactive, colorway, loading };
|
|
137
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
colorway,
|
|
5
|
+
interactiveStyles,
|
|
6
|
+
surfaceBackgrounds,
|
|
7
|
+
} from "~/stories/data";
|
|
8
|
+
|
|
9
|
+
import { View, type ViewProps } from ".";
|
|
10
|
+
import { tokens } from "../../styles/tokens";
|
|
11
|
+
import { Surface } from "../surface";
|
|
12
|
+
import { Text } from "../text";
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: "View",
|
|
16
|
+
component: View,
|
|
17
|
+
parameters: { layout: "centered" },
|
|
18
|
+
argTypes: {
|
|
19
|
+
interactive: {
|
|
20
|
+
control: "select",
|
|
21
|
+
options: [...interactiveStyles, ...colorway],
|
|
22
|
+
},
|
|
23
|
+
loading: {
|
|
24
|
+
control: "boolean",
|
|
25
|
+
},
|
|
26
|
+
"aria-disabled": {
|
|
27
|
+
control: "boolean",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
} satisfies Meta<ViewProps & { "aria-disabled"?: boolean }>;
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Interactive: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
interactive: "fill",
|
|
39
|
+
loading: false,
|
|
40
|
+
},
|
|
41
|
+
render: (args) => (
|
|
42
|
+
<View
|
|
43
|
+
{...args}
|
|
44
|
+
style={{
|
|
45
|
+
paddingInline: tokens.space16,
|
|
46
|
+
height: tokens.space32,
|
|
47
|
+
justifyContent: "center",
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
Hello world
|
|
51
|
+
</View>
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Colorway: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
interactive: "primary_fill",
|
|
58
|
+
loading: false,
|
|
59
|
+
},
|
|
60
|
+
argTypes: {
|
|
61
|
+
interactive: {
|
|
62
|
+
table: {
|
|
63
|
+
disable: true,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
render: (args) => (
|
|
68
|
+
<View
|
|
69
|
+
{...args}
|
|
70
|
+
style={{
|
|
71
|
+
paddingInline: tokens.space16,
|
|
72
|
+
height: tokens.space32,
|
|
73
|
+
justifyContent: "center",
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
Hello world
|
|
77
|
+
</View>
|
|
78
|
+
),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const AllInteractiveStyles: Story = {
|
|
82
|
+
argTypes: {
|
|
83
|
+
interactive: {
|
|
84
|
+
table: {
|
|
85
|
+
disable: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
args: {
|
|
90
|
+
loading: false,
|
|
91
|
+
},
|
|
92
|
+
render: (args) => (
|
|
93
|
+
<View
|
|
94
|
+
style={{
|
|
95
|
+
flexDirection: "row",
|
|
96
|
+
flexWrap: "wrap",
|
|
97
|
+
gap: tokens.space8,
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{surfaceBackgrounds.map((background) => (
|
|
101
|
+
<Surface
|
|
102
|
+
style={{
|
|
103
|
+
border: `1px solid ${tokens.outlineDimmest}`,
|
|
104
|
+
padding: tokens.space16,
|
|
105
|
+
width: tokens.space256,
|
|
106
|
+
gap: tokens.space6,
|
|
107
|
+
}}
|
|
108
|
+
key={background}
|
|
109
|
+
background={background}
|
|
110
|
+
>
|
|
111
|
+
<p style={{ fontWeight: 500 }}>{background}</p>
|
|
112
|
+
|
|
113
|
+
{interactiveStyles.map((variant) => (
|
|
114
|
+
<View
|
|
115
|
+
key={variant}
|
|
116
|
+
interactive={variant}
|
|
117
|
+
style={{
|
|
118
|
+
paddingInline: tokens.space16,
|
|
119
|
+
height: tokens.space32,
|
|
120
|
+
justifyContent: "center",
|
|
121
|
+
}}
|
|
122
|
+
{...args}
|
|
123
|
+
>
|
|
124
|
+
<Text>{variant}</Text>
|
|
125
|
+
</View>
|
|
126
|
+
))}
|
|
127
|
+
</Surface>
|
|
128
|
+
))}
|
|
129
|
+
</View>
|
|
130
|
+
),
|
|
131
|
+
};
|