@peerbots/core 0.1.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/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/publish.yml +42 -0
- package/.github/workflows/storybook.yml +46 -0
- package/.storybook/main.ts +28 -0
- package/.storybook/preview.ts +22 -0
- package/README.md +9 -0
- package/dist/index.css +1 -0
- package/dist/index.d.mts +704 -0
- package/dist/index.d.ts +704 -0
- package/dist/index.js +5 -0
- package/dist/index.mjs +5 -0
- package/package.json +60 -0
- package/src/charts/DistributionBarChart.stories.tsx +41 -0
- package/src/charts/DistributionBarChart.tsx +170 -0
- package/src/charts/DistributionHistogram.stories.tsx +56 -0
- package/src/charts/DistributionHistogram.tsx +193 -0
- package/src/charts/index.ts +10 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/SEO.tsx +41 -0
- package/src/index.ts +6 -0
- package/src/styles/theme.css +60 -0
- package/src/ui/Alert.stories.tsx +41 -0
- package/src/ui/Alert.tsx +72 -0
- package/src/ui/Anchor.stories.tsx +25 -0
- package/src/ui/Anchor.tsx +32 -0
- package/src/ui/AuthFormUI.stories.tsx +67 -0
- package/src/ui/AuthFormUI.tsx +217 -0
- package/src/ui/BasePanel.stories.tsx +36 -0
- package/src/ui/BasePanel.tsx +59 -0
- package/src/ui/Button.stories.tsx +108 -0
- package/src/ui/Button.tsx +121 -0
- package/src/ui/Checkbox.stories.tsx +61 -0
- package/src/ui/Checkbox.tsx +45 -0
- package/src/ui/Collapsible.stories.tsx +91 -0
- package/src/ui/Collapsible.tsx +52 -0
- package/src/ui/Colors.stories.tsx +67 -0
- package/src/ui/Dialog.stories.tsx +29 -0
- package/src/ui/Dialog.tsx +56 -0
- package/src/ui/Dropdown.tsx +66 -0
- package/src/ui/Field.stories.tsx +181 -0
- package/src/ui/Field.tsx +108 -0
- package/src/ui/Icon.stories.tsx +192 -0
- package/src/ui/Icon.tsx +42 -0
- package/src/ui/IconRegistry.tsx +189 -0
- package/src/ui/Input.stories.tsx +67 -0
- package/src/ui/Input.tsx +43 -0
- package/src/ui/Label.stories.tsx +42 -0
- package/src/ui/Label.tsx +26 -0
- package/src/ui/NumberField.stories.tsx +86 -0
- package/src/ui/NumberField.tsx +116 -0
- package/src/ui/Popover.tsx +42 -0
- package/src/ui/Select.stories.tsx +74 -0
- package/src/ui/Select.tsx +122 -0
- package/src/ui/Separator.stories.tsx +61 -0
- package/src/ui/Separator.tsx +28 -0
- package/src/ui/SettingsPanel.stories.tsx +83 -0
- package/src/ui/SettingsPanel.tsx +81 -0
- package/src/ui/Skeleton.stories.tsx +43 -0
- package/src/ui/Skeleton.tsx +15 -0
- package/src/ui/Slider.stories.tsx +140 -0
- package/src/ui/Slider.tsx +95 -0
- package/src/ui/SliderWithNumberField.stories.tsx +101 -0
- package/src/ui/SliderWithNumberField.tsx +88 -0
- package/src/ui/Switch.stories.tsx +81 -0
- package/src/ui/Switch.tsx +60 -0
- package/src/ui/TabRadio.stories.tsx +153 -0
- package/src/ui/TabRadio.tsx +68 -0
- package/src/ui/TabSelection.stories.tsx +44 -0
- package/src/ui/TabSelection.tsx +91 -0
- package/src/ui/TextArea.stories.tsx +64 -0
- package/src/ui/TextArea.tsx +24 -0
- package/src/ui/Tooltip.stories.tsx +84 -0
- package/src/ui/Tooltip.tsx +61 -0
- package/src/ui/Typography.stories.tsx +87 -0
- package/src/ui/Typography.tsx +80 -0
- package/src/ui/index.ts +28 -0
- package/src/ui/utils.ts +6 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +36 -0
- package/vitest.shims.d.ts +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { TabRadio, TabRadioOption } from "./TabRadio";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Icon } from "./Icon";
|
|
5
|
+
import { Heading, Text } from "./Typography";
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "UI/TabRadio",
|
|
10
|
+
component: TabRadio,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: "centered",
|
|
13
|
+
},
|
|
14
|
+
tags: ["autodocs"],
|
|
15
|
+
argTypes: {
|
|
16
|
+
variant: {
|
|
17
|
+
control: "select",
|
|
18
|
+
options: ["default", "primary", "danger"],
|
|
19
|
+
},
|
|
20
|
+
onChange: { action: "changed" },
|
|
21
|
+
},
|
|
22
|
+
} satisfies Meta<typeof TabRadio>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
|
|
27
|
+
// Wrapper to manage state in the story
|
|
28
|
+
const TabRadioWithState = <T extends string>(
|
|
29
|
+
args: React.ComponentProps<typeof TabRadio<T>>,
|
|
30
|
+
) => {
|
|
31
|
+
const [value, setValue] = useState(args.value);
|
|
32
|
+
return (
|
|
33
|
+
<TabRadio
|
|
34
|
+
{...args}
|
|
35
|
+
value={value}
|
|
36
|
+
onChange={(val) => {
|
|
37
|
+
setValue(val);
|
|
38
|
+
args.onChange?.(val);
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const textOptions = [
|
|
45
|
+
{ value: "daily", label: "Daily" },
|
|
46
|
+
{ value: "weekly", label: "Weekly" },
|
|
47
|
+
{ value: "monthly", label: "Monthly" },
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
export const Default: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
options: textOptions as readonly TabRadioOption<string>[],
|
|
53
|
+
value: "daily",
|
|
54
|
+
onChange: () => {},
|
|
55
|
+
},
|
|
56
|
+
render: (args) => <TabRadioWithState {...args} />,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const Variations: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
options: textOptions as readonly TabRadioOption<string>[],
|
|
62
|
+
value: "daily",
|
|
63
|
+
onChange: () => {},
|
|
64
|
+
},
|
|
65
|
+
render: () => (
|
|
66
|
+
<div className="flex flex-col gap-12 p-4 min-w-[400px]">
|
|
67
|
+
<div className="space-y-4">
|
|
68
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
69
|
+
Variants
|
|
70
|
+
</Heading>
|
|
71
|
+
<div className="space-y-6">
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Text variant="small" className="italic">
|
|
74
|
+
Default
|
|
75
|
+
</Text>
|
|
76
|
+
<TabRadioWithState
|
|
77
|
+
options={textOptions as readonly TabRadioOption<string>[]}
|
|
78
|
+
value="daily"
|
|
79
|
+
variant="default"
|
|
80
|
+
onChange={() => {}}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
<div className="space-y-2">
|
|
84
|
+
<Text variant="small" className="italic">
|
|
85
|
+
Primary
|
|
86
|
+
</Text>
|
|
87
|
+
<TabRadioWithState
|
|
88
|
+
options={textOptions as readonly TabRadioOption<string>[]}
|
|
89
|
+
value="daily"
|
|
90
|
+
variant="primary"
|
|
91
|
+
onChange={() => {}}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
<div className="space-y-2">
|
|
95
|
+
<Text variant="small" className="italic">
|
|
96
|
+
Danger
|
|
97
|
+
</Text>
|
|
98
|
+
<TabRadioWithState
|
|
99
|
+
options={textOptions as readonly TabRadioOption<string>[]}
|
|
100
|
+
value="daily"
|
|
101
|
+
variant="danger"
|
|
102
|
+
onChange={() => {}}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className="space-y-4">
|
|
109
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
110
|
+
With Icons
|
|
111
|
+
</Heading>
|
|
112
|
+
<TabRadioWithState
|
|
113
|
+
value="Message"
|
|
114
|
+
onChange={() => {}}
|
|
115
|
+
options={
|
|
116
|
+
[
|
|
117
|
+
{
|
|
118
|
+
value: "Message",
|
|
119
|
+
label: (
|
|
120
|
+
<div className="flex items-center gap-1">
|
|
121
|
+
<Icon size="sm" className="w-4 h-4">
|
|
122
|
+
<path
|
|
123
|
+
strokeLinecap="round"
|
|
124
|
+
strokeLinejoin="round"
|
|
125
|
+
d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"
|
|
126
|
+
/>
|
|
127
|
+
</Icon>
|
|
128
|
+
Message
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
value: "Log",
|
|
134
|
+
label: (
|
|
135
|
+
<div className="flex items-center gap-1">
|
|
136
|
+
<Icon size="sm" className="w-4 h-4">
|
|
137
|
+
<path
|
|
138
|
+
strokeLinecap="round"
|
|
139
|
+
strokeLinejoin="round"
|
|
140
|
+
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
|
|
141
|
+
/>
|
|
142
|
+
</Icon>
|
|
143
|
+
Log
|
|
144
|
+
</div>
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
] as const
|
|
148
|
+
}
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { cn } from "./utils";
|
|
3
|
+
import { Toggle, ToggleGroup } from "@base-ui/react";
|
|
4
|
+
|
|
5
|
+
export interface TabRadioOption<T extends string> {
|
|
6
|
+
value: T;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TabRadioProps<T extends string> {
|
|
12
|
+
options: readonly TabRadioOption<T>[];
|
|
13
|
+
value: T | undefined | null;
|
|
14
|
+
onChange: (value: T | undefined) => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
variant?: "default" | "primary" | "danger";
|
|
17
|
+
"aria-label"?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const variantStyles = {
|
|
21
|
+
default:
|
|
22
|
+
"data-[pressed]:bg-white data-[pressed]:text-gray-900 data-[pressed]:shadow-sm data-[pressed]:ring-1 data-[pressed]:ring-gray-200",
|
|
23
|
+
primary:
|
|
24
|
+
"data-[pressed]:bg-primary/20 data-[pressed]:shadow-sm data-[pressed]:ring-1 data-[pressed]:ring-primary",
|
|
25
|
+
danger:
|
|
26
|
+
"data-[pressed]:bg-danger/20 data-[pressed]:shadow-sm data-[pressed]:ring-1 data-[pressed]:ring-danger",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function TabRadio<T extends string>({
|
|
30
|
+
options,
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
className,
|
|
34
|
+
variant = "default",
|
|
35
|
+
"aria-label": ariaLabel,
|
|
36
|
+
}: TabRadioProps<T>) {
|
|
37
|
+
return (
|
|
38
|
+
<ToggleGroup
|
|
39
|
+
value={value ? [value] : []}
|
|
40
|
+
onValueChange={(val) => {
|
|
41
|
+
onChange(val[0] as T | undefined);
|
|
42
|
+
}}
|
|
43
|
+
aria-label={ariaLabel}
|
|
44
|
+
className={cn(
|
|
45
|
+
"inline-flex p-1 space-x-1 bg-gray-100 rounded-lg border border-gray-200",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{options.map((option) => {
|
|
50
|
+
return (
|
|
51
|
+
<Toggle
|
|
52
|
+
key={option.value}
|
|
53
|
+
value={option.value}
|
|
54
|
+
disabled={option.disabled}
|
|
55
|
+
className={cn(
|
|
56
|
+
"relative cursor-pointer px-3 py-1.5 text-sm font-medium rounded-md transition-all flex items-center justify-center outline-none",
|
|
57
|
+
"text-gray-600 hover:text-gray-900 hover:bg-gray-200",
|
|
58
|
+
"disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:text-gray-600",
|
|
59
|
+
variantStyles[variant],
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{option.label}
|
|
63
|
+
</Toggle>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</ToggleGroup>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { TabSelection } from "./TabSelection";
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof TabSelection> = {
|
|
6
|
+
title: "UI/TabSelection",
|
|
7
|
+
component: TabSelection,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
|
|
15
|
+
type Story = StoryObj<typeof TabSelection>;
|
|
16
|
+
|
|
17
|
+
const mockTabs = [
|
|
18
|
+
{ id: "1", label: "Default Template" },
|
|
19
|
+
{ id: "2", label: "Robot Calibration" },
|
|
20
|
+
{ id: "3", label: "Experimental Sequence" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
render: function Render() {
|
|
25
|
+
const [activeTab, setActiveTab] = useState<string>("1");
|
|
26
|
+
|
|
27
|
+
const tabsWithActions = mockTabs.map((tab) => ({
|
|
28
|
+
...tab,
|
|
29
|
+
onEdit: () => alert(`Editing ${tab.label}`),
|
|
30
|
+
onClose: () => alert(`Closing ${tab.label}`),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="w-[600px] border p-4 bg-gray-50">
|
|
35
|
+
<TabSelection
|
|
36
|
+
tabs={tabsWithActions}
|
|
37
|
+
activeTabId={activeTab}
|
|
38
|
+
onTabClick={setActiveTab}
|
|
39
|
+
onAddClick={() => alert("Add new tab clicked!")}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Button, Icon } from ".";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
export interface TabItem {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
onEdit?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TabSelectionProps {
|
|
12
|
+
tabs: TabItem[];
|
|
13
|
+
activeTabId: string | null;
|
|
14
|
+
onTabClick: (id: string) => void;
|
|
15
|
+
onAddClick?: () => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function TabSelection({
|
|
20
|
+
tabs,
|
|
21
|
+
activeTabId,
|
|
22
|
+
onTabClick,
|
|
23
|
+
onAddClick,
|
|
24
|
+
className = "",
|
|
25
|
+
}: TabSelectionProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={`overflow-x-auto max-h-20 text-sm whitespace-nowrap ${className}`}
|
|
29
|
+
>
|
|
30
|
+
{tabs.map((tab) => {
|
|
31
|
+
const isActive = tab.id === activeTabId;
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
key={tab.id}
|
|
35
|
+
className={
|
|
36
|
+
"inline-block align-middle m-1 px-2 py-2 cursor-pointer rounded-sm " +
|
|
37
|
+
(isActive
|
|
38
|
+
? "font-bold bg-primary/30"
|
|
39
|
+
: "font-gray-300 bg-gray-200")
|
|
40
|
+
}
|
|
41
|
+
onClick={() => onTabClick(tab.id)}
|
|
42
|
+
>
|
|
43
|
+
<span className="inline-block align-middle">{tab.label}</span>
|
|
44
|
+
|
|
45
|
+
{isActive && (tab.onEdit || tab.onClose) && (
|
|
46
|
+
<span className="inline-flex items-center ml-2 align-middle">
|
|
47
|
+
{tab.onEdit && (
|
|
48
|
+
<Button
|
|
49
|
+
variant="ghost"
|
|
50
|
+
size="sm"
|
|
51
|
+
aria-label="Edit Tab"
|
|
52
|
+
onClick={(e) => {
|
|
53
|
+
e.stopPropagation();
|
|
54
|
+
tab.onEdit!();
|
|
55
|
+
}}
|
|
56
|
+
leftIcon={<Icon name="edit" size="sm" />}
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
{tab.onEdit && tab.onClose && (
|
|
60
|
+
<span className="opacity-10 align-middle mx-1">{" | "}</span>
|
|
61
|
+
)}
|
|
62
|
+
{tab.onClose && (
|
|
63
|
+
<Button
|
|
64
|
+
variant="ghost"
|
|
65
|
+
size="sm"
|
|
66
|
+
aria-label="Close Tab"
|
|
67
|
+
onClick={(e) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
tab.onClose!();
|
|
70
|
+
}}
|
|
71
|
+
leftIcon={<Icon name="close" size="sm" />}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</span>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
})}
|
|
79
|
+
{onAddClick && (
|
|
80
|
+
<Button
|
|
81
|
+
variant="primary"
|
|
82
|
+
size="sm"
|
|
83
|
+
aria-label="Create New Tab"
|
|
84
|
+
onClick={onAddClick}
|
|
85
|
+
leftIcon={<Icon name="plus" size="sm" />}
|
|
86
|
+
className="m-1 align-middle inline-flex"
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { TextArea } from "./TextArea";
|
|
3
|
+
import { Heading, Text } from "./Typography";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof TextArea> = {
|
|
6
|
+
title: "UI/TextArea",
|
|
7
|
+
component: TextArea,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
argTypes: {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
type Story = StoryObj<typeof TextArea>;
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
args: {
|
|
17
|
+
placeholder: "Type your message here...",
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const Variations: Story = {
|
|
22
|
+
render: () => (
|
|
23
|
+
<div className="flex flex-col gap-12 p-4 max-w-lg">
|
|
24
|
+
<div className="space-y-4">
|
|
25
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
26
|
+
Styles & Sizes
|
|
27
|
+
</Heading>
|
|
28
|
+
<div className="space-y-6">
|
|
29
|
+
<div className="space-y-1">
|
|
30
|
+
<Text variant="small" className="font-bold">
|
|
31
|
+
With Predefined Value
|
|
32
|
+
</Text>
|
|
33
|
+
<TextArea defaultValue="This is some predefined text in the textarea." />
|
|
34
|
+
</div>
|
|
35
|
+
<div className="space-y-1">
|
|
36
|
+
<Text variant="small" className="font-bold">
|
|
37
|
+
Large (10 rows)
|
|
38
|
+
</Text>
|
|
39
|
+
<TextArea
|
|
40
|
+
rows={10}
|
|
41
|
+
placeholder="A larger textarea for more content..."
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div className="space-y-4">
|
|
48
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
49
|
+
States
|
|
50
|
+
</Heading>
|
|
51
|
+
<div className="space-y-1">
|
|
52
|
+
<Text variant="small" className="font-bold">
|
|
53
|
+
Disabled
|
|
54
|
+
</Text>
|
|
55
|
+
<TextArea
|
|
56
|
+
disabled
|
|
57
|
+
placeholder="Cannot type here"
|
|
58
|
+
defaultValue="Read only content"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Input as BaseInput } from "@base-ui/react";
|
|
2
|
+
import React, { ComponentPropsWithoutRef } from "react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
|
|
5
|
+
export type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
6
|
+
|
|
7
|
+
export const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
8
|
+
({ className, id, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<BaseInput
|
|
11
|
+
render={<textarea />}
|
|
12
|
+
ref={ref as React.Ref<HTMLTextAreaElement>}
|
|
13
|
+
id={id}
|
|
14
|
+
className={cn(
|
|
15
|
+
"block w-full rounded-md border-0 px-1.5 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary sm:text-sm sm:leading-6 bg-primary/10 appearance-none",
|
|
16
|
+
className,
|
|
17
|
+
)}
|
|
18
|
+
{...(props as ComponentPropsWithoutRef<typeof BaseInput>)}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
TextArea.displayName = "TextArea";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Tooltip, HelperTooltip } from "./Tooltip";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { Heading } from "./Typography";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Tooltip> = {
|
|
7
|
+
title: "UI/Tooltip",
|
|
8
|
+
component: Tooltip,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
content: { control: "text" },
|
|
12
|
+
delay: { control: "radio", options: ["normal", "slow"] },
|
|
13
|
+
},
|
|
14
|
+
decorators: [
|
|
15
|
+
(Story) => (
|
|
16
|
+
<div className="flex h-32 items-center justify-center">
|
|
17
|
+
<Story />
|
|
18
|
+
</div>
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof Tooltip>;
|
|
25
|
+
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
args: {
|
|
28
|
+
content: "This is a tooltip",
|
|
29
|
+
children: <span>Hover me</span>,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Variations: Story = {
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (
|
|
36
|
+
<div className="p-12">
|
|
37
|
+
<Story />
|
|
38
|
+
</div>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
render: () => (
|
|
42
|
+
<div className="flex flex-col gap-16">
|
|
43
|
+
<div className="space-y-4">
|
|
44
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
45
|
+
Placements & Triggers
|
|
46
|
+
</Heading>
|
|
47
|
+
<div className="flex flex-wrap gap-8 items-center justify-center">
|
|
48
|
+
<Tooltip content="Tooltip on button">
|
|
49
|
+
<Button>On Button</Button>
|
|
50
|
+
</Tooltip>
|
|
51
|
+
<Tooltip content="Simple span trigger">
|
|
52
|
+
<span className="cursor-help underline decoration-dotted">
|
|
53
|
+
On Text
|
|
54
|
+
</span>
|
|
55
|
+
</Tooltip>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="space-y-4">
|
|
60
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
61
|
+
Configurations
|
|
62
|
+
</Heading>
|
|
63
|
+
<div className="flex flex-wrap gap-8 items-center justify-center">
|
|
64
|
+
<Tooltip content="I appear slowly" delay="slow">
|
|
65
|
+
<Button variant="secondary">Slow Delay (1s)</Button>
|
|
66
|
+
</Tooltip>
|
|
67
|
+
<Tooltip content="This is a very long tooltip message that should wrap nicely within the tooltip bounds instead of overflowing outside the container.">
|
|
68
|
+
<Button variant="ghost">Long Content wrapping</Button>
|
|
69
|
+
</Tooltip>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="space-y-4">
|
|
74
|
+
<Heading level={4} className="text-sm font-medium text-black uppercase">
|
|
75
|
+
Helper Tooltip
|
|
76
|
+
</Heading>
|
|
77
|
+
<div className="flex flex-wrap gap-8 items-center justify-center">
|
|
78
|
+
<HelperTooltip content="I'm a helper" />
|
|
79
|
+
<HelperTooltip content="This is a much longer message that is trying to illustrate what happens when we have a long message for the helper tooltip." />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Tooltip as BaseTooltip } from "@base-ui/react";
|
|
3
|
+
import { cn } from "./utils";
|
|
4
|
+
import { Icon } from "./Icon";
|
|
5
|
+
|
|
6
|
+
export interface TooltipProps {
|
|
7
|
+
content: React.ReactNode;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
delay?: "normal" | "slow";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Tooltip({
|
|
14
|
+
content,
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
delay = "normal",
|
|
18
|
+
}: TooltipProps) {
|
|
19
|
+
return (
|
|
20
|
+
<BaseTooltip.Provider delay={delay === "slow" ? 1000 : 0}>
|
|
21
|
+
<BaseTooltip.Root>
|
|
22
|
+
{React.isValidElement(children) ? (
|
|
23
|
+
<BaseTooltip.Trigger render={children} />
|
|
24
|
+
) : (
|
|
25
|
+
<BaseTooltip.Trigger>{children}</BaseTooltip.Trigger>
|
|
26
|
+
)}
|
|
27
|
+
<BaseTooltip.Portal>
|
|
28
|
+
<BaseTooltip.Positioner sideOffset={8}>
|
|
29
|
+
<BaseTooltip.Popup
|
|
30
|
+
className={cn(
|
|
31
|
+
"z-50 rounded shadow-lg bg-white p-2 text-xs text-center whitespace-normal max-w-xs text-gray-900 border border-gray-200",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
{content}
|
|
36
|
+
<BaseTooltip.Arrow className="fill-white stroke-gray-200" />
|
|
37
|
+
</BaseTooltip.Popup>
|
|
38
|
+
</BaseTooltip.Positioner>
|
|
39
|
+
</BaseTooltip.Portal>
|
|
40
|
+
</BaseTooltip.Root>
|
|
41
|
+
</BaseTooltip.Provider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
export interface HelperTooltipProps {
|
|
45
|
+
content: React.ReactNode;
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function HelperTooltip({ content, className }: HelperTooltipProps) {
|
|
50
|
+
return (
|
|
51
|
+
<Tooltip content={content} className={className}>
|
|
52
|
+
<Icon size="md" className="text-gray-500 hover:text-gray-700 cursor-help">
|
|
53
|
+
<path
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z"
|
|
57
|
+
/>
|
|
58
|
+
</Icon>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Heading, Text } from "./Typography";
|
|
3
|
+
|
|
4
|
+
const meta: Meta = {
|
|
5
|
+
title: "UI/Typography",
|
|
6
|
+
tags: ["autodocs"],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
|
|
11
|
+
export const Default: StoryObj = {
|
|
12
|
+
render: () => (
|
|
13
|
+
<div className="space-y-4 max-w-lg">
|
|
14
|
+
<Heading level={2}>Core Typography</Heading>
|
|
15
|
+
<Text>
|
|
16
|
+
The quick brown fox jumps over the lazy dog. This is our standard body
|
|
17
|
+
text used across the application.
|
|
18
|
+
</Text>
|
|
19
|
+
</div>
|
|
20
|
+
),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const Variations: StoryObj = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<div className="flex flex-col gap-12 p-4">
|
|
26
|
+
<div className="space-y-4">
|
|
27
|
+
<Heading
|
|
28
|
+
level={4}
|
|
29
|
+
className="text-sm font-medium text-black uppercase tracking-wider border-b pb-1"
|
|
30
|
+
>
|
|
31
|
+
Headings
|
|
32
|
+
</Heading>
|
|
33
|
+
<div className="space-y-4">
|
|
34
|
+
<Heading level={1}>Heading 1</Heading>
|
|
35
|
+
<Heading level={2}>Heading 2</Heading>
|
|
36
|
+
<Heading level={3}>Heading 3</Heading>
|
|
37
|
+
<Heading level={4}>Heading 4</Heading>
|
|
38
|
+
<Heading level={5}>Heading 5</Heading>
|
|
39
|
+
<Heading level={6}>Heading 6</Heading>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="space-y-4">
|
|
44
|
+
<Heading
|
|
45
|
+
level={4}
|
|
46
|
+
className="text-sm font-medium text-black uppercase tracking-wider border-b pb-1"
|
|
47
|
+
>
|
|
48
|
+
Text Variants
|
|
49
|
+
</Heading>
|
|
50
|
+
<div className="space-y-4">
|
|
51
|
+
<div className="space-y-1">
|
|
52
|
+
<Text variant="small" className="italic font-bold">
|
|
53
|
+
Default
|
|
54
|
+
</Text>
|
|
55
|
+
<Text variant="default">
|
|
56
|
+
The quick brown fox jumps over the lazy dog.
|
|
57
|
+
</Text>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="space-y-1">
|
|
60
|
+
<Text variant="small" className="italic font-bold">
|
|
61
|
+
Small
|
|
62
|
+
</Text>
|
|
63
|
+
<Text variant="small">
|
|
64
|
+
The quick brown fox jumps over the lazy dog.
|
|
65
|
+
</Text>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="space-y-1">
|
|
68
|
+
<Text variant="small" className="italic font-bold">
|
|
69
|
+
Muted
|
|
70
|
+
</Text>
|
|
71
|
+
<Text variant="muted">
|
|
72
|
+
The quick brown fox jumps over the lazy dog.
|
|
73
|
+
</Text>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="space-y-1">
|
|
76
|
+
<Text variant="small" className="italic font-bold">
|
|
77
|
+
Error
|
|
78
|
+
</Text>
|
|
79
|
+
<Text variant="error">
|
|
80
|
+
The quick brown fox jumps over the lazy dog.
|
|
81
|
+
</Text>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
};
|