@peerbots/core 0.2.6 → 1.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/.changeset/config.json +1 -1
- package/.github/workflows/storybook.yml +9 -1
- package/CHANGELOG.md +94 -0
- package/dist/index.d.mts +643 -131
- package/dist/index.d.ts +643 -131
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -3
- package/src/charts/DistributionBarChart.tsx +10 -4
- package/src/charts/DistributionHistogram.tsx +13 -5
- package/src/charts/LineGraph.stories.tsx +60 -0
- package/src/charts/LineGraph.tsx +216 -0
- package/src/charts/index.ts +2 -0
- package/src/docs/DesignSystem.mdx +241 -25
- package/src/styles/theme.css +8 -0
- package/src/ui/feedback/Alert.stories.tsx +56 -0
- package/src/ui/feedback/Alert.tsx +168 -0
- package/src/ui/{Dialog.stories.tsx → feedback/Dialog.stories.tsx} +2 -2
- package/src/ui/{Dialog.tsx → feedback/Dialog.tsx} +2 -2
- package/src/ui/feedback/EmptyState.stories.tsx +57 -0
- package/src/ui/feedback/EmptyState.tsx +123 -0
- package/src/ui/feedback/Popover.stories.tsx +130 -0
- package/src/ui/feedback/Popover.tsx +135 -0
- package/src/ui/feedback/Skeleton.stories.tsx +88 -0
- package/src/ui/{Skeleton.tsx → feedback/Skeleton.tsx} +2 -2
- package/src/ui/feedback/Spinner.stories.tsx +77 -0
- package/src/ui/feedback/Spinner.tsx +62 -0
- package/src/ui/{Tooltip.stories.tsx → feedback/Tooltip.stories.tsx} +3 -3
- package/src/ui/{Tooltip.tsx → feedback/Tooltip.tsx} +2 -2
- package/src/ui/feedback/index.ts +7 -0
- package/src/ui/forms/Button.stories.tsx +181 -0
- package/src/ui/forms/Button.tsx +323 -0
- package/src/ui/{Checkbox.stories.tsx → forms/Checkbox.stories.tsx} +2 -2
- package/src/ui/{Checkbox.tsx → forms/Checkbox.tsx} +2 -2
- package/src/ui/forms/ChipGroup.stories.tsx +118 -0
- package/src/ui/forms/ChipGroup.tsx +239 -0
- package/src/ui/{Dropdown.tsx → forms/Dropdown.tsx} +1 -1
- package/src/ui/{Field.stories.tsx → forms/Field.stories.tsx} +3 -3
- package/src/ui/{Field.tsx → forms/Field.tsx} +1 -1
- package/src/ui/{Input.stories.tsx → forms/Input.stories.tsx} +6 -6
- package/src/ui/{Input.tsx → forms/Input.tsx} +1 -1
- package/src/ui/{Label.stories.tsx → forms/Label.stories.tsx} +2 -2
- package/src/ui/{Label.tsx → forms/Label.tsx} +2 -2
- package/src/ui/{NumberField.stories.tsx → forms/NumberField.stories.tsx} +2 -2
- package/src/ui/{NumberField.tsx → forms/NumberField.tsx} +8 -9
- package/src/ui/forms/PeerbotsColorSelector.stories.tsx +49 -0
- package/src/ui/forms/PeerbotsColorSelector.tsx +100 -0
- package/src/ui/{Select.stories.tsx → forms/Select.stories.tsx} +2 -2
- package/src/ui/{Select.tsx → forms/Select.tsx} +5 -3
- package/src/ui/{Slider.stories.tsx → forms/Slider.stories.tsx} +6 -4
- package/src/ui/{Slider.tsx → forms/Slider.tsx} +7 -3
- package/src/ui/forms/SliderWithNumberField.stories.tsx +136 -0
- package/src/ui/{SliderWithNumberField.tsx → forms/SliderWithNumberField.tsx} +19 -7
- package/src/ui/{Switch.stories.tsx → forms/Switch.stories.tsx} +2 -2
- package/src/ui/{Switch.tsx → forms/Switch.tsx} +1 -1
- package/src/ui/{TabRadio.stories.tsx → forms/TabRadio.stories.tsx} +3 -3
- package/src/ui/{TabRadio.tsx → forms/TabRadio.tsx} +1 -1
- package/src/ui/{TextArea.stories.tsx → forms/TextArea.stories.tsx} +5 -3
- package/src/ui/{TextArea.tsx → forms/TextArea.tsx} +1 -1
- package/src/ui/forms/index.ts +15 -0
- package/src/ui/foundations/Anchor.stories.tsx +39 -0
- package/src/ui/foundations/Anchor.tsx +96 -0
- package/src/ui/foundations/CodeBlock.stories.tsx +86 -0
- package/src/ui/foundations/CodeBlock.tsx +148 -0
- package/src/ui/foundations/Colors.stories.tsx +80 -0
- package/src/ui/{Icon.stories.tsx → foundations/Icon.stories.tsx} +1 -1
- package/src/ui/{Icon.tsx → foundations/Icon.tsx} +1 -1
- package/src/ui/{IconRegistry.tsx → foundations/IconRegistry.tsx} +261 -57
- package/src/ui/foundations/SocialLinks.stories.tsx +38 -0
- package/src/ui/foundations/SocialLinks.tsx +117 -0
- package/src/ui/foundations/Typography.stories.tsx +58 -0
- package/src/ui/foundations/Typography.tsx +163 -0
- package/src/ui/foundations/index.ts +6 -0
- package/src/ui/index.ts +7 -27
- package/src/ui/layout/Accordion.stories.tsx +86 -0
- package/src/ui/layout/Accordion.tsx +205 -0
- package/src/ui/layout/Card.stories.tsx +151 -0
- package/src/ui/layout/Card.tsx +124 -0
- package/src/ui/layout/Collapsible.stories.tsx +54 -0
- package/src/ui/layout/Collapsible.tsx +54 -0
- package/src/ui/{Separator.stories.tsx → layout/Separator.stories.tsx} +2 -2
- package/src/ui/{Separator.tsx → layout/Separator.tsx} +1 -1
- package/src/ui/{SettingsPanel.stories.tsx → layout/SettingsPanel.stories.tsx} +5 -5
- package/src/ui/{SettingsPanel.tsx → layout/SettingsPanel.tsx} +33 -29
- package/src/ui/layout/index.ts +5 -0
- package/src/ui/media/Avatar.stories.tsx +89 -0
- package/src/ui/media/Avatar.tsx +122 -0
- package/src/ui/media/Dropzone.stories.tsx +95 -0
- package/src/ui/media/Dropzone.tsx +328 -0
- package/src/ui/media/FileUpload.stories.tsx +62 -0
- package/src/ui/media/FileUpload.tsx +175 -0
- package/src/ui/media/index.ts +3 -0
- package/src/ui/{AuthFormUI.stories.tsx → patterns/AuthFormUI.stories.tsx} +1 -1
- package/src/ui/{AuthFormUI.tsx → patterns/AuthFormUI.tsx} +24 -23
- package/src/ui/patterns/CopyButton.stories.tsx +77 -0
- package/src/ui/patterns/CopyButton.tsx +93 -0
- package/src/ui/patterns/SearchInput.stories.tsx +51 -0
- package/src/ui/patterns/SearchInput.tsx +93 -0
- package/src/ui/patterns/Stepper.stories.tsx +129 -0
- package/src/ui/patterns/Stepper.tsx +295 -0
- package/src/ui/{TabSelection.stories.tsx → patterns/TabSelection.stories.tsx} +1 -1
- package/src/ui/{TabSelection.tsx → patterns/TabSelection.tsx} +2 -2
- package/src/ui/patterns/index.ts +6 -0
- package/src/ui/patterns/useClipboard.ts +75 -0
- package/src/ui/types.ts +48 -0
- package/src/ui/Alert.stories.tsx +0 -41
- package/src/ui/Alert.tsx +0 -72
- package/src/ui/Anchor.stories.tsx +0 -25
- package/src/ui/Anchor.tsx +0 -32
- package/src/ui/Button.stories.tsx +0 -108
- package/src/ui/Button.tsx +0 -121
- package/src/ui/Collapsible.stories.tsx +0 -91
- package/src/ui/Collapsible.tsx +0 -52
- package/src/ui/Colors.stories.tsx +0 -67
- package/src/ui/Popover.tsx +0 -42
- package/src/ui/Skeleton.stories.tsx +0 -43
- package/src/ui/SliderWithNumberField.stories.tsx +0 -101
- package/src/ui/Typography.stories.tsx +0 -87
- package/src/ui/Typography.tsx +0 -80
package/src/styles/theme.css
CHANGED
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
--sidebar-bg: #f9ffff;
|
|
14
14
|
--color-sidebar-bg: #f9ffff;
|
|
15
15
|
|
|
16
|
+
--color-peerbots-pink: #e96c8a;
|
|
17
|
+
--color-peerbots-teal: #5fc7cc;
|
|
18
|
+
--color-peerbots-darkteal: #0f766e;
|
|
19
|
+
--color-peerbots-darkblue: #2e3a59;
|
|
20
|
+
--color-peerbots-olive: #82871f;
|
|
21
|
+
--color-peerbots-dark: #1a1a1a;
|
|
22
|
+
--color-peerbots-light: #f8f9fa;
|
|
23
|
+
|
|
16
24
|
--background-image-gradient-radial: radial-gradient(var(--tw-gradient-stops));
|
|
17
25
|
--background-image-gradient-conic: conic-gradient(from 180deg at 50% 50%,
|
|
18
26
|
var(--tw-gradient-stops));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Alert } from "./Alert";
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Alert> = {
|
|
6
|
+
title: "Feedback/Alert",
|
|
7
|
+
component: Alert,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
argTypes: {
|
|
13
|
+
color: {
|
|
14
|
+
control: "select",
|
|
15
|
+
options: ["info", "success", "warning", "danger", "neutral"],
|
|
16
|
+
},
|
|
17
|
+
variant: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: ["soft", "outline", "solid"],
|
|
20
|
+
},
|
|
21
|
+
title: { control: "text" },
|
|
22
|
+
message: { control: "text" },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof Alert>;
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
color: "info",
|
|
32
|
+
variant: "outline",
|
|
33
|
+
title: "System Update",
|
|
34
|
+
message: "A new robot face configuration has been synchronized to your controller.",
|
|
35
|
+
className: "pb:w-[480px]",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const ColorsAndVariants: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<div className="pb:flex pb:flex-col pb:gap-4 pb:w-[520px]">
|
|
42
|
+
<Alert color="info" variant="outline" title="Info Banner (Outline)" message="New feature release available." />
|
|
43
|
+
<Alert color="success" variant="soft" title="Session Connected" message="Controller paired with robot face." />
|
|
44
|
+
<Alert color="warning" variant="soft" title="Battery Low" message="Robot head battery is below 15%." />
|
|
45
|
+
<Alert
|
|
46
|
+
color="danger"
|
|
47
|
+
variant="soft"
|
|
48
|
+
title="Connection Failed"
|
|
49
|
+
message="Unable to reach the WebSocket server."
|
|
50
|
+
action={{ name: "Retry", callback: () => console.log("Retrying...") }}
|
|
51
|
+
/>
|
|
52
|
+
<Alert color="danger" variant="outline" title="Outline Danger" message="Action cannot be undone." />
|
|
53
|
+
<Alert color="success" variant="solid" title="Solid Success" message="Changes published successfully." />
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Button } from "../forms/Button";
|
|
3
|
+
import { Icon } from "../foundations/Icon";
|
|
4
|
+
import { cn } from "../utils";
|
|
5
|
+
import { ComponentRadius, FillVariant } from "../types";
|
|
6
|
+
|
|
7
|
+
export type AlertLevel = "Error" | "Warning" | "Success" | "Info";
|
|
8
|
+
export type AlertColor = "danger" | "warning" | "success" | "info" | "neutral";
|
|
9
|
+
|
|
10
|
+
export interface AlertAction {
|
|
11
|
+
name: string;
|
|
12
|
+
callback: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AlertUIProps {
|
|
16
|
+
/** Alert semantic color */
|
|
17
|
+
color?: AlertColor;
|
|
18
|
+
/** Legacy level prop for backward compatibility */
|
|
19
|
+
level?: AlertLevel;
|
|
20
|
+
/** Visual style variant */
|
|
21
|
+
variant?: "soft" | "outline" | "solid";
|
|
22
|
+
/** Optional alert title */
|
|
23
|
+
title?: React.ReactNode;
|
|
24
|
+
/** Alert message body */
|
|
25
|
+
message?: React.ReactNode;
|
|
26
|
+
/** Children as alternative to message */
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
/** Optional action button */
|
|
29
|
+
action?: AlertAction;
|
|
30
|
+
/** Callback fired when alert is dismissed */
|
|
31
|
+
onClose?: () => void;
|
|
32
|
+
/** Border radius scale */
|
|
33
|
+
radius?: ComponentRadius;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const levelColorMap: Record<AlertLevel, AlertColor> = {
|
|
38
|
+
Error: "danger",
|
|
39
|
+
Warning: "warning",
|
|
40
|
+
Success: "success",
|
|
41
|
+
Info: "info",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const iconMap: Record<AlertColor, { name: any; colorClass: string }> = {
|
|
45
|
+
danger: { name: "exclamationCircle", colorClass: "pb:text-red-700" },
|
|
46
|
+
warning: { name: "exclamationTriangle", colorClass: "pb:text-amber-800" },
|
|
47
|
+
success: { name: "checkCircle", colorClass: "pb:text-emerald-800" },
|
|
48
|
+
info: { name: "megaphone", colorClass: "pb:text-teal-800" },
|
|
49
|
+
neutral: { name: "questionMarkCircle", colorClass: "pb:text-gray-700" },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const variantColorClasses: Record<string, Record<AlertColor, string>> = {
|
|
53
|
+
soft: {
|
|
54
|
+
danger: "pb:bg-red-50 pb:border-red-200 pb:text-red-900",
|
|
55
|
+
warning: "pb:bg-amber-50 pb:border-amber-200 pb:text-amber-900",
|
|
56
|
+
success: "pb:bg-emerald-50 pb:border-emerald-200 pb:text-emerald-900",
|
|
57
|
+
info: "pb:bg-teal-50 pb:border-teal-200 pb:text-teal-900",
|
|
58
|
+
neutral: "pb:bg-gray-50 pb:border-gray-200 pb:text-gray-900",
|
|
59
|
+
},
|
|
60
|
+
outline: {
|
|
61
|
+
danger: "pb:bg-red-50/40 pb:border-2 pb:border-red-400 pb:text-red-950",
|
|
62
|
+
warning: "pb:bg-amber-50/40 pb:border-2 pb:border-amber-400 pb:text-amber-950",
|
|
63
|
+
success: "pb:bg-emerald-50/40 pb:border-2 pb:border-emerald-500 pb:text-emerald-950",
|
|
64
|
+
info: "pb:bg-teal-50/40 pb:border-2 pb:border-teal-500 pb:text-teal-950",
|
|
65
|
+
neutral: "pb:bg-gray-50/40 pb:border-2 pb:border-gray-300 pb:text-gray-950",
|
|
66
|
+
},
|
|
67
|
+
solid: {
|
|
68
|
+
danger: "pb:bg-red-700 pb:border-transparent pb:text-white",
|
|
69
|
+
warning: "pb:bg-amber-300 pb:border-transparent pb:text-gray-950 pb:font-medium",
|
|
70
|
+
success: "pb:bg-emerald-700 pb:border-transparent pb:text-white",
|
|
71
|
+
info: "pb:bg-teal-700 pb:border-transparent pb:text-white",
|
|
72
|
+
neutral: "pb:bg-gray-900 pb:border-transparent pb:text-white",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const radiusClasses: Record<ComponentRadius, string> = {
|
|
77
|
+
none: "pb:rounded-none",
|
|
78
|
+
sm: "pb:rounded-md",
|
|
79
|
+
md: "pb:rounded-xl",
|
|
80
|
+
lg: "pb:rounded-2xl",
|
|
81
|
+
"2xl": "pb:rounded-2xl",
|
|
82
|
+
pill: "pb:rounded-full",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function Alert({
|
|
86
|
+
color,
|
|
87
|
+
level,
|
|
88
|
+
variant = "soft",
|
|
89
|
+
title,
|
|
90
|
+
message,
|
|
91
|
+
children,
|
|
92
|
+
action,
|
|
93
|
+
onClose,
|
|
94
|
+
radius = "2xl",
|
|
95
|
+
className,
|
|
96
|
+
}: AlertUIProps) {
|
|
97
|
+
const [visible, setVisible] = useState(true);
|
|
98
|
+
|
|
99
|
+
if (!visible) return null;
|
|
100
|
+
|
|
101
|
+
const resolvedColor: AlertColor =
|
|
102
|
+
color || (level ? levelColorMap[level] : "info") || "info";
|
|
103
|
+
|
|
104
|
+
const { name: iconName, colorClass } = iconMap[resolvedColor];
|
|
105
|
+
const colorStyle = variantColorClasses[variant]?.[resolvedColor] || variantColorClasses.soft.info;
|
|
106
|
+
|
|
107
|
+
const handleDismiss = () => {
|
|
108
|
+
setVisible(false);
|
|
109
|
+
onClose?.();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const body = children || message;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
role="alert"
|
|
117
|
+
className={cn(
|
|
118
|
+
"pb:flex pb:w-full pb:items-start pb:gap-3.5 pb:p-4.5 pb:border pb:shadow-xs pb:transition-all pb:duration-200",
|
|
119
|
+
radiusClasses[radius] || radiusClasses["2xl"],
|
|
120
|
+
colorStyle,
|
|
121
|
+
className,
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
<div className="pb:shrink-0 pb:pt-0.5">
|
|
125
|
+
<Icon
|
|
126
|
+
name={iconName}
|
|
127
|
+
className={cn(
|
|
128
|
+
"pb:w-5 pb:h-5",
|
|
129
|
+
variant === "solid" ? "pb:text-current" : colorClass,
|
|
130
|
+
)}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div className="pb:flex-1 pb:min-w-0 pb:text-sm pb:leading-relaxed">
|
|
135
|
+
{title && (
|
|
136
|
+
<div className="pb:font-bold pb:text-base pb:mb-1 pb:leading-snug">
|
|
137
|
+
{title}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
<div className={cn(title && "pb:opacity-90")}>{body}</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{action && (
|
|
144
|
+
<Button
|
|
145
|
+
size="sm"
|
|
146
|
+
variant={variant === "solid" ? "outline" : "solid"}
|
|
147
|
+
color={resolvedColor === "danger" ? "danger" : "neutral"}
|
|
148
|
+
onClick={() => {
|
|
149
|
+
action.callback();
|
|
150
|
+
handleDismiss();
|
|
151
|
+
}}
|
|
152
|
+
className="pb:shrink-0 pb:self-center"
|
|
153
|
+
>
|
|
154
|
+
{action.name}
|
|
155
|
+
</Button>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={handleDismiss}
|
|
161
|
+
aria-label="Dismiss alert"
|
|
162
|
+
className="pb:shrink-0 pb:p-1 pb:rounded-lg pb:opacity-70 pb:hover:opacity-100 pb:transition-opacity pb:cursor-pointer pb:self-start pb:-mr-1"
|
|
163
|
+
>
|
|
164
|
+
<Icon name="xCircle" className="pb:w-5 pb:h-5" />
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { Dialog, DialogTitle, DialogDescription, DialogClose } from "./Dialog";
|
|
3
|
-
import { Button } from "
|
|
3
|
+
import { Button } from "../forms/Button";
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Dialog> = {
|
|
6
|
-
title: "
|
|
6
|
+
title: "Feedback/Dialog",
|
|
7
7
|
component: Dialog,
|
|
8
8
|
parameters: {
|
|
9
9
|
layout: "centered",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Dialog as BaseDialog } from "@base-ui/react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../utils";
|
|
4
4
|
|
|
5
5
|
export interface DialogProps {
|
|
6
6
|
open?: boolean;
|
|
@@ -34,7 +34,7 @@ export function Dialog({
|
|
|
34
34
|
<BaseDialog.Backdrop className="pb:fixed pb:inset-0 pb:z-50 pb:bg-black/40 pb:transition-all pb:duration-200 data-[state=open]:pb:animate-in data-[state=closed]:pb:animate-out pb:data-[state=closed]:fade-out-0 pb:data-[state=open]:fade-in-0" />
|
|
35
35
|
<BaseDialog.Popup
|
|
36
36
|
className={cn(
|
|
37
|
-
"pb:fixed pb:left-[50%] pb:top-[50%] pb:z-50 pb:grid pb:w-auto pb:max-w-[90vw] pb:translate-x-[-50%] pb:translate-y-[-50%] pb:gap-4 pb:border pb:bg-white pb:p-6 pb:shadow-
|
|
37
|
+
"pb:fixed pb:left-[50%] pb:top-[50%] pb:z-50 pb:grid pb:w-auto pb:max-w-[90vw] pb:translate-x-[-50%] pb:translate-y-[-50%] pb:gap-4 pb:border pb:border-gray-100 pb:bg-white pb:p-6 pb:shadow-2xl pb:duration-200 data-[state=open]:pb:animate-in data-[state=closed]:pb:animate-out data-[state=closed]:pb:fade-out-0 data-[state=open]:pb:fade-in-0 data-[state=closed]:pb:zoom-out-95 data-[state=open]:pb:zoom-in-95 data-[state=closed]:pb:slide-out-to-left-1/2 data-[state=closed]:pb:slide-out-to-top-[48%] data-[state=open]:pb:slide-in-from-left-1/2 data-[state=open]:pb:slide-in-from-top-[48%] pb:rounded-2xl",
|
|
38
38
|
className,
|
|
39
39
|
)}
|
|
40
40
|
>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { EmptyState } from "./EmptyState";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof EmptyState> = {
|
|
5
|
+
title: "Feedback/EmptyState",
|
|
6
|
+
component: EmptyState,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof EmptyState>;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
args: {
|
|
14
|
+
title: "No Items Found",
|
|
15
|
+
description: "You haven't created any items yet. Get started by creating your first item.",
|
|
16
|
+
icon: "document",
|
|
17
|
+
primaryAction: {
|
|
18
|
+
label: "Create Item",
|
|
19
|
+
icon: "plus",
|
|
20
|
+
onClick: () => alert("Create clicked"),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Variations: Story = {
|
|
26
|
+
render: () => (
|
|
27
|
+
<div className="pb:flex pb:flex-col pb:gap-8 pb:p-4">
|
|
28
|
+
<div>
|
|
29
|
+
<span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:block pb:mb-2">With Dual Actions</span>
|
|
30
|
+
<EmptyState
|
|
31
|
+
title="No Content Collections"
|
|
32
|
+
description="You can create and save interactions or acquire content from the marketplace."
|
|
33
|
+
icon="squares"
|
|
34
|
+
primaryAction={{
|
|
35
|
+
label: "Create Interactions",
|
|
36
|
+
variant: "primary",
|
|
37
|
+
icon: "plus",
|
|
38
|
+
onClick: () => {},
|
|
39
|
+
}}
|
|
40
|
+
secondaryAction={{
|
|
41
|
+
label: "Browse Marketplace",
|
|
42
|
+
variant: "secondary",
|
|
43
|
+
icon: "externalLink",
|
|
44
|
+
onClick: () => {},
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:block pb:mb-2">Simple Text Only (No Icon, No Actions)</span>
|
|
50
|
+
<EmptyState
|
|
51
|
+
title="Nothing to display"
|
|
52
|
+
description="Your search did not match any templates."
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
),
|
|
57
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "../forms/Button";
|
|
3
|
+
import { Icon, IconProps } from "../foundations/Icon";
|
|
4
|
+
import { Heading, Text } from "../foundations/Typography";
|
|
5
|
+
import { cn } from "../utils";
|
|
6
|
+
import { ComponentColor, FillVariant } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface EmptyStateAction {
|
|
9
|
+
label: React.ReactNode;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
render?: React.ReactElement;
|
|
12
|
+
variant?: FillVariant;
|
|
13
|
+
color?: ComponentColor;
|
|
14
|
+
icon?: IconProps["name"] | React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EmptyStateProps {
|
|
18
|
+
title: React.ReactNode;
|
|
19
|
+
description?: React.ReactNode;
|
|
20
|
+
icon?: IconProps["name"] | React.ReactNode;
|
|
21
|
+
primaryAction?: EmptyStateAction;
|
|
22
|
+
secondaryAction?: EmptyStateAction;
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function EmptyState({
|
|
28
|
+
title,
|
|
29
|
+
description,
|
|
30
|
+
icon,
|
|
31
|
+
primaryAction,
|
|
32
|
+
secondaryAction,
|
|
33
|
+
children,
|
|
34
|
+
className,
|
|
35
|
+
}: EmptyStateProps) {
|
|
36
|
+
const renderIcon = () => {
|
|
37
|
+
if (!icon) return null;
|
|
38
|
+
if (typeof icon === "string") {
|
|
39
|
+
return (
|
|
40
|
+
<Icon
|
|
41
|
+
name={icon as IconProps["name"]}
|
|
42
|
+
className="pb:w-16 pb:h-16 pb:mx-auto pb:text-gray-400 pb:mb-4"
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return <div className="pb:flex pb:justify-center pb:mb-4">{icon}</div>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
"pb:flex pb:flex-col pb:items-center pb:justify-center pb:p-8 pb:text-center pb:rounded-2xl pb:border pb:border-dashed pb:border-gray-200 pb:bg-white/50",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{renderIcon()}
|
|
57
|
+
|
|
58
|
+
{typeof title === "string" ? (
|
|
59
|
+
<Heading
|
|
60
|
+
level={3}
|
|
61
|
+
className="pb:font-bold pb:text-gray-800 pb:mb-2"
|
|
62
|
+
>
|
|
63
|
+
{title}
|
|
64
|
+
</Heading>
|
|
65
|
+
) : (
|
|
66
|
+
title
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{description &&
|
|
70
|
+
(typeof description === "string" ? (
|
|
71
|
+
<Text
|
|
72
|
+
color="muted"
|
|
73
|
+
className="pb:max-w-md pb:mb-6"
|
|
74
|
+
>
|
|
75
|
+
{description}
|
|
76
|
+
</Text>
|
|
77
|
+
) : (
|
|
78
|
+
<div className="pb:text-gray-600 pb:max-w-md pb:mb-6">{description}</div>
|
|
79
|
+
))}
|
|
80
|
+
|
|
81
|
+
{(primaryAction || secondaryAction) && (
|
|
82
|
+
<div className="pb:flex pb:flex-wrap pb:gap-3 pb:justify-center pb:items-center">
|
|
83
|
+
{primaryAction && (
|
|
84
|
+
<Button
|
|
85
|
+
variant={primaryAction.variant || "solid"}
|
|
86
|
+
color={primaryAction.color || "primary"}
|
|
87
|
+
onClick={primaryAction.onClick}
|
|
88
|
+
render={primaryAction.render}
|
|
89
|
+
leftIcon={
|
|
90
|
+
typeof primaryAction.icon === "string" ? (
|
|
91
|
+
<Icon name={primaryAction.icon as IconProps["name"]} />
|
|
92
|
+
) : (
|
|
93
|
+
primaryAction.icon
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
>
|
|
97
|
+
{primaryAction.label}
|
|
98
|
+
</Button>
|
|
99
|
+
)}
|
|
100
|
+
{secondaryAction && (
|
|
101
|
+
<Button
|
|
102
|
+
variant={secondaryAction.variant || "solid"}
|
|
103
|
+
color={secondaryAction.color || "neutral"}
|
|
104
|
+
onClick={secondaryAction.onClick}
|
|
105
|
+
render={secondaryAction.render}
|
|
106
|
+
leftIcon={
|
|
107
|
+
typeof secondaryAction.icon === "string" ? (
|
|
108
|
+
<Icon name={secondaryAction.icon as IconProps["name"]} />
|
|
109
|
+
) : (
|
|
110
|
+
secondaryAction.icon
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
{secondaryAction.label}
|
|
115
|
+
</Button>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{children}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Popover } from "./Popover";
|
|
3
|
+
import { Button } from "../forms/Button";
|
|
4
|
+
import { Input } from "../forms/Input";
|
|
5
|
+
import { Text } from "../foundations/Typography";
|
|
6
|
+
import { Icon } from "../foundations/Icon";
|
|
7
|
+
import React from "react";
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof Popover> = {
|
|
10
|
+
title: "Feedback/Popover",
|
|
11
|
+
component: Popover,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: "centered",
|
|
14
|
+
},
|
|
15
|
+
tags: ["autodocs"],
|
|
16
|
+
argTypes: {
|
|
17
|
+
side: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: ["top", "bottom", "left", "right"],
|
|
20
|
+
},
|
|
21
|
+
align: {
|
|
22
|
+
control: "select",
|
|
23
|
+
options: ["start", "center", "end"],
|
|
24
|
+
},
|
|
25
|
+
variant: {
|
|
26
|
+
control: "select",
|
|
27
|
+
options: ["surface", "glass", "outline", "flat"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof Popover>;
|
|
34
|
+
|
|
35
|
+
export const Default: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
title: "Quick Settings",
|
|
38
|
+
trigger: <Button color="teal">Open Popover</Button>,
|
|
39
|
+
children: (
|
|
40
|
+
<div className="pb:space-y-3 pb:py-1">
|
|
41
|
+
<Text size="xs" color="muted">
|
|
42
|
+
Adjust the active robot connection stream rate.
|
|
43
|
+
</Text>
|
|
44
|
+
<Input placeholder="Rate (Hz)" defaultValue="60" />
|
|
45
|
+
<Button size="sm" color="teal" className="pb:w-full">
|
|
46
|
+
Save Preset
|
|
47
|
+
</Button>
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
side: "bottom",
|
|
51
|
+
align: "center",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const ExportActionsPopover: Story = {
|
|
56
|
+
render: () => (
|
|
57
|
+
<Popover
|
|
58
|
+
title="Export CAD Configuration"
|
|
59
|
+
trigger={
|
|
60
|
+
<Button color="neutral" leftIcon={<Icon name="arrowDownTray" />}>
|
|
61
|
+
Export
|
|
62
|
+
</Button>
|
|
63
|
+
}
|
|
64
|
+
side="bottom"
|
|
65
|
+
align="end"
|
|
66
|
+
>
|
|
67
|
+
<div className="pb:flex pb:flex-col pb:gap-2 pb:w-64 pb:py-1">
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
className="pb:flex pb:items-center pb:gap-2.5 pb:p-2 pb:rounded-xl pb:hover:bg-gray-100 pb:text-left pb:cursor-pointer pb:transition-colors"
|
|
71
|
+
>
|
|
72
|
+
<div className="pb:w-7 pb:h-7 pb:rounded-lg pb:bg-peerbots-teal/15 pb:text-peerbots-darkteal pb:flex pb:items-center pb:justify-center">
|
|
73
|
+
<Icon name="code" className="pb:w-4 pb:h-4" />
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<div className="pb:text-xs pb:font-bold pb:text-gray-900">Download JSON</div>
|
|
77
|
+
<div className="pb:text-[10px] pb:text-gray-500">Raw parameter payload</div>
|
|
78
|
+
</div>
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
className="pb:flex pb:items-center pb:gap-2.5 pb:p-2 pb:rounded-xl pb:hover:bg-gray-100 pb:text-left pb:cursor-pointer pb:transition-colors"
|
|
84
|
+
>
|
|
85
|
+
<div className="pb:w-7 pb:h-7 pb:rounded-lg pb:bg-peerbots-pink/15 pb:text-peerbots-pink pb:flex pb:items-center pb:justify-center">
|
|
86
|
+
<Icon name="cube" className="pb:w-4 pb:h-4" />
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<div className="pb:text-xs pb:font-bold pb:text-gray-900">Export 3MF / STL</div>
|
|
90
|
+
<div className="pb:text-[10px] pb:text-gray-500">3D printable mesh parts</div>
|
|
91
|
+
</div>
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</Popover>
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const GlassPopoverInContext: Story = {
|
|
99
|
+
render: () => (
|
|
100
|
+
<div className="pb:relative pb:w-[500px] pb:h-72 pb:rounded-3xl pb:bg-gradient-to-br pb:from-slate-900 pb:via-indigo-950 pb:to-teal-950 pb:flex pb:items-center pb:justify-center pb:overflow-hidden pb:p-6">
|
|
101
|
+
<div className="pb:text-center pb:text-white/60 pb:text-xs pb:space-y-1">
|
|
102
|
+
<div>3D Robot Viewport Stage</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="pb:absolute pb:top-6 pb:right-6">
|
|
106
|
+
<Popover
|
|
107
|
+
title="Viewport HUD"
|
|
108
|
+
variant="glass"
|
|
109
|
+
trigger={
|
|
110
|
+
<Button variant="outline" color="teal" size="sm">
|
|
111
|
+
Camera HUD
|
|
112
|
+
</Button>
|
|
113
|
+
}
|
|
114
|
+
side="bottom"
|
|
115
|
+
align="end"
|
|
116
|
+
>
|
|
117
|
+
<div className="pb:space-y-2 pb:w-56 pb:py-1">
|
|
118
|
+
<Text size="xs" color="muted">
|
|
119
|
+
Translucent glass popover floating over 3D canvas.
|
|
120
|
+
</Text>
|
|
121
|
+
<div className="pb:flex pb:gap-2">
|
|
122
|
+
<Button size="xs" color="teal" className="pb:flex-1">Reset</Button>
|
|
123
|
+
<Button size="xs" color="neutral" className="pb:flex-1">Snap</Button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</Popover>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
};
|