@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,36 @@
|
|
|
1
|
+
.icon {
|
|
2
|
+
--icon-size: var(--space-16);
|
|
3
|
+
|
|
4
|
+
vertical-align: middle;
|
|
5
|
+
width: var(--icon-size);
|
|
6
|
+
min-width: var(--icon-size);
|
|
7
|
+
height: var(--icon-size);
|
|
8
|
+
min-height: var(--icon-size);
|
|
9
|
+
font-size: var(--icon-size);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Maximize performance by declaring styles instead of inline styles */
|
|
13
|
+
.icon_size_sm {
|
|
14
|
+
/* TODO revisit this - we kind of depend on icons to be at least 16 px, so maybe expose an "xs" size? idk */
|
|
15
|
+
--icon-size: var(--space-16);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.icon_size_md {
|
|
19
|
+
--icon-size: var(--space-16);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.icon_size_lg {
|
|
23
|
+
--icon-size: var(--space-20);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.icon_size_xl {
|
|
27
|
+
--icon-size: var(--space-24);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.icon_size_2xl {
|
|
31
|
+
--icon-size: var(--space-32);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.icon_size_3xl {
|
|
35
|
+
--icon-size: var(--space-48);
|
|
36
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { colorTokens, tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import { Icon } from ".";
|
|
6
|
+
|
|
7
|
+
const iconSizeOptions = ["sm", "md", "lg", "xl", "2xl", "3xl"] as const;
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: "Icon",
|
|
11
|
+
component: Icon,
|
|
12
|
+
parameters: { layout: "centered" },
|
|
13
|
+
argTypes: {
|
|
14
|
+
color: {
|
|
15
|
+
control: "select",
|
|
16
|
+
options: Object.values(colorTokens),
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
control: "select",
|
|
20
|
+
options: iconSizeOptions,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
} satisfies Meta<typeof Icon>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
name: "heart-fill",
|
|
32
|
+
color: tokens.primaryDefault,
|
|
33
|
+
size: "md",
|
|
34
|
+
},
|
|
35
|
+
render: (props) => (
|
|
36
|
+
<div>
|
|
37
|
+
<Icon {...props} />
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { type ComponentProps } from "react";
|
|
3
|
+
import svgSprite from "remixicon/fonts/remixicon.symbol.svg";
|
|
4
|
+
|
|
5
|
+
import { type ColorToken, type Size } from "~/styles/tokens";
|
|
6
|
+
|
|
7
|
+
import { type IconName } from "./icon-names";
|
|
8
|
+
|
|
9
|
+
import styles from "./icon.module.css";
|
|
10
|
+
|
|
11
|
+
export interface IconProps extends Omit<ComponentProps<"svg">, "name"> {
|
|
12
|
+
/**
|
|
13
|
+
* Overrides the icon size provider.
|
|
14
|
+
* Defaults to "md".
|
|
15
|
+
*/
|
|
16
|
+
size?: Size;
|
|
17
|
+
|
|
18
|
+
/** Icon name */
|
|
19
|
+
name: IconName;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Icon fill color
|
|
23
|
+
* You probably should provide a color token, but we allow any color
|
|
24
|
+
*/
|
|
25
|
+
color?: ColorToken | (string & {});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Use like `img alt` attribute.
|
|
29
|
+
* Leave empty if icon is purely decorative.
|
|
30
|
+
*/
|
|
31
|
+
alt?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Icon({
|
|
35
|
+
size = "md",
|
|
36
|
+
name,
|
|
37
|
+
color,
|
|
38
|
+
alt,
|
|
39
|
+
className,
|
|
40
|
+
...props
|
|
41
|
+
}: IconProps) {
|
|
42
|
+
const href = `${svgSprite}#ri-${name}`;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<svg
|
|
46
|
+
preserveAspectRatio="xMidYMin"
|
|
47
|
+
viewBox="0 0 24 24"
|
|
48
|
+
aria-hidden={!alt}
|
|
49
|
+
focusable={false}
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
fill={color || "currentColor"}
|
|
52
|
+
className={clsx(styles["icon"], styles[`icon_size_${size}`], className)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{/* https://css-tricks.com/accessible-svg-icons/ */}
|
|
56
|
+
{alt ? <title>{alt}</title> : null}
|
|
57
|
+
<use xlinkHref={href} href={href} />
|
|
58
|
+
</svg>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.icon-button {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: center;
|
|
4
|
+
align-items: center;
|
|
5
|
+
border-radius: var(--border-radius-default);
|
|
6
|
+
aspect-ratio: 1 / 1;
|
|
7
|
+
width: var(--icon-button-size);
|
|
8
|
+
height: var(--icon-button-size);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.icon-button_size_sm {
|
|
12
|
+
--icon-button-size: var(--space-24);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.icon-button_size_md {
|
|
16
|
+
--icon-button-size: var(--space-32);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.icon-button_size_lg {
|
|
20
|
+
--icon-button-size: var(--space-40);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.icon-button_size_xl {
|
|
24
|
+
--icon-button-size: var(--space-48);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.icon-button_size_2xl {
|
|
28
|
+
--icon-button-size: var(--space-56);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.icon-button_size_3xl {
|
|
32
|
+
--icon-button-size: var(--space-64);
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { AllVariants } from "~/stories/components/all-variants";
|
|
4
|
+
import { sizes } from "~/stories/data";
|
|
5
|
+
|
|
6
|
+
import { IconButton } from ".";
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "IconButton",
|
|
10
|
+
component: IconButton,
|
|
11
|
+
parameters: { layout: "centered" },
|
|
12
|
+
argTypes: {
|
|
13
|
+
size: {
|
|
14
|
+
control: "select",
|
|
15
|
+
options: sizes,
|
|
16
|
+
},
|
|
17
|
+
interactive: {
|
|
18
|
+
control: "boolean",
|
|
19
|
+
},
|
|
20
|
+
loading: {
|
|
21
|
+
control: "boolean",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof IconButton>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof meta>;
|
|
29
|
+
|
|
30
|
+
export const Default: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
size: "md",
|
|
33
|
+
icon: "add-line",
|
|
34
|
+
alt: "Create a new project.",
|
|
35
|
+
interactive: true,
|
|
36
|
+
loading: false,
|
|
37
|
+
},
|
|
38
|
+
render: (args) => <IconButton {...args} />,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const AllSizes: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
icon: "add-line",
|
|
44
|
+
alt: "Create a new project.",
|
|
45
|
+
},
|
|
46
|
+
argTypes: {
|
|
47
|
+
size: {
|
|
48
|
+
control: false,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
render: (props) => (
|
|
52
|
+
<AllVariants
|
|
53
|
+
variantName="size"
|
|
54
|
+
variants={sizes}
|
|
55
|
+
element={<IconButton interactive {...props} />}
|
|
56
|
+
style={{ flexDirection: "row" }}
|
|
57
|
+
/>
|
|
58
|
+
),
|
|
59
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
import { type Size } from "~/styles/tokens";
|
|
5
|
+
|
|
6
|
+
import { Icon } from "../icon";
|
|
7
|
+
import type { IconName } from "../icon/icon-names";
|
|
8
|
+
import { View, type ViewProps } from "../view";
|
|
9
|
+
|
|
10
|
+
import styles from "./icon-button.module.css";
|
|
11
|
+
|
|
12
|
+
export type IconButtonProps = ViewProps<"button"> & {
|
|
13
|
+
/** Icon to display. */
|
|
14
|
+
icon: IconName;
|
|
15
|
+
|
|
16
|
+
/** Explain what the button does. Required for accessibility. */
|
|
17
|
+
alt: string;
|
|
18
|
+
|
|
19
|
+
/** What size should the IconButton be. Defaults to "md". */
|
|
20
|
+
size?: Size;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function IconButton({
|
|
24
|
+
icon,
|
|
25
|
+
alt,
|
|
26
|
+
size = "md",
|
|
27
|
+
render,
|
|
28
|
+
...props
|
|
29
|
+
}: IconButtonProps) {
|
|
30
|
+
return (
|
|
31
|
+
<View<"button">
|
|
32
|
+
interactive={true}
|
|
33
|
+
render={
|
|
34
|
+
<ButtonPrimitive
|
|
35
|
+
className={clsx(
|
|
36
|
+
styles["icon-button"],
|
|
37
|
+
styles[`icon-button_size_${size}`],
|
|
38
|
+
)}
|
|
39
|
+
focusableWhenDisabled={!!props.loading}
|
|
40
|
+
render={render}
|
|
41
|
+
/>
|
|
42
|
+
}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
<Icon name={icon} size={size} alt={alt} />
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { useRender } from "@base-ui/react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
import { Surface } from "../surface";
|
|
5
|
+
import { Text } from "../text";
|
|
6
|
+
import type { Color } from "../view";
|
|
7
|
+
|
|
8
|
+
import styles from "./inline-code.module.css";
|
|
9
|
+
|
|
10
|
+
export interface InlineCodeProps extends useRender.ComponentProps<"span"> {
|
|
11
|
+
color?: Color;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const InlineCode = ({ color, ...props }: InlineCodeProps) => {
|
|
15
|
+
return (
|
|
16
|
+
<Surface
|
|
17
|
+
elevated
|
|
18
|
+
className={clsx(
|
|
19
|
+
styles["inline-code"],
|
|
20
|
+
color && styles["inline-code_colorway"],
|
|
21
|
+
)}
|
|
22
|
+
color={color}
|
|
23
|
+
>
|
|
24
|
+
<Text size="sm" color="inherit">
|
|
25
|
+
<code {...props} />
|
|
26
|
+
</Text>
|
|
27
|
+
</Surface>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.inline-code {
|
|
2
|
+
display: inline;
|
|
3
|
+
border: 1px solid var(--surface-interactive-border);
|
|
4
|
+
border-radius: var(--border-radius-6);
|
|
5
|
+
padding: 0 var(--space-4);
|
|
6
|
+
line-height: var(--line-height-default);
|
|
7
|
+
font-family: var(--font-family-code);
|
|
8
|
+
overflow-wrap: anywhere;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.inline-code_colorway {
|
|
12
|
+
color: var(--view-colorway-default);
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import { InlineCode } from ".";
|
|
6
|
+
import { Surface } from "../surface";
|
|
7
|
+
import { Text } from "../text";
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: "Code/Inline Code",
|
|
11
|
+
component: InlineCode,
|
|
12
|
+
parameters: { layout: "centered" },
|
|
13
|
+
} satisfies Meta<typeof InlineCode>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
color: undefined,
|
|
22
|
+
},
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<Surface style={{ padding: tokens.space16 }} elevated>
|
|
25
|
+
<Text>
|
|
26
|
+
Import the library with{" "}
|
|
27
|
+
<InlineCode {...args}>import * from "natmfat"</InlineCode>
|
|
28
|
+
</Text>
|
|
29
|
+
</Surface>
|
|
30
|
+
),
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
import { View } from "../view";
|
|
5
|
+
|
|
6
|
+
import styles from "./input.module.css";
|
|
7
|
+
|
|
8
|
+
export type InputProps = InputPrimitive.Props;
|
|
9
|
+
|
|
10
|
+
export function Input({ className, ...props }: InputProps) {
|
|
11
|
+
return (
|
|
12
|
+
<View
|
|
13
|
+
interactive="fill-outline"
|
|
14
|
+
render={
|
|
15
|
+
<InputPrimitive
|
|
16
|
+
className={clsx(styles["input"], className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.input {
|
|
2
|
+
flex: 1 1 0%;
|
|
3
|
+
outline: none;
|
|
4
|
+
padding: var(--space-4) var(--space-8);
|
|
5
|
+
height: var(--space-32);
|
|
6
|
+
min-height: var(--space-32);
|
|
7
|
+
font-size: var(--font-size-default);
|
|
8
|
+
line-height: var(--line-height-input);
|
|
9
|
+
font-family: var(--font-family-default);
|
|
10
|
+
|
|
11
|
+
&::placeholder {
|
|
12
|
+
color: var(--foreground-dimmest);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&:not([disabled], [data-disabled]) {
|
|
16
|
+
cursor: text;
|
|
17
|
+
color: var(--foreground-default);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.input_autosize {
|
|
22
|
+
resize: none;
|
|
23
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { Input } from ".";
|
|
8
|
+
import { Surface } from "../surface";
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Input/Text",
|
|
12
|
+
component: Input,
|
|
13
|
+
parameters: { layout: "centered" },
|
|
14
|
+
} satisfies Meta<typeof Input>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {
|
|
21
|
+
args: {
|
|
22
|
+
placeholder: "Hello World",
|
|
23
|
+
disabled: false,
|
|
24
|
+
},
|
|
25
|
+
render: (args) => <Input {...args} style={{ width: tokens.space256 }} />,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const AllInteractiveStyles: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
placeholder: "Hello World",
|
|
31
|
+
disabled: false,
|
|
32
|
+
},
|
|
33
|
+
render: (args) => (
|
|
34
|
+
<AllVariants
|
|
35
|
+
variantName="background"
|
|
36
|
+
variants={surfaceBackgrounds}
|
|
37
|
+
style={{ backgroundColor: "transparent" }}
|
|
38
|
+
element={
|
|
39
|
+
<Surface
|
|
40
|
+
style={{
|
|
41
|
+
padding: tokens.space16,
|
|
42
|
+
gap: tokens.space8,
|
|
43
|
+
borderWidth: "1px",
|
|
44
|
+
borderColor: tokens.outlineDimmest,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<Input {...args} />
|
|
48
|
+
</Surface>
|
|
49
|
+
}
|
|
50
|
+
/>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Meter as MeterPrimitive } from "@base-ui/react/meter";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
import { Text } from "../text";
|
|
5
|
+
import { View, type Color } from "../view";
|
|
6
|
+
|
|
7
|
+
import styles from "./meter.module.css";
|
|
8
|
+
|
|
9
|
+
export type MeterProps = MeterPrimitive.Root.Props & {
|
|
10
|
+
/**
|
|
11
|
+
* What is this meter even for bro?
|
|
12
|
+
* If one is not provided, we render a standard meter (no label, no value).
|
|
13
|
+
* You can still provide one yourself through children.
|
|
14
|
+
* @example
|
|
15
|
+
* <Meter value={24}>
|
|
16
|
+
* <MeterPrimitive.Label render={<Text />} />
|
|
17
|
+
* <MeterPrimitive.Value render={<Text />} />
|
|
18
|
+
* </Meter>
|
|
19
|
+
*/
|
|
20
|
+
label?: string;
|
|
21
|
+
|
|
22
|
+
/** Communicate purpose with color. Defaults to "primary". */
|
|
23
|
+
color?: Color;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function Meter({
|
|
27
|
+
label,
|
|
28
|
+
color = "primary",
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: MeterProps) {
|
|
33
|
+
return (
|
|
34
|
+
<View color={color}>
|
|
35
|
+
<MeterPrimitive.Root
|
|
36
|
+
className={clsx(styles["meter"], className)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{label ? (
|
|
40
|
+
<>
|
|
41
|
+
<Text render={<MeterPrimitive.Label />}>{label}</Text>
|
|
42
|
+
<Text
|
|
43
|
+
className={styles["meter__value"]}
|
|
44
|
+
render={<MeterPrimitive.Value />}
|
|
45
|
+
/>
|
|
46
|
+
</>
|
|
47
|
+
) : null}
|
|
48
|
+
{children}
|
|
49
|
+
<MeterPrimitive.Track className={styles["meter__track"]}>
|
|
50
|
+
<MeterPrimitive.Indicator className={styles["meter__indicator"]} />
|
|
51
|
+
</MeterPrimitive.Track>
|
|
52
|
+
</MeterPrimitive.Root>
|
|
53
|
+
</View>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.meter {
|
|
2
|
+
display: grid;
|
|
3
|
+
grid-template-columns: 1fr 1fr;
|
|
4
|
+
row-gap: var(--space-4);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.meter__value {
|
|
8
|
+
grid-column-start: 2;
|
|
9
|
+
margin: 0;
|
|
10
|
+
text-align: right;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.meter__track {
|
|
14
|
+
grid-column: 1 / 3;
|
|
15
|
+
background-color: var(--surface-interactive-border);
|
|
16
|
+
height: var(--space-8);
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.meter__indicator {
|
|
21
|
+
transition: width var(--transition-duration-chill);
|
|
22
|
+
background-color: var(--view-colorway-default);
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { colors } from "~/stories/data";
|
|
4
|
+
import { tokens } from "~/styles/tokens";
|
|
5
|
+
|
|
6
|
+
import { Meter } from ".";
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Meter",
|
|
10
|
+
component: Meter,
|
|
11
|
+
parameters: { layout: "centered" },
|
|
12
|
+
argTypes: {
|
|
13
|
+
color: {
|
|
14
|
+
control: "select",
|
|
15
|
+
options: colors,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
} satisfies Meta<typeof Meter>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
color: "orange",
|
|
27
|
+
label: "Storage used",
|
|
28
|
+
value: 24,
|
|
29
|
+
},
|
|
30
|
+
render: (args) => <Meter {...args} style={{ width: tokens.space256 }} />,
|
|
31
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { useRender } from "@base-ui/react";
|
|
2
|
+
import autosize from "autosize";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
|
|
6
|
+
import { mergeRefs } from "~/-utils";
|
|
7
|
+
|
|
8
|
+
import { View } from "../view";
|
|
9
|
+
|
|
10
|
+
import styles from "../input/input.module.css";
|
|
11
|
+
|
|
12
|
+
export interface MultilineInputProps extends useRender.ComponentProps<"textarea"> {
|
|
13
|
+
/** Automatically resize textarea. Defaults to false. */
|
|
14
|
+
autosize?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function MultilineInput({
|
|
18
|
+
autosize = false,
|
|
19
|
+
className,
|
|
20
|
+
ref,
|
|
21
|
+
...props
|
|
22
|
+
}: MultilineInputProps) {
|
|
23
|
+
const autosizeRef = useAutosize(autosize);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<View
|
|
27
|
+
interactive="fill-outline"
|
|
28
|
+
render={
|
|
29
|
+
<textarea
|
|
30
|
+
className={clsx(
|
|
31
|
+
styles["input"],
|
|
32
|
+
autosize && styles["input_autosize"],
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
ref={mergeRefs(ref, autosizeRef)}
|
|
36
|
+
{...props}
|
|
37
|
+
></textarea>
|
|
38
|
+
}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function useAutosize(enabled: boolean) {
|
|
44
|
+
const ref = useRef<HTMLTextAreaElement>(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const element = ref.current;
|
|
48
|
+
if (!element || !enabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
autosize(element);
|
|
52
|
+
return () => {
|
|
53
|
+
autosize.destroy(element);
|
|
54
|
+
};
|
|
55
|
+
}, [enabled]);
|
|
56
|
+
|
|
57
|
+
return ref;
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { tokens } from "~/styles/tokens";
|
|
4
|
+
|
|
5
|
+
import { MultilineInput } from ".";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Input/Textarea",
|
|
9
|
+
component: MultilineInput,
|
|
10
|
+
parameters: { layout: "centered" },
|
|
11
|
+
} satisfies Meta<typeof MultilineInput>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const Default: Story = {
|
|
18
|
+
args: {
|
|
19
|
+
placeholder: "Hello World",
|
|
20
|
+
disabled: false,
|
|
21
|
+
autosize: true,
|
|
22
|
+
},
|
|
23
|
+
render: (args) => (
|
|
24
|
+
<MultilineInput {...args} style={{ width: tokens.space256 }} />
|
|
25
|
+
),
|
|
26
|
+
};
|