@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,74 @@
|
|
|
1
|
+
import { NumberField } from "@base-ui/react/number-field";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
|
|
5
|
+
import { IconButton } from "../icon-button";
|
|
6
|
+
import { Input } from "../input";
|
|
7
|
+
import { Separator } from "../separator";
|
|
8
|
+
import { Text } from "../text";
|
|
9
|
+
import { View } from "../view";
|
|
10
|
+
|
|
11
|
+
import styles from "./number-input.module.css";
|
|
12
|
+
|
|
13
|
+
// TODO find icon for scrub area
|
|
14
|
+
|
|
15
|
+
export type NumberInputProps = NumberField.Root.Props & {
|
|
16
|
+
/** Optional label, includes scrub area */
|
|
17
|
+
label?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function NumberInput({
|
|
21
|
+
label,
|
|
22
|
+
/** Apply className to NumberField.Input */
|
|
23
|
+
className,
|
|
24
|
+
/** Apply style to NumberField.Input */
|
|
25
|
+
style,
|
|
26
|
+
...props
|
|
27
|
+
}: NumberInputProps) {
|
|
28
|
+
const defaultId = useId();
|
|
29
|
+
const id = props.id || defaultId;
|
|
30
|
+
return (
|
|
31
|
+
<NumberField.Root
|
|
32
|
+
id={id}
|
|
33
|
+
className={styles["number-input__root"]}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{label ? (
|
|
37
|
+
<NumberField.ScrubArea className={styles["number-input__scrub-area"]}>
|
|
38
|
+
<label htmlFor={id}>
|
|
39
|
+
<Text color="dimmer">{label}</Text>
|
|
40
|
+
</label>
|
|
41
|
+
</NumberField.ScrubArea>
|
|
42
|
+
) : null}
|
|
43
|
+
|
|
44
|
+
<NumberField.Group className={styles["number-input__group"]}>
|
|
45
|
+
<Input
|
|
46
|
+
render={
|
|
47
|
+
<NumberField.Input
|
|
48
|
+
className={clsx(styles["number-input"], className)}
|
|
49
|
+
style={style}
|
|
50
|
+
/>
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
<View className={styles["number-input__controls"]}>
|
|
54
|
+
<IconButton
|
|
55
|
+
size="sm"
|
|
56
|
+
alt="Decrement"
|
|
57
|
+
icon="subtract-line"
|
|
58
|
+
render={<NumberField.Decrement />}
|
|
59
|
+
/>
|
|
60
|
+
<Separator
|
|
61
|
+
orientation="vertical"
|
|
62
|
+
className={styles["number-input__separator"]}
|
|
63
|
+
/>
|
|
64
|
+
<IconButton
|
|
65
|
+
size="sm"
|
|
66
|
+
alt="Increment"
|
|
67
|
+
icon="add-line"
|
|
68
|
+
render={<NumberField.Increment />}
|
|
69
|
+
/>
|
|
70
|
+
</View>
|
|
71
|
+
</NumberField.Group>
|
|
72
|
+
</NumberField.Root>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.number-input__root {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--space-2);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
https://stackoverflow.com/questions/72789466/remove-the-up-down-increment-decrement-buttons-on-number-inputs-using-css
|
|
9
|
+
Hide increment/decrement buttons on input
|
|
10
|
+
*/
|
|
11
|
+
.number-input[type="number"]::-webkit-inner-spin-button,
|
|
12
|
+
.number-input[type="number"]::-webkit-outer-spin-button {
|
|
13
|
+
/* stylelint-disable-next-line */
|
|
14
|
+
-webkit-appearance: none;
|
|
15
|
+
padding-right: var(--space-32);
|
|
16
|
+
text-overflow: ellipsis;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.number-input__scrub-area {
|
|
20
|
+
cursor: ew-resize;
|
|
21
|
+
user-select: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.number-input__group {
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.number-input__controls {
|
|
29
|
+
display: flex;
|
|
30
|
+
position: absolute;
|
|
31
|
+
top: 50%;
|
|
32
|
+
right: var(--space-4);
|
|
33
|
+
flex-direction: row;
|
|
34
|
+
align-items: center;
|
|
35
|
+
gap: var(--space-4);
|
|
36
|
+
transform: translateY(-50%);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.number-input__separator {
|
|
40
|
+
height: var(--space-16);
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import { NumberInput } from ".";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Input/Number",
|
|
9
|
+
component: NumberInput,
|
|
10
|
+
parameters: { layout: "centered" },
|
|
11
|
+
} satisfies Meta<typeof NumberInput>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
label: "Amount",
|
|
20
|
+
},
|
|
21
|
+
render: (args) => (
|
|
22
|
+
<NumberInput {...args} style={{ width: tokens.space256 }} />
|
|
23
|
+
),
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { IconButton } from "../icon-button";
|
|
4
|
+
import { Input, type InputProps } from "../input";
|
|
5
|
+
import { View } from "../view";
|
|
6
|
+
|
|
7
|
+
import styles from "./password-input.module.css";
|
|
8
|
+
|
|
9
|
+
export function PasswordInput(props: Omit<InputProps, "type">) {
|
|
10
|
+
const [show, setShow] = useState(false);
|
|
11
|
+
return (
|
|
12
|
+
<View className={styles["password-input__root"]}>
|
|
13
|
+
<Input type={show ? "text" : "password"} {...props} />
|
|
14
|
+
<IconButton
|
|
15
|
+
alt={show ? "Hide password" : "Show password"}
|
|
16
|
+
icon={show ? "eye-line" : "eye-close-line"}
|
|
17
|
+
className={styles["password-input__eye"]}
|
|
18
|
+
type="button"
|
|
19
|
+
size="sm"
|
|
20
|
+
onClick={() => setShow((prevShow) => !prevShow)}
|
|
21
|
+
/>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import { PasswordInput } from ".";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Input/Password",
|
|
9
|
+
component: PasswordInput,
|
|
10
|
+
parameters: { layout: "centered" },
|
|
11
|
+
} satisfies Meta<typeof PasswordInput>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
placeholder: "Password",
|
|
20
|
+
},
|
|
21
|
+
render: (args) => (
|
|
22
|
+
<PasswordInput {...args} style={{ width: tokens.space256 }} />
|
|
23
|
+
),
|
|
24
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
|
|
3
|
+
import { textify } from "~/-utils";
|
|
4
|
+
|
|
5
|
+
import { Icon } from "../icon";
|
|
6
|
+
import type { IconName } from "../icon/icon-names";
|
|
7
|
+
import { View, type ViewProps } from "../view";
|
|
8
|
+
|
|
9
|
+
import styles from "./pill.module.css";
|
|
10
|
+
|
|
11
|
+
export type PillProps = ViewProps & {
|
|
12
|
+
/**
|
|
13
|
+
* Icon to display on the left side of the pill content
|
|
14
|
+
*/
|
|
15
|
+
leftIcon?: IconName;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icon to display on the right side of the pill content
|
|
19
|
+
*/
|
|
20
|
+
rightIcon?: IconName;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function Pill({
|
|
24
|
+
leftIcon,
|
|
25
|
+
rightIcon,
|
|
26
|
+
className,
|
|
27
|
+
children,
|
|
28
|
+
...props
|
|
29
|
+
}: PillProps) {
|
|
30
|
+
return (
|
|
31
|
+
<View
|
|
32
|
+
className={clsx(
|
|
33
|
+
styles["pill"],
|
|
34
|
+
leftIcon && styles["pill_icon_left"],
|
|
35
|
+
rightIcon && styles["pill_icon_right"],
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
{leftIcon ? <Icon name={leftIcon} /> : null}
|
|
41
|
+
{textify(children, { color: "inherit" })}
|
|
42
|
+
{rightIcon ? <Icon name={rightIcon} /> : null}
|
|
43
|
+
</View>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.pill {
|
|
2
|
+
flex-shrink: 1;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: var(--space-4);
|
|
7
|
+
border-radius: var(--border-radius-round) !important;
|
|
8
|
+
padding-right: var(--space-8);
|
|
9
|
+
padding-left: var(--space-8);
|
|
10
|
+
height: var(--space-24);
|
|
11
|
+
font-size: var(--font-size-small);
|
|
12
|
+
user-select: none;
|
|
13
|
+
white-space: nowrap;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.pill_icon_left {
|
|
17
|
+
padding-left: var(--space-4);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.pill_icon_right {
|
|
21
|
+
padding-right: var(--space-4);
|
|
22
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { colors, colorway, interactiveStyles } from "~/stories/data";
|
|
4
|
+
import { disable } from "~/stories/utils";
|
|
5
|
+
import { tokens } from "~/styles/tokens";
|
|
6
|
+
|
|
7
|
+
import { Pill } from ".";
|
|
8
|
+
import { Text } from "../text";
|
|
9
|
+
import { View } from "../view";
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: "Pill",
|
|
13
|
+
component: Pill,
|
|
14
|
+
parameters: { layout: "centered" },
|
|
15
|
+
argTypes: {
|
|
16
|
+
...disable(["color", "loading"]),
|
|
17
|
+
interactive: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: [...interactiveStyles, ...colorway],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof Pill>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
interactive: "primary_mute-static",
|
|
31
|
+
leftIcon: "time-line",
|
|
32
|
+
},
|
|
33
|
+
render: (args) => <Pill {...args} children="Beginner friendly" />,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const AllVariants: Story = {
|
|
37
|
+
argTypes: {
|
|
38
|
+
...disable(["interactive", "color", "loading"]),
|
|
39
|
+
},
|
|
40
|
+
render: () => (
|
|
41
|
+
<View style={{ gap: tokens.space8 }}>
|
|
42
|
+
<View
|
|
43
|
+
style={{
|
|
44
|
+
flexDirection: "row",
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
gap: tokens.space8,
|
|
47
|
+
width: "100%",
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Pill interactive="fill">#python</Pill>
|
|
51
|
+
<Pill interactive="outline">#game</Pill>
|
|
52
|
+
<Text size="sm">interactive</Text>
|
|
53
|
+
</View>
|
|
54
|
+
|
|
55
|
+
{colors.map((color) => (
|
|
56
|
+
<View
|
|
57
|
+
style={{
|
|
58
|
+
flexDirection: "row",
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
gap: tokens.space8,
|
|
61
|
+
}}
|
|
62
|
+
key={color}
|
|
63
|
+
>
|
|
64
|
+
<Pill interactive={`${color}_fill`} render={<button />}>
|
|
65
|
+
#python
|
|
66
|
+
</Pill>
|
|
67
|
+
|
|
68
|
+
<Pill interactive={`${color}_fill-static`}>#game</Pill>
|
|
69
|
+
|
|
70
|
+
<Pill leftIcon="time-line" interactive={`${color}_mute-static`}>
|
|
71
|
+
Beginner friendly
|
|
72
|
+
</Pill>
|
|
73
|
+
|
|
74
|
+
<Pill rightIcon="star-line" interactive={`${color}_outline-static`}>
|
|
75
|
+
Beginner friendly
|
|
76
|
+
</Pill>
|
|
77
|
+
|
|
78
|
+
<Text size="sm">{color}</Text>
|
|
79
|
+
</View>
|
|
80
|
+
))}
|
|
81
|
+
</View>
|
|
82
|
+
),
|
|
83
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "@base-ui/react/popover";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ReactElement, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { Heading } from "../heading";
|
|
6
|
+
import { Surface, type Background } from "../surface";
|
|
7
|
+
import { Text } from "../text";
|
|
8
|
+
import { TooltipArrow } from "../tooltip/tooltip-arrow";
|
|
9
|
+
import { View } from "../view";
|
|
10
|
+
|
|
11
|
+
import transitionStyles from "../../styles/transitions.module.css";
|
|
12
|
+
import tooltipStyles from "../tooltip/tooltip.module.css";
|
|
13
|
+
import styles from "./popover.module.css";
|
|
14
|
+
|
|
15
|
+
type PopoverRootProps = Omit<PopoverPrimitive.Root.Props, "children">;
|
|
16
|
+
type PopoverPositionerProps = Pick<
|
|
17
|
+
PopoverPrimitive.Positioner.Props,
|
|
18
|
+
"align" | "side"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export type PopoverProps = PopoverRootProps &
|
|
22
|
+
PopoverPositionerProps & {
|
|
23
|
+
/** Optional title description. */
|
|
24
|
+
title?: string;
|
|
25
|
+
|
|
26
|
+
/** Optional popover description. */
|
|
27
|
+
description?: string;
|
|
28
|
+
|
|
29
|
+
/** Specify trigger to open popover. */
|
|
30
|
+
trigger?: ReactElement;
|
|
31
|
+
|
|
32
|
+
/** Surface background popover should use. Defaults to "root". */
|
|
33
|
+
background?: Background;
|
|
34
|
+
|
|
35
|
+
/** Apply className to PopoverPrimitive.Popup */
|
|
36
|
+
className?: string;
|
|
37
|
+
|
|
38
|
+
/** Popover content */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function Popover({
|
|
43
|
+
align,
|
|
44
|
+
side,
|
|
45
|
+
title,
|
|
46
|
+
description,
|
|
47
|
+
background = "root",
|
|
48
|
+
trigger,
|
|
49
|
+
className,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: PopoverProps) {
|
|
53
|
+
return (
|
|
54
|
+
<PopoverPrimitive.Root {...props}>
|
|
55
|
+
{trigger ? <PopoverPrimitive.Trigger render={trigger} /> : null}
|
|
56
|
+
<PopoverPrimitive.Portal>
|
|
57
|
+
<PopoverPrimitive.Positioner align={align} side={side} sideOffset={8}>
|
|
58
|
+
<PopoverPrimitive.Popup
|
|
59
|
+
render={<Surface background={background} />}
|
|
60
|
+
className={clsx(
|
|
61
|
+
tooltipStyles["tooltip"],
|
|
62
|
+
styles["popover"],
|
|
63
|
+
transitionStyles["transition_scale"],
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<PopoverPrimitive.Arrow className={tooltipStyles["tooltip__arrow"]}>
|
|
68
|
+
<TooltipArrow />
|
|
69
|
+
</PopoverPrimitive.Arrow>
|
|
70
|
+
{title || description ? (
|
|
71
|
+
<View className={styles["popover__header"]}>
|
|
72
|
+
{title ? (
|
|
73
|
+
<PopoverPrimitive.Title
|
|
74
|
+
render={<Heading level={2} size="2xl" />}
|
|
75
|
+
>
|
|
76
|
+
{title}
|
|
77
|
+
</PopoverPrimitive.Title>
|
|
78
|
+
) : null}
|
|
79
|
+
{description ? (
|
|
80
|
+
<PopoverPrimitive.Description
|
|
81
|
+
render={<Text color="dimmer" />}
|
|
82
|
+
>
|
|
83
|
+
{description}
|
|
84
|
+
</PopoverPrimitive.Description>
|
|
85
|
+
) : null}
|
|
86
|
+
</View>
|
|
87
|
+
) : null}
|
|
88
|
+
{children}
|
|
89
|
+
</PopoverPrimitive.Popup>
|
|
90
|
+
</PopoverPrimitive.Positioner>
|
|
91
|
+
</PopoverPrimitive.Portal>
|
|
92
|
+
</PopoverPrimitive.Root>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { surfaceBackgrounds } from "~/stories/data";
|
|
4
|
+
import { disable } from "~/stories/utils";
|
|
5
|
+
|
|
6
|
+
import { Popover } from ".";
|
|
7
|
+
import { IconButton } from "../icon-button";
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: "Popover",
|
|
11
|
+
component: Popover,
|
|
12
|
+
parameters: { layout: "centered" },
|
|
13
|
+
argTypes: {
|
|
14
|
+
align: {
|
|
15
|
+
control: "inline-radio",
|
|
16
|
+
options: ["start", "center", "end"],
|
|
17
|
+
},
|
|
18
|
+
side: {
|
|
19
|
+
control: "select",
|
|
20
|
+
options: ["top", "bottom", "left", "right"],
|
|
21
|
+
},
|
|
22
|
+
background: {
|
|
23
|
+
control: "select",
|
|
24
|
+
options: surfaceBackgrounds,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
} satisfies Meta<typeof Popover>;
|
|
28
|
+
|
|
29
|
+
export default meta;
|
|
30
|
+
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Default: Story = {
|
|
34
|
+
argTypes: {
|
|
35
|
+
...disable(["trigger", "children", "className"]),
|
|
36
|
+
},
|
|
37
|
+
args: {
|
|
38
|
+
title: "Notifications",
|
|
39
|
+
description: "You're all caught up!",
|
|
40
|
+
align: "center",
|
|
41
|
+
side: "bottom",
|
|
42
|
+
},
|
|
43
|
+
render: (args) => (
|
|
44
|
+
<Popover
|
|
45
|
+
trigger={
|
|
46
|
+
<IconButton icon="notification-line" alt="Notifications" interactive />
|
|
47
|
+
}
|
|
48
|
+
{...args}
|
|
49
|
+
/>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// TODO notifications story (unfortunately we need button group (tabs) b4 we do this)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ReactElement, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { Surface, type Background } from "../surface";
|
|
6
|
+
import { TooltipArrow } from "../tooltip/tooltip-arrow";
|
|
7
|
+
|
|
8
|
+
import transitionStyles from "../../styles/transitions.module.css";
|
|
9
|
+
import tooltipStyles from "../tooltip/tooltip.module.css";
|
|
10
|
+
import styles from "./preview-card.module.css";
|
|
11
|
+
|
|
12
|
+
export type PreviewCardProps = PreviewCardPrimitive.Root.Props &
|
|
13
|
+
Pick<PreviewCardPrimitive.Positioner.Props, "align" | "side"> & {
|
|
14
|
+
/**
|
|
15
|
+
* Specify trigger to open preview.
|
|
16
|
+
* You should use an Anchor component.
|
|
17
|
+
*/
|
|
18
|
+
trigger?: ReactElement;
|
|
19
|
+
|
|
20
|
+
/** Surface background preview card should use. Defaults to "root". */
|
|
21
|
+
background?: Background;
|
|
22
|
+
|
|
23
|
+
/** Apply styles to preview card content. */
|
|
24
|
+
className?: string;
|
|
25
|
+
|
|
26
|
+
/** Preview card content */
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function PreviewCard({
|
|
31
|
+
align,
|
|
32
|
+
side,
|
|
33
|
+
background,
|
|
34
|
+
trigger,
|
|
35
|
+
children,
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: PreviewCardProps) {
|
|
39
|
+
return (
|
|
40
|
+
<PreviewCardPrimitive.Root {...props}>
|
|
41
|
+
{trigger ? <PreviewCardPrimitive.Trigger render={trigger} /> : null}
|
|
42
|
+
<PreviewCardPrimitive.Portal>
|
|
43
|
+
<PreviewCardPrimitive.Positioner
|
|
44
|
+
align={align}
|
|
45
|
+
side={side}
|
|
46
|
+
sideOffset={8}
|
|
47
|
+
>
|
|
48
|
+
<PreviewCardPrimitive.Popup
|
|
49
|
+
render={<Surface background={background} />}
|
|
50
|
+
className={clsx(
|
|
51
|
+
tooltipStyles["tooltip"],
|
|
52
|
+
styles["preview-card"],
|
|
53
|
+
transitionStyles["transition_scale"],
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<PreviewCardPrimitive.Arrow
|
|
58
|
+
className={tooltipStyles["tooltip__arrow"]}
|
|
59
|
+
>
|
|
60
|
+
<TooltipArrow />
|
|
61
|
+
</PreviewCardPrimitive.Arrow>
|
|
62
|
+
{children}
|
|
63
|
+
</PreviewCardPrimitive.Popup>
|
|
64
|
+
</PreviewCardPrimitive.Positioner>
|
|
65
|
+
</PreviewCardPrimitive.Portal>
|
|
66
|
+
</PreviewCardPrimitive.Root>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { surfaceBackgrounds } from "~/stories/data";
|
|
4
|
+
import { disable } from "~/stories/utils";
|
|
5
|
+
import { tokens } from "~/styles/tokens";
|
|
6
|
+
|
|
7
|
+
import { PreviewCard } from ".";
|
|
8
|
+
import { Anchor } from "../anchor";
|
|
9
|
+
import { Text } from "../text";
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: "Preview Card",
|
|
13
|
+
component: PreviewCard,
|
|
14
|
+
parameters: { layout: "centered" },
|
|
15
|
+
argTypes: {
|
|
16
|
+
...disable(["trigger", "className", "children"]),
|
|
17
|
+
background: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: surfaceBackgrounds,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof PreviewCard>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
background: "highest",
|
|
31
|
+
},
|
|
32
|
+
render: (args) => (
|
|
33
|
+
<Text>
|
|
34
|
+
The principles of good{" "}
|
|
35
|
+
<PreviewCard {...args} trigger={<Anchor>typography</Anchor>}>
|
|
36
|
+
<img
|
|
37
|
+
width="448"
|
|
38
|
+
height="300"
|
|
39
|
+
src="https://images.unsplash.com/photo-1619615391095-dfa29e1672ef?q=80&w=448&h=300"
|
|
40
|
+
alt="Station Hofplein signage in Rotterdam, Netherlands"
|
|
41
|
+
style={{
|
|
42
|
+
borderRadius: tokens.borderRadiusDefault,
|
|
43
|
+
width: "100%",
|
|
44
|
+
height: "auto",
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
<Text style={{ flex: "1 1 auto" }} multiline>
|
|
48
|
+
<strong style={{ fontWeight: tokens.fontWeightMedium }}>
|
|
49
|
+
Typography
|
|
50
|
+
</strong>{" "}
|
|
51
|
+
is the art and science of arranging type to make written language
|
|
52
|
+
clear, visually appealing, and effective in communication.
|
|
53
|
+
</Text>
|
|
54
|
+
</PreviewCard>{" "}
|
|
55
|
+
remain into the digital age.
|
|
56
|
+
</Text>
|
|
57
|
+
),
|
|
58
|
+
};
|