@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/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "1.0.0",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"style": "./dist/index.css",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"author": "Peerbots",
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/
|
|
38
|
+
"url": "git+https://github.com/PEERbots/peerbots-core.git"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"@base-ui/react": "^1.2.0",
|
|
53
53
|
"react": "^19.2.4",
|
|
54
54
|
"react-dom": "^19.2.4",
|
|
55
|
-
"react-router-dom": "^7",
|
|
56
55
|
"tailwindcss": "^4.2.0",
|
|
57
56
|
"victory": "^37.3.6"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
59
|
+
"@base-ui/react": "^1.7.0",
|
|
60
60
|
"@changesets/changelog-github": "^0.6.0",
|
|
61
61
|
"@changesets/cli": "^2.30.0",
|
|
62
62
|
"@chromatic-com/storybook": "^5.1.1",
|
|
@@ -76,9 +76,12 @@
|
|
|
76
76
|
"postcss": "^8.5.8",
|
|
77
77
|
"react": "^19.2.4",
|
|
78
78
|
"react-dom": "^19.2.4",
|
|
79
|
+
"react-router-dom": "^7.18.3",
|
|
79
80
|
"storybook": "^10.3.3",
|
|
81
|
+
"tailwindcss": "^4.3.3",
|
|
80
82
|
"tsup": "^8.5.1",
|
|
81
83
|
"typescript": "^6.0.2",
|
|
84
|
+
"victory": "^37.3.6",
|
|
82
85
|
"vitest": "^4.1.2"
|
|
83
86
|
},
|
|
84
87
|
"description": "The core shared components, styles and helpers for Peerbots web applications.",
|
|
@@ -43,17 +43,23 @@ export function DistributionBarChart({
|
|
|
43
43
|
if (data.length === 0) return null;
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
|
-
<div className="pb:mb-6" role="
|
|
46
|
+
<div className="pb:mb-6" role="region" aria-label={alt}>
|
|
47
47
|
{label && (
|
|
48
|
-
<
|
|
48
|
+
<span className="pb:text-xs pb:uppercase pb:font-bold pb:text-gray-700 pb:block pb:mb-2 pb:px-1">
|
|
49
49
|
{label}
|
|
50
|
-
</
|
|
50
|
+
</span>
|
|
51
51
|
)}
|
|
52
52
|
<VictoryChart
|
|
53
53
|
width={chartWidth}
|
|
54
54
|
height={chartHeight}
|
|
55
55
|
padding={{ left: 100, right: 50, top: 40, bottom: 60 }}
|
|
56
|
-
containerComponent={
|
|
56
|
+
containerComponent={
|
|
57
|
+
<VictoryVoronoiContainer
|
|
58
|
+
title={alt}
|
|
59
|
+
desc={alt}
|
|
60
|
+
aria-label={alt}
|
|
61
|
+
/>
|
|
62
|
+
}
|
|
57
63
|
>
|
|
58
64
|
<VictoryAxis
|
|
59
65
|
horizontal
|
|
@@ -52,15 +52,23 @@ export function DistributionHistogram({
|
|
|
52
52
|
const padding = { left: 60, right: 30, top: 20, bottom: 70 };
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
|
-
<div className="pb:mb-6" role="
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
<div className="pb:mb-6" role="region" aria-label={alt}>
|
|
56
|
+
{label && (
|
|
57
|
+
<span className="pb:text-xs pb:uppercase pb:font-bold pb:text-gray-700 pb:block pb:mb-2 pb:px-1">
|
|
58
|
+
{label}
|
|
59
|
+
</span>
|
|
60
|
+
)}
|
|
59
61
|
<VictoryChart
|
|
60
62
|
width={chartWidth}
|
|
61
63
|
height={chartHeight}
|
|
62
64
|
padding={padding}
|
|
63
|
-
containerComponent={
|
|
65
|
+
containerComponent={
|
|
66
|
+
<VictoryVoronoiContainer
|
|
67
|
+
title={alt}
|
|
68
|
+
desc={alt}
|
|
69
|
+
aria-label={alt}
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
64
72
|
>
|
|
65
73
|
<VictoryAxis
|
|
66
74
|
label="Length"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { LineGraph } from "./LineGraph";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof LineGraph> = {
|
|
5
|
+
title: "Charts/LineGraph",
|
|
6
|
+
component: LineGraph,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof LineGraph>;
|
|
11
|
+
|
|
12
|
+
const sampleWaveform = Array.from({ length: 40 }, (_, i) => ({
|
|
13
|
+
x: i * 25,
|
|
14
|
+
y: Math.max(0, Math.sin(i / 3) * 0.8 + Math.random() * 0.2),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const sampleExtrema = [
|
|
18
|
+
{ x: 100, y: 0.85, color: "#ef4444", label: "Peak: 0.85 at 100ms" },
|
|
19
|
+
{ x: 400, y: 0.92, color: "#ef4444", label: "Peak: 0.92 at 400ms" },
|
|
20
|
+
{ x: 750, y: 0.78, color: "#10b981", label: "Dip: 0.78 at 750ms" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const Default: Story = {
|
|
24
|
+
args: {
|
|
25
|
+
data: sampleWaveform,
|
|
26
|
+
highlightPoints: sampleExtrema,
|
|
27
|
+
height: 180,
|
|
28
|
+
strokeColor: "#3b82f6",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Variations: Story = {
|
|
33
|
+
render: () => (
|
|
34
|
+
<div className="pb:flex pb:flex-col pb:gap-6 pb:p-4">
|
|
35
|
+
<div>
|
|
36
|
+
<span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:block pb:mb-2">With Area Fill</span>
|
|
37
|
+
<LineGraph
|
|
38
|
+
data={sampleWaveform}
|
|
39
|
+
fillColor="#60a5fa"
|
|
40
|
+
strokeColor="#2563eb"
|
|
41
|
+
height={160}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:block pb:mb-2">Teal Brand Theme</span>
|
|
46
|
+
<LineGraph
|
|
47
|
+
data={sampleWaveform}
|
|
48
|
+
strokeColor="#0f766e"
|
|
49
|
+
fillColor="#46d9d9"
|
|
50
|
+
height={140}
|
|
51
|
+
xUnit="s"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:block pb:mb-2">Empty State (No Data)</span>
|
|
56
|
+
<LineGraph data={[]} height={120} />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "../ui/utils";
|
|
3
|
+
|
|
4
|
+
export interface LineGraphPoint {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LineGraphHighlightPoint extends LineGraphPoint {
|
|
10
|
+
color?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LineGraphProps {
|
|
15
|
+
data: LineGraphPoint[];
|
|
16
|
+
highlightPoints?: LineGraphHighlightPoint[];
|
|
17
|
+
minX?: number;
|
|
18
|
+
maxX?: number;
|
|
19
|
+
minY?: number;
|
|
20
|
+
maxY?: number;
|
|
21
|
+
width?: number | string;
|
|
22
|
+
height?: number;
|
|
23
|
+
strokeColor?: string;
|
|
24
|
+
strokeWidth?: number;
|
|
25
|
+
fillColor?: string;
|
|
26
|
+
showGrid?: boolean;
|
|
27
|
+
showAxisLabels?: boolean;
|
|
28
|
+
xUnit?: string;
|
|
29
|
+
padding?: number;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function LineGraph({
|
|
34
|
+
data = [],
|
|
35
|
+
highlightPoints = [],
|
|
36
|
+
minX,
|
|
37
|
+
maxX,
|
|
38
|
+
minY = 0,
|
|
39
|
+
maxY,
|
|
40
|
+
width = "100%",
|
|
41
|
+
height = 200,
|
|
42
|
+
strokeColor = "#3b82f6",
|
|
43
|
+
strokeWidth = 2,
|
|
44
|
+
fillColor,
|
|
45
|
+
showGrid = true,
|
|
46
|
+
showAxisLabels = true,
|
|
47
|
+
xUnit = "ms",
|
|
48
|
+
padding = 24,
|
|
49
|
+
className,
|
|
50
|
+
}: LineGraphProps) {
|
|
51
|
+
const svgWidth = typeof width === "number" ? width : 800;
|
|
52
|
+
const svgHeight = height;
|
|
53
|
+
|
|
54
|
+
const hasData = data && data.length > 0;
|
|
55
|
+
|
|
56
|
+
const calculatedMinX =
|
|
57
|
+
minX ?? (hasData ? Math.min(...data.map((d) => d.x)) : 0);
|
|
58
|
+
const calculatedMaxX =
|
|
59
|
+
maxX ?? (hasData ? Math.max(...data.map((d) => d.x)) : 1000);
|
|
60
|
+
const xSpan = Math.max(calculatedMaxX - calculatedMinX, 1);
|
|
61
|
+
|
|
62
|
+
const calculatedMinY =
|
|
63
|
+
minY ?? (hasData ? Math.min(...data.map((d) => d.y)) : 0);
|
|
64
|
+
const calculatedMaxY =
|
|
65
|
+
maxY ?? (hasData ? Math.max(...data.map((d) => d.y)) : 1);
|
|
66
|
+
const ySpan = Math.max(calculatedMaxY - calculatedMinY, 0.001);
|
|
67
|
+
|
|
68
|
+
const getCanvasX = (x: number) =>
|
|
69
|
+
((x - calculatedMinX) / xSpan) * (svgWidth - 2 * padding) + padding;
|
|
70
|
+
|
|
71
|
+
const getCanvasY = (y: number) =>
|
|
72
|
+
svgHeight -
|
|
73
|
+
padding -
|
|
74
|
+
((y - calculatedMinY) / ySpan) * (svgHeight - 2 * padding);
|
|
75
|
+
|
|
76
|
+
const pathData = hasData
|
|
77
|
+
? data
|
|
78
|
+
.map(
|
|
79
|
+
(p, i) =>
|
|
80
|
+
`${i === 0 ? "M" : "L"} ${getCanvasX(p.x)} ${getCanvasY(p.y)}`,
|
|
81
|
+
)
|
|
82
|
+
.join(" ")
|
|
83
|
+
: `M ${padding} ${getCanvasY(calculatedMinY)} L ${
|
|
84
|
+
svgWidth - padding
|
|
85
|
+
} ${getCanvasY(calculatedMinY)}`;
|
|
86
|
+
|
|
87
|
+
const fillPathData =
|
|
88
|
+
hasData && fillColor
|
|
89
|
+
? `${pathData} L ${getCanvasX(
|
|
90
|
+
data[data.length - 1].x,
|
|
91
|
+
)} ${getCanvasY(calculatedMinY)} L ${getCanvasX(
|
|
92
|
+
data[0].x,
|
|
93
|
+
)} ${getCanvasY(calculatedMinY)} Z`
|
|
94
|
+
: undefined;
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className={cn("pb:relative pb:w-full pb:overflow-x-auto", className)}>
|
|
98
|
+
<svg
|
|
99
|
+
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
|
100
|
+
className="pb:w-full pb:h-auto pb:bg-gray-50 pb:rounded pb:border pb:border-gray-200"
|
|
101
|
+
style={{ minWidth: typeof width === "number" ? `${width}px` : "300px" }}
|
|
102
|
+
>
|
|
103
|
+
{/* Grid and Axes */}
|
|
104
|
+
{showGrid && (
|
|
105
|
+
<>
|
|
106
|
+
{/* X-axis */}
|
|
107
|
+
<line
|
|
108
|
+
x1={padding}
|
|
109
|
+
y1={svgHeight - padding}
|
|
110
|
+
x2={svgWidth - padding}
|
|
111
|
+
y2={svgHeight - padding}
|
|
112
|
+
stroke="#e5e7eb"
|
|
113
|
+
strokeWidth="1"
|
|
114
|
+
/>
|
|
115
|
+
{/* Y-axis */}
|
|
116
|
+
<line
|
|
117
|
+
x1={padding}
|
|
118
|
+
y1={padding}
|
|
119
|
+
x2={padding}
|
|
120
|
+
y2={svgHeight - padding}
|
|
121
|
+
stroke="#e5e7eb"
|
|
122
|
+
strokeWidth="1"
|
|
123
|
+
/>
|
|
124
|
+
{/* Mid Y-grid */}
|
|
125
|
+
<line
|
|
126
|
+
x1={padding}
|
|
127
|
+
y1={getCanvasY((calculatedMinY + calculatedMaxY) / 2)}
|
|
128
|
+
x2={svgWidth - padding}
|
|
129
|
+
y2={getCanvasY((calculatedMinY + calculatedMaxY) / 2)}
|
|
130
|
+
stroke="#f3f4f6"
|
|
131
|
+
strokeWidth="1"
|
|
132
|
+
strokeDasharray="4"
|
|
133
|
+
/>
|
|
134
|
+
</>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Fill Area */}
|
|
138
|
+
{fillPathData && (
|
|
139
|
+
<path d={fillPathData} fill={fillColor} opacity={0.3} />
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{/* Data Path */}
|
|
143
|
+
<path
|
|
144
|
+
d={pathData}
|
|
145
|
+
fill="none"
|
|
146
|
+
stroke={strokeColor}
|
|
147
|
+
strokeWidth={hasData ? strokeWidth : 1}
|
|
148
|
+
strokeLinejoin="round"
|
|
149
|
+
strokeLinecap="round"
|
|
150
|
+
/>
|
|
151
|
+
|
|
152
|
+
{/* Highlight Points */}
|
|
153
|
+
{highlightPoints?.map((p, i) => (
|
|
154
|
+
<circle
|
|
155
|
+
key={i}
|
|
156
|
+
cx={getCanvasX(p.x)}
|
|
157
|
+
cy={getCanvasY(p.y)}
|
|
158
|
+
r="3.5"
|
|
159
|
+
fill={p.color ?? "#ef4444"}
|
|
160
|
+
className="pb:transition-all pb:duration-150 pb:hover:r-5 pb:cursor-crosshair"
|
|
161
|
+
>
|
|
162
|
+
{p.label && <title>{p.label}</title>}
|
|
163
|
+
</circle>
|
|
164
|
+
))}
|
|
165
|
+
|
|
166
|
+
{/* Axis Labels */}
|
|
167
|
+
{showAxisLabels && (
|
|
168
|
+
<>
|
|
169
|
+
{/* Bottom-left X min */}
|
|
170
|
+
<text
|
|
171
|
+
x={padding}
|
|
172
|
+
y={svgHeight - 6}
|
|
173
|
+
fontSize="10"
|
|
174
|
+
fill="#9ca3af"
|
|
175
|
+
textAnchor="start"
|
|
176
|
+
>
|
|
177
|
+
{calculatedMinX}
|
|
178
|
+
{xUnit}
|
|
179
|
+
</text>
|
|
180
|
+
{/* Bottom-right X max */}
|
|
181
|
+
<text
|
|
182
|
+
x={svgWidth - padding}
|
|
183
|
+
y={svgHeight - 6}
|
|
184
|
+
fontSize="10"
|
|
185
|
+
fill="#9ca3af"
|
|
186
|
+
textAnchor="end"
|
|
187
|
+
>
|
|
188
|
+
{calculatedMaxX}
|
|
189
|
+
{xUnit}
|
|
190
|
+
</text>
|
|
191
|
+
{/* Top-left Y max */}
|
|
192
|
+
<text
|
|
193
|
+
x={6}
|
|
194
|
+
y={padding + 4}
|
|
195
|
+
fontSize="10"
|
|
196
|
+
fill="#9ca3af"
|
|
197
|
+
textAnchor="start"
|
|
198
|
+
>
|
|
199
|
+
{calculatedMaxY.toFixed(1)}
|
|
200
|
+
</text>
|
|
201
|
+
{/* Bottom-left Y min */}
|
|
202
|
+
<text
|
|
203
|
+
x={6}
|
|
204
|
+
y={svgHeight - padding}
|
|
205
|
+
fontSize="10"
|
|
206
|
+
fill="#9ca3af"
|
|
207
|
+
textAnchor="start"
|
|
208
|
+
>
|
|
209
|
+
{calculatedMinY.toFixed(1)}
|
|
210
|
+
</text>
|
|
211
|
+
</>
|
|
212
|
+
)}
|
|
213
|
+
</svg>
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
package/src/charts/index.ts
CHANGED
|
@@ -1,46 +1,262 @@
|
|
|
1
|
-
import { Meta, Canvas
|
|
2
|
-
import * as TypographyStories from "../ui/Typography.stories";
|
|
3
|
-
import * as ColorStories from "../ui/Colors.stories";
|
|
1
|
+
import { Meta, Canvas } from "@storybook/addon-docs/blocks";
|
|
2
|
+
import * as TypographyStories from "../ui/foundations/Typography.stories";
|
|
3
|
+
import * as ColorStories from "../ui/foundations/Colors.stories";
|
|
4
|
+
import * as CardStories from "../ui/layout/Card.stories";
|
|
5
|
+
import * as ButtonStories from "../ui/forms/Button.stories";
|
|
6
|
+
import * as AccordionStories from "../ui/layout/Accordion.stories";
|
|
7
|
+
import * as CollapsibleStories from "../ui/layout/Collapsible.stories";
|
|
4
8
|
|
|
5
9
|
<Meta title="Design System" />
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
# Peerbots Design System
|
|
12
|
+
|
|
13
|
+
The Peerbots design system ensures a consistent, accessible, and friendly experience across web applications, robot controllers, visualization tools, and community sites.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Design Governance & Orthogonal Props
|
|
18
|
+
|
|
19
|
+
To guarantee that different developers build cohesive interfaces without fragmented styling, all core components adhere to **Orthogonal Dimensions**:
|
|
20
|
+
|
|
21
|
+
<div className="pb:grid pb:grid-cols-1 md:pb:grid-cols-2 pb:gap-4 pb:my-6">
|
|
22
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-gray-200 pb:bg-white pb:shadow-xs">
|
|
23
|
+
<div className="pb:text-xs pb:font-bold pb:text-peerbots-teal pb:uppercase pb:tracking-wider pb:mb-1">
|
|
24
|
+
1. Fill Style (<code>variant</code>)
|
|
25
|
+
</div>
|
|
26
|
+
<div className="pb:text-sm pb:font-medium pb:text-gray-900 pb:mb-2">
|
|
27
|
+
<code>solid</code> • <code>soft</code> • <code>outline</code> • <code>ghost</code> • <code>link</code>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="pb:text-xs pb:text-gray-600">
|
|
30
|
+
Controls structural background and border treatment across buttons, badges, and alerts.
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-gray-200 pb:bg-white pb:shadow-xs">
|
|
35
|
+
<div className="pb:text-xs pb:font-bold pb:text-peerbots-pink pb:uppercase pb:tracking-wider pb:mb-1">
|
|
36
|
+
2. Palette & Intent (<code>color</code>)
|
|
37
|
+
</div>
|
|
38
|
+
<div className="pb:text-sm pb:font-medium pb:text-gray-900 pb:mb-2">
|
|
39
|
+
<code>primary</code> • <code>neutral</code> • <code>teal</code> • <code>pink</code> • <code>darkblue</code> • <code>danger</code>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="pb:text-xs pb:text-gray-600">
|
|
42
|
+
Semantic intent (primary, neutral, danger) or brand identity color (teal, pink, darkblue).
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-gray-200 pb:bg-white pb:shadow-xs">
|
|
47
|
+
<div className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:mb-1">
|
|
48
|
+
3. Scale (<code>size</code>)
|
|
49
|
+
</div>
|
|
50
|
+
<div className="pb:text-sm pb:font-medium pb:text-gray-900 pb:mb-2">
|
|
51
|
+
<code>xs</code> • <code>sm</code> • <code>md</code> • <code>lg</code> • <code>xl</code>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="pb:text-xs pb:text-gray-600">
|
|
54
|
+
Controls touch target size, typography scale, and inner padding. Default is <code>md</code>.
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-gray-200 pb:bg-white pb:shadow-xs">
|
|
59
|
+
<div className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:mb-1">
|
|
60
|
+
4. Geometry (<code>radius</code>)
|
|
61
|
+
</div>
|
|
62
|
+
<div className="pb:text-sm pb:font-medium pb:text-gray-900 pb:mb-2">
|
|
63
|
+
<code>none</code> • <code>sm</code> • <code>md</code> • <code>lg</code> • <code>2xl</code> • <code>pill</code>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="pb:text-xs pb:text-gray-600">
|
|
66
|
+
Controls border curvature independently of size. Use <code>pill</code> for hero CTAs.
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 2. Standard Component Recipes
|
|
74
|
+
|
|
75
|
+
Follow these official recipes for common UX patterns across all Peerbots apps:
|
|
76
|
+
|
|
77
|
+
<div className="pb:flex pb:flex-col pb:gap-3 pb:my-6">
|
|
78
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
79
|
+
<div>
|
|
80
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Form Submit Action</div>
|
|
81
|
+
<div className="pb:text-xs pb:text-gray-500">High-emphasis electric cyan button</div>
|
|
82
|
+
</div>
|
|
83
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
84
|
+
<Button color="primary">Save Changes</Button>
|
|
85
|
+
</code>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
89
|
+
<div>
|
|
90
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Form Cancel / Back</div>
|
|
91
|
+
<div className="pb:text-xs pb:text-gray-500">Calm, neutral gray secondary button</div>
|
|
92
|
+
</div>
|
|
93
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
94
|
+
<Button color="neutral">Cancel</Button>
|
|
95
|
+
</code>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
99
|
+
<div>
|
|
100
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Destructive Confirmation</div>
|
|
101
|
+
<div className="pb:text-xs pb:text-gray-500">Solid red destructive action</div>
|
|
102
|
+
</div>
|
|
103
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
104
|
+
<Button color="danger">Delete Robot</Button>
|
|
105
|
+
</code>
|
|
106
|
+
</div>
|
|
8
107
|
|
|
9
|
-
|
|
108
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
109
|
+
<div>
|
|
110
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Destructive Dismiss</div>
|
|
111
|
+
<div className="pb:text-xs pb:text-gray-500">Subtle red ghost button</div>
|
|
112
|
+
</div>
|
|
113
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
114
|
+
<Button color="danger" variant="ghost">Remove</Button>
|
|
115
|
+
</code>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
119
|
+
<div>
|
|
120
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Brand Hero CTA</div>
|
|
121
|
+
<div className="pb:text-xs pb:text-gray-500">Pill brand CTA with icon</div>
|
|
122
|
+
</div>
|
|
123
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
124
|
+
<Button color="teal" radius="pill" size="lg">Get Started</Button>
|
|
125
|
+
</code>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
129
|
+
<div>
|
|
130
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Floating 3D HUD Overlay</div>
|
|
131
|
+
<div className="pb:text-xs pb:text-gray-500">Translucent backdrop blur overlay</div>
|
|
132
|
+
</div>
|
|
133
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
134
|
+
<Card variant="glass" padding="sm">...</Card>
|
|
135
|
+
</code>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="pb:flex pb:flex-col sm:pb:flex-row sm:pb:items-center sm:pb:justify-between pb:gap-2 pb:p-4 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/70">
|
|
139
|
+
<div>
|
|
140
|
+
<div className="pb:text-sm pb:font-bold pb:text-gray-900">Standard Settings Panel</div>
|
|
141
|
+
<div className="pb:text-xs pb:text-gray-500">Opaque elevated white container</div>
|
|
142
|
+
</div>
|
|
143
|
+
<code className="pb:text-xs pb:bg-white pb:px-2.5 pb:py-1.5 pb:rounded-lg pb:border pb:border-gray-200 pb:font-mono">
|
|
144
|
+
<Card variant="surface" padding="md">...</Card>
|
|
145
|
+
</code>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<Canvas of={ButtonStories.Recipes} />
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 3. Brand Palette & Color Architecture
|
|
154
|
+
|
|
155
|
+
Defined in `src/styles/theme.css`, our color tokens fall into two primary roles:
|
|
156
|
+
|
|
157
|
+
### Canonical Brand Palette
|
|
158
|
+
- **Peerbots Teal (`#5fc7cc`)**: Primary brand identity, marketing accents, and active focus rings.
|
|
159
|
+
- **Peerbots Pink (`#e96c8a`)**: Secondary brand identity, highlights, badges, and attention-drawing elements.
|
|
160
|
+
- **Dark Teal (`#3f8588`)**: High-contrast interactive states and text on teal tints.
|
|
161
|
+
- **Dark Blue (`#2e3a59`)**: Deep navy for titles, dark cards, and primary typography.
|
|
162
|
+
- **Olive (`#82871f`)**: Earthy tertiary accent.
|
|
163
|
+
|
|
164
|
+
### Application & Controller Tokens
|
|
165
|
+
- **Electric Primary (`#46d9d9`)**: High-energy button fills and active state indicators in the controller.
|
|
166
|
+
- **Dark Primary (`#0f766e`)**: Hover state for electric primary buttons.
|
|
167
|
+
- **Secondary (`#e86e8a`)**: Canonical pink secondary token.
|
|
10
168
|
|
|
11
169
|
<Canvas of={ColorStories.Variations} />
|
|
12
170
|
|
|
13
|
-
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 4. Typography Scale
|
|
14
174
|
|
|
15
|
-
**Font Family**: `Avenir`, `Nunito`, sans-serif
|
|
175
|
+
- **Primary Font Family**: `Avenir`, `Nunito`, `sans-serif`
|
|
176
|
+
- **Monospace Font Family**: `ui-monospace`, `SFMono-Regular`, `Menlo`, `monospace` (used in `CodeBlock` and data readouts)
|
|
16
177
|
|
|
17
178
|
<Canvas of={TypographyStories.Variations} />
|
|
18
179
|
|
|
180
|
+
---
|
|
19
181
|
|
|
20
|
-
|
|
182
|
+
## 5. Surface & Glassmorphism Guidelines
|
|
183
|
+
|
|
184
|
+
### When to Use Glass (<code>variant="glass"</code>)
|
|
185
|
+
- **Floating 3D HUD Toolbars**: Camera controls, zoom buttons, and parameter readouts floating above WebGL / 3D canvases.
|
|
186
|
+
- **Media Overlays**: Controls or title cards positioned directly over images, videos, or dynamic gradient backgrounds.
|
|
187
|
+
- **Floating Header Navigation**: Top navigation bars over scrolling page content.
|
|
188
|
+
|
|
189
|
+
### When NOT to Use Glass
|
|
190
|
+
- **Standard Forms & Text**: Never use glass on flat white or gray backgrounds. The blur is invisible, contrast is reduced, and text legibility suffers. Always use <code>variant="surface"</code> (solid white).
|
|
191
|
+
- **Data Tables & Dense Lists**: Use solid opaque cards with crisp borders.
|
|
192
|
+
|
|
193
|
+
<Canvas of={CardStories.GlassInContext} />
|
|
194
|
+
|
|
195
|
+
---
|
|
21
196
|
|
|
22
|
-
|
|
197
|
+
## 6. Disclosures: Accordion vs. Collapsible
|
|
23
198
|
|
|
24
|
-
|
|
199
|
+
Both components provide smooth, hardware-accelerated expanding and collapsing animations, but serve different architectural purposes:
|
|
25
200
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
201
|
+
<div className="pb:grid pb:grid-cols-1 md:pb:grid-cols-2 pb:gap-4 pb:my-6">
|
|
202
|
+
<div className="pb:p-5 pb:rounded-2xl pb:border pb:border-peerbots-teal/30 pb:bg-white pb:shadow-xs">
|
|
203
|
+
<div className="pb:flex pb:items-center pb:justify-between pb:mb-2">
|
|
204
|
+
<span className="pb:text-base pb:font-bold pb:text-peerbots-darkteal">Accordion</span>
|
|
205
|
+
<span className="pb:text-[10px] pb:font-bold pb:bg-peerbots-teal/15 pb:text-peerbots-darkteal pb:px-2 pb:py-0.5 pb:rounded-full pb:uppercase">Multi-Item Lists</span>
|
|
206
|
+
</div>
|
|
207
|
+
<p className="pb:text-xs pb:text-gray-600 pb:mb-3">
|
|
208
|
+
Use when displaying a <strong>structured list or group of items</strong> (e.g. FAQs, documentation categories, multi-step help centers).
|
|
209
|
+
</p>
|
|
210
|
+
<div className="pb:text-xs pb:text-gray-800 pb:space-y-1 pb:border-t pb:border-gray-100 pb:pt-2">
|
|
211
|
+
<div>• <strong>Data-driven</strong>: Easily passed an <code>items</code> array of question/answer objects.</div>
|
|
212
|
+
<div>• <strong>Mutual Exclusivity</strong>: Controls whether only one item can open at a time (<code>allowMultiple=false</code>).</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
29
215
|
|
|
30
|
-
|
|
216
|
+
<div className="pb:p-5 pb:rounded-2xl pb:border pb:border-peerbots-pink/30 pb:bg-white pb:shadow-xs">
|
|
217
|
+
<div className="pb:flex pb:items-center pb:justify-between pb:mb-2">
|
|
218
|
+
<span className="pb:text-base pb:font-bold pb:text-peerbots-pink">Collapsible</span>
|
|
219
|
+
<span className="pb:text-[10px] pb:font-bold pb:bg-peerbots-pink/15 pb:text-peerbots-pink pb:px-2 pb:py-0.5 pb:rounded-full pb:uppercase">Single Disclosure</span>
|
|
220
|
+
</div>
|
|
221
|
+
<p className="pb:text-xs pb:text-gray-600 pb:mb-3">
|
|
222
|
+
Use when you have a <strong>single standalone expandable section</strong> embedded within a form, settings page, or dialog.
|
|
223
|
+
</p>
|
|
224
|
+
<div className="pb:text-xs pb:text-gray-800 pb:space-y-1 pb:border-t pb:border-gray-100 pb:pt-2">
|
|
225
|
+
<div>• <strong>Arbitrary Children</strong>: Wrap custom form fields, debug logs, or configuration inputs (<code><Collapsible title="...">...</Collapsible></code>).</div>
|
|
226
|
+
<div>• <strong>Composable</strong>: Embedded inside <code>SettingsPanel</code> or cards.</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
31
230
|
|
|
32
|
-
|
|
231
|
+
<Canvas of={AccordionStories.Default} />
|
|
33
232
|
|
|
34
|
-
|
|
35
|
-
- `btn-secondary`: For alternative actions.
|
|
36
|
-
- `btn-danger`: For irreversible or dangerous actions.
|
|
37
|
-
- `input-base`: For text inputs and textareas.
|
|
38
|
-
- `range-primary`: For slider inputs.
|
|
233
|
+
---
|
|
39
234
|
|
|
40
|
-
##
|
|
235
|
+
## 7. Do's and Don'ts
|
|
41
236
|
|
|
42
|
-
|
|
237
|
+
<div className="pb:grid pb:grid-cols-1 md:pb:grid-cols-2 pb:gap-4 pb:my-6">
|
|
238
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-emerald-200 pb:bg-emerald-50/60">
|
|
239
|
+
<div className="pb:text-xs pb:font-bold pb:text-emerald-800 pb:uppercase pb:tracking-wider pb:mb-2">
|
|
240
|
+
✅ Do
|
|
241
|
+
</div>
|
|
242
|
+
<ul className="pb:text-xs pb:space-y-2 pb:text-emerald-950 pb:list-none pb:p-0 pb:m-0">
|
|
243
|
+
<li>• Pair one primary button (<code>color="primary"</code>) with one neutral button (<code>color="neutral"</code>) in dialogs.</li>
|
|
244
|
+
<li>• Use <code>radius="pill"</code> for marketing CTAs and hero actions.</li>
|
|
245
|
+
<li>• Use <code>variant="glass"</code> strictly over 3D canvases, video, or hero gradients.</li>
|
|
246
|
+
<li>• Use <code>size="sm"</code> for dense table actions and toolbars.</li>
|
|
247
|
+
<li>• Use <code>Accordion</code> for multi-question FAQs and <code>Collapsible</code> for single advanced form sections.</li>
|
|
248
|
+
</ul>
|
|
249
|
+
</div>
|
|
43
250
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
251
|
+
<div className="pb:p-4 pb:rounded-xl pb:border pb:border-red-200 pb:bg-red-50/60">
|
|
252
|
+
<div className="pb:text-xs pb:font-bold pb:text-red-800 pb:uppercase pb:tracking-wider pb:mb-2">
|
|
253
|
+
❌ Don't
|
|
254
|
+
</div>
|
|
255
|
+
<ul className="pb:text-xs pb:space-y-2 pb:text-red-950 pb:list-none pb:p-0 pb:m-0">
|
|
256
|
+
<li>• Don't put multiple <code>solid primary</code> buttons side-by-side.</li>
|
|
257
|
+
<li>• Don't use <code>variant="glass"</code> on plain white or gray pages.</li>
|
|
258
|
+
<li>• Don't use raw unconstrained hex colors in component styles.</li>
|
|
259
|
+
<li>• Don't mix multiple conflicting border radii within the same card.</li>
|
|
260
|
+
</ul>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|