@peerbots/core 0.2.5 → 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.
Files changed (123) hide show
  1. package/.changeset/config.json +1 -1
  2. package/.github/workflows/storybook.yml +9 -1
  3. package/CHANGELOG.md +100 -0
  4. package/dist/index.d.mts +643 -131
  5. package/dist/index.d.ts +643 -131
  6. package/dist/index.js +7 -6
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +7 -6
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +6 -3
  11. package/src/charts/DistributionBarChart.tsx +10 -4
  12. package/src/charts/DistributionHistogram.tsx +13 -5
  13. package/src/charts/LineGraph.stories.tsx +60 -0
  14. package/src/charts/LineGraph.tsx +216 -0
  15. package/src/charts/index.ts +2 -0
  16. package/src/docs/DesignSystem.mdx +241 -25
  17. package/src/styles/theme.css +8 -0
  18. package/src/ui/feedback/Alert.stories.tsx +56 -0
  19. package/src/ui/feedback/Alert.tsx +168 -0
  20. package/src/ui/{Dialog.stories.tsx → feedback/Dialog.stories.tsx} +2 -2
  21. package/src/ui/{Dialog.tsx → feedback/Dialog.tsx} +2 -2
  22. package/src/ui/feedback/EmptyState.stories.tsx +57 -0
  23. package/src/ui/feedback/EmptyState.tsx +123 -0
  24. package/src/ui/feedback/Popover.stories.tsx +130 -0
  25. package/src/ui/feedback/Popover.tsx +135 -0
  26. package/src/ui/feedback/Skeleton.stories.tsx +88 -0
  27. package/src/ui/{Skeleton.tsx → feedback/Skeleton.tsx} +2 -2
  28. package/src/ui/feedback/Spinner.stories.tsx +77 -0
  29. package/src/ui/feedback/Spinner.tsx +62 -0
  30. package/src/ui/{Tooltip.stories.tsx → feedback/Tooltip.stories.tsx} +3 -3
  31. package/src/ui/{Tooltip.tsx → feedback/Tooltip.tsx} +2 -2
  32. package/src/ui/feedback/index.ts +7 -0
  33. package/src/ui/forms/Button.stories.tsx +181 -0
  34. package/src/ui/forms/Button.tsx +323 -0
  35. package/src/ui/{Checkbox.stories.tsx → forms/Checkbox.stories.tsx} +2 -2
  36. package/src/ui/{Checkbox.tsx → forms/Checkbox.tsx} +2 -2
  37. package/src/ui/forms/ChipGroup.stories.tsx +118 -0
  38. package/src/ui/forms/ChipGroup.tsx +239 -0
  39. package/src/ui/{Dropdown.tsx → forms/Dropdown.tsx} +1 -1
  40. package/src/ui/{Field.stories.tsx → forms/Field.stories.tsx} +3 -3
  41. package/src/ui/{Field.tsx → forms/Field.tsx} +1 -1
  42. package/src/ui/{Input.stories.tsx → forms/Input.stories.tsx} +6 -6
  43. package/src/ui/{Input.tsx → forms/Input.tsx} +1 -1
  44. package/src/ui/{Label.stories.tsx → forms/Label.stories.tsx} +2 -2
  45. package/src/ui/{Label.tsx → forms/Label.tsx} +2 -2
  46. package/src/ui/{NumberField.stories.tsx → forms/NumberField.stories.tsx} +2 -2
  47. package/src/ui/{NumberField.tsx → forms/NumberField.tsx} +8 -9
  48. package/src/ui/forms/PeerbotsColorSelector.stories.tsx +49 -0
  49. package/src/ui/forms/PeerbotsColorSelector.tsx +100 -0
  50. package/src/ui/{Select.stories.tsx → forms/Select.stories.tsx} +2 -2
  51. package/src/ui/{Select.tsx → forms/Select.tsx} +5 -3
  52. package/src/ui/{Slider.stories.tsx → forms/Slider.stories.tsx} +6 -4
  53. package/src/ui/{Slider.tsx → forms/Slider.tsx} +7 -3
  54. package/src/ui/forms/SliderWithNumberField.stories.tsx +136 -0
  55. package/src/ui/{SliderWithNumberField.tsx → forms/SliderWithNumberField.tsx} +19 -7
  56. package/src/ui/{Switch.stories.tsx → forms/Switch.stories.tsx} +2 -2
  57. package/src/ui/{Switch.tsx → forms/Switch.tsx} +1 -1
  58. package/src/ui/{TabRadio.stories.tsx → forms/TabRadio.stories.tsx} +3 -3
  59. package/src/ui/{TabRadio.tsx → forms/TabRadio.tsx} +1 -1
  60. package/src/ui/{TextArea.stories.tsx → forms/TextArea.stories.tsx} +5 -3
  61. package/src/ui/{TextArea.tsx → forms/TextArea.tsx} +1 -1
  62. package/src/ui/forms/index.ts +15 -0
  63. package/src/ui/foundations/Anchor.stories.tsx +39 -0
  64. package/src/ui/foundations/Anchor.tsx +96 -0
  65. package/src/ui/foundations/CodeBlock.stories.tsx +86 -0
  66. package/src/ui/foundations/CodeBlock.tsx +148 -0
  67. package/src/ui/foundations/Colors.stories.tsx +80 -0
  68. package/src/ui/{Icon.stories.tsx → foundations/Icon.stories.tsx} +1 -1
  69. package/src/ui/{Icon.tsx → foundations/Icon.tsx} +1 -1
  70. package/src/ui/{IconRegistry.tsx → foundations/IconRegistry.tsx} +261 -57
  71. package/src/ui/foundations/SocialLinks.stories.tsx +38 -0
  72. package/src/ui/foundations/SocialLinks.tsx +117 -0
  73. package/src/ui/foundations/Typography.stories.tsx +58 -0
  74. package/src/ui/foundations/Typography.tsx +163 -0
  75. package/src/ui/foundations/index.ts +6 -0
  76. package/src/ui/index.ts +7 -28
  77. package/src/ui/layout/Accordion.stories.tsx +86 -0
  78. package/src/ui/layout/Accordion.tsx +205 -0
  79. package/src/ui/layout/Card.stories.tsx +151 -0
  80. package/src/ui/layout/Card.tsx +124 -0
  81. package/src/ui/layout/Collapsible.stories.tsx +54 -0
  82. package/src/ui/layout/Collapsible.tsx +54 -0
  83. package/src/ui/{Separator.stories.tsx → layout/Separator.stories.tsx} +2 -2
  84. package/src/ui/{Separator.tsx → layout/Separator.tsx} +1 -1
  85. package/src/ui/{SettingsPanel.stories.tsx → layout/SettingsPanel.stories.tsx} +5 -5
  86. package/src/ui/{SettingsPanel.tsx → layout/SettingsPanel.tsx} +33 -29
  87. package/src/ui/layout/index.ts +5 -0
  88. package/src/ui/media/Avatar.stories.tsx +89 -0
  89. package/src/ui/media/Avatar.tsx +122 -0
  90. package/src/ui/media/Dropzone.stories.tsx +95 -0
  91. package/src/ui/media/Dropzone.tsx +328 -0
  92. package/src/ui/media/FileUpload.stories.tsx +62 -0
  93. package/src/ui/media/FileUpload.tsx +175 -0
  94. package/src/ui/media/index.ts +3 -0
  95. package/src/ui/{AuthFormUI.stories.tsx → patterns/AuthFormUI.stories.tsx} +1 -1
  96. package/src/ui/{AuthFormUI.tsx → patterns/AuthFormUI.tsx} +24 -23
  97. package/src/ui/patterns/CopyButton.stories.tsx +77 -0
  98. package/src/ui/patterns/CopyButton.tsx +93 -0
  99. package/src/ui/patterns/SearchInput.stories.tsx +51 -0
  100. package/src/ui/patterns/SearchInput.tsx +93 -0
  101. package/src/ui/patterns/Stepper.stories.tsx +129 -0
  102. package/src/ui/patterns/Stepper.tsx +295 -0
  103. package/src/ui/{TabSelection.stories.tsx → patterns/TabSelection.stories.tsx} +1 -1
  104. package/src/ui/{TabSelection.tsx → patterns/TabSelection.tsx} +2 -2
  105. package/src/ui/patterns/index.ts +6 -0
  106. package/src/ui/patterns/useClipboard.ts +75 -0
  107. package/src/ui/types.ts +48 -0
  108. package/src/ui/Alert.stories.tsx +0 -41
  109. package/src/ui/Alert.tsx +0 -72
  110. package/src/ui/Anchor.stories.tsx +0 -25
  111. package/src/ui/Anchor.tsx +0 -32
  112. package/src/ui/BasePanel.stories.tsx +0 -36
  113. package/src/ui/BasePanel.tsx +0 -59
  114. package/src/ui/Button.stories.tsx +0 -108
  115. package/src/ui/Button.tsx +0 -121
  116. package/src/ui/Collapsible.stories.tsx +0 -91
  117. package/src/ui/Collapsible.tsx +0 -52
  118. package/src/ui/Colors.stories.tsx +0 -67
  119. package/src/ui/Popover.tsx +0 -42
  120. package/src/ui/Skeleton.stories.tsx +0 -43
  121. package/src/ui/SliderWithNumberField.stories.tsx +0 -101
  122. package/src/ui/Typography.stories.tsx +0 -87
  123. 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.2.5",
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/peerbots/peerbots-core.git"
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="img" aria-label={alt}>
46
+ <div className="pb:mb-6" role="region" aria-label={alt}>
47
47
  {label && (
48
- <label className="pb:text-xs pb:uppercase pb:font-bold pb:text-gray-400 pb:block pb:mb-2 pb:px-1">
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
- </label>
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={<VictoryVoronoiContainer />}
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="img" aria-label={alt}>
56
- <label className="pb:text-xs pb:uppercase pb:font-bold pb:text-gray-400 pb:block pb:mb-2 pb:px-1">
57
- {label}
58
- </label>
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={<VictoryVoronoiContainer />}
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
+ }
@@ -8,3 +8,5 @@ export type {
8
8
  DistributionHistogramProps,
9
9
  DistributionHistogramData,
10
10
  } from "./DistributionHistogram";
11
+ export { LineGraph } from "./LineGraph";
12
+ export type { LineGraphProps, LineGraphPoint } from "./LineGraph";
@@ -1,46 +1,262 @@
1
- import { Meta, Canvas, Story } from "@storybook/addon-docs/blocks";
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
- ## Colors
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
+ &lt;Button color="primary"&gt;Save Changes&lt;/Button&gt;
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
+ &lt;Button color="neutral"&gt;Cancel&lt;/Button&gt;
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
+ &lt;Button color="danger"&gt;Delete Robot&lt;/Button&gt;
105
+ </code>
106
+ </div>
8
107
 
9
- These colors are defined in `src/styles/theme.css`. and are used throughout the application via Tailwind classes.
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
+ &lt;Button color="danger" variant="ghost"&gt;Remove&lt;/Button&gt;
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
+ &lt;Button color="teal" radius="pill" size="lg"&gt;Get Started&lt;/Button&gt;
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
+ &lt;Card variant="glass" padding="sm"&gt;...&lt;/Card&gt;
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
+ &lt;Card variant="surface" padding="md"&gt;...&lt;/Card&gt;
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
- ## Typography
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
- # Peerbots Design System
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
- The Peerbots design system ensures a consistent, friendly, and accessible experience across the platforms.
197
+ ## 6. Disclosures: Accordion vs. Collapsible
23
198
 
24
- ## Design Philosophy
199
+ Both components provide smooth, hardware-accelerated expanding and collapsing animations, but serve different architectural purposes:
25
200
 
26
- - **Friendly & Approachable**: Use rounded corners, soft shadows, and vibrant colors.
27
- - **Accessible**: High contrast for critical buttons and readable font sizes.
28
- - **Responsive**: Adapts gracefully to various screen sizes, from phones to tablets.
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
- ### Utilities
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>&lt;Collapsible title="..."&gt;...&lt;/Collapsible&gt;</code>).</div>
226
+ <div>• <strong>Composable</strong>: Embedded inside <code>SettingsPanel</code> or cards.</div>
227
+ </div>
228
+ </div>
229
+ </div>
31
230
 
32
- Use these common utility classes for consistency:
231
+ <Canvas of={AccordionStories.Default} />
33
232
 
34
- - `btn-primary`: For the main action on a page/modal.
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
- ## Evolving the System
235
+ ## 7. Do's and Don'ts
41
236
 
42
- This design system is a living document.
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
- - **New Needs**: When new features require UI elements not covered here, agents and developers should propose a new pattern.
45
- - **Documentation**: Any new repeatable pattern, component, or utility introduced (especially if driven by user needs) **must** be added to this document to remain the source of truth.
46
- - **Refinement**: Periodic audits should be conducted to ensure hardcoded styles are migrated to the design system.
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>