@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.
Files changed (81) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.github/workflows/publish.yml +42 -0
  4. package/.github/workflows/storybook.yml +46 -0
  5. package/.storybook/main.ts +28 -0
  6. package/.storybook/preview.ts +22 -0
  7. package/README.md +9 -0
  8. package/dist/index.css +1 -0
  9. package/dist/index.d.mts +704 -0
  10. package/dist/index.d.ts +704 -0
  11. package/dist/index.js +5 -0
  12. package/dist/index.mjs +5 -0
  13. package/package.json +60 -0
  14. package/src/charts/DistributionBarChart.stories.tsx +41 -0
  15. package/src/charts/DistributionBarChart.tsx +170 -0
  16. package/src/charts/DistributionHistogram.stories.tsx +56 -0
  17. package/src/charts/DistributionHistogram.tsx +193 -0
  18. package/src/charts/index.ts +10 -0
  19. package/src/global.d.ts +1 -0
  20. package/src/helpers/SEO.tsx +41 -0
  21. package/src/index.ts +6 -0
  22. package/src/styles/theme.css +60 -0
  23. package/src/ui/Alert.stories.tsx +41 -0
  24. package/src/ui/Alert.tsx +72 -0
  25. package/src/ui/Anchor.stories.tsx +25 -0
  26. package/src/ui/Anchor.tsx +32 -0
  27. package/src/ui/AuthFormUI.stories.tsx +67 -0
  28. package/src/ui/AuthFormUI.tsx +217 -0
  29. package/src/ui/BasePanel.stories.tsx +36 -0
  30. package/src/ui/BasePanel.tsx +59 -0
  31. package/src/ui/Button.stories.tsx +108 -0
  32. package/src/ui/Button.tsx +121 -0
  33. package/src/ui/Checkbox.stories.tsx +61 -0
  34. package/src/ui/Checkbox.tsx +45 -0
  35. package/src/ui/Collapsible.stories.tsx +91 -0
  36. package/src/ui/Collapsible.tsx +52 -0
  37. package/src/ui/Colors.stories.tsx +67 -0
  38. package/src/ui/Dialog.stories.tsx +29 -0
  39. package/src/ui/Dialog.tsx +56 -0
  40. package/src/ui/Dropdown.tsx +66 -0
  41. package/src/ui/Field.stories.tsx +181 -0
  42. package/src/ui/Field.tsx +108 -0
  43. package/src/ui/Icon.stories.tsx +192 -0
  44. package/src/ui/Icon.tsx +42 -0
  45. package/src/ui/IconRegistry.tsx +189 -0
  46. package/src/ui/Input.stories.tsx +67 -0
  47. package/src/ui/Input.tsx +43 -0
  48. package/src/ui/Label.stories.tsx +42 -0
  49. package/src/ui/Label.tsx +26 -0
  50. package/src/ui/NumberField.stories.tsx +86 -0
  51. package/src/ui/NumberField.tsx +116 -0
  52. package/src/ui/Popover.tsx +42 -0
  53. package/src/ui/Select.stories.tsx +74 -0
  54. package/src/ui/Select.tsx +122 -0
  55. package/src/ui/Separator.stories.tsx +61 -0
  56. package/src/ui/Separator.tsx +28 -0
  57. package/src/ui/SettingsPanel.stories.tsx +83 -0
  58. package/src/ui/SettingsPanel.tsx +81 -0
  59. package/src/ui/Skeleton.stories.tsx +43 -0
  60. package/src/ui/Skeleton.tsx +15 -0
  61. package/src/ui/Slider.stories.tsx +140 -0
  62. package/src/ui/Slider.tsx +95 -0
  63. package/src/ui/SliderWithNumberField.stories.tsx +101 -0
  64. package/src/ui/SliderWithNumberField.tsx +88 -0
  65. package/src/ui/Switch.stories.tsx +81 -0
  66. package/src/ui/Switch.tsx +60 -0
  67. package/src/ui/TabRadio.stories.tsx +153 -0
  68. package/src/ui/TabRadio.tsx +68 -0
  69. package/src/ui/TabSelection.stories.tsx +44 -0
  70. package/src/ui/TabSelection.tsx +91 -0
  71. package/src/ui/TextArea.stories.tsx +64 -0
  72. package/src/ui/TextArea.tsx +24 -0
  73. package/src/ui/Tooltip.stories.tsx +84 -0
  74. package/src/ui/Tooltip.tsx +61 -0
  75. package/src/ui/Typography.stories.tsx +87 -0
  76. package/src/ui/Typography.tsx +80 -0
  77. package/src/ui/index.ts +28 -0
  78. package/src/ui/utils.ts +6 -0
  79. package/tsconfig.json +12 -0
  80. package/vitest.config.ts +36 -0
  81. 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
+ };