@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.
Files changed (121) hide show
  1. package/.changeset/config.json +1 -1
  2. package/.github/workflows/storybook.yml +9 -1
  3. package/CHANGELOG.md +94 -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 -27
  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/Button.stories.tsx +0 -108
  113. package/src/ui/Button.tsx +0 -121
  114. package/src/ui/Collapsible.stories.tsx +0 -91
  115. package/src/ui/Collapsible.tsx +0 -52
  116. package/src/ui/Colors.stories.tsx +0 -67
  117. package/src/ui/Popover.tsx +0 -42
  118. package/src/ui/Skeleton.stories.tsx +0 -43
  119. package/src/ui/SliderWithNumberField.stories.tsx +0 -101
  120. package/src/ui/Typography.stories.tsx +0 -87
  121. package/src/ui/Typography.tsx +0 -80
@@ -0,0 +1,148 @@
1
+ import React, { useState } from "react";
2
+ import { cn } from "../utils";
3
+ import { Icon } from "./Icon";
4
+
5
+ export interface CodeBlockProps {
6
+ /** The code string to display */
7
+ code: string;
8
+ /** Programming language for syntax/badge (e.g. bash, tsx, json) */
9
+ language?: string;
10
+ /** Optional file name displayed in the window header */
11
+ fileName?: string;
12
+ /** Whether to show line numbers */
13
+ showLineNumbers?: boolean;
14
+ /** Style variant: 'default' (clean block) or 'window' (macOS window with colored dots) */
15
+ variant?: "default" | "window" | "flat";
16
+ /** Whether to show the copy button */
17
+ showCopyButton?: boolean;
18
+ className?: string;
19
+ }
20
+
21
+ export const CodeBlock: React.FC<CodeBlockProps> = ({
22
+ code = "",
23
+ language,
24
+ fileName,
25
+ showLineNumbers = false,
26
+ variant = "default",
27
+ showCopyButton = true,
28
+ className,
29
+ }) => {
30
+ const [copied, setCopied] = useState(false);
31
+
32
+ const handleCopy = async () => {
33
+ try {
34
+ await navigator.clipboard.writeText(code);
35
+ setCopied(true);
36
+ setTimeout(() => setCopied(false), 2000);
37
+ } catch (err) {
38
+ console.error("Failed to copy code to clipboard", err);
39
+ }
40
+ };
41
+
42
+ const codeLines = code.trim().split("\n");
43
+ const isWindow = variant === "window";
44
+
45
+ return (
46
+ <div
47
+ className={cn(
48
+ "pb:relative pb:group pb:rounded-xl pb:overflow-hidden pb:font-mono pb:text-sm pb:transition-all",
49
+ isWindow
50
+ ? "pb:shadow-xl pb:border pb:border-gray-800 pb:bg-[#1e222b] pb:text-gray-100"
51
+ : variant === "flat"
52
+ ? "pb:bg-[#1e222b]/90 pb:text-gray-100"
53
+ : "pb:border pb:border-gray-800 pb:bg-[#1e222b] pb:text-gray-100 pb:shadow-md",
54
+ className,
55
+ )}
56
+ >
57
+ {/* Window Header Bar (Only in window variant) */}
58
+ {isWindow ? (
59
+ <div className="pb:flex pb:items-center pb:justify-between pb:px-4 pb:py-3 pb:bg-[#161920] pb:border-b pb:border-gray-800/80">
60
+ <div className="pb:flex pb:items-center pb:gap-2">
61
+ <span className="pb:w-3 pb:h-3 pb:rounded-full pb:bg-red-500/80 pb:inline-block" />
62
+ <span className="pb:w-3 pb:h-3 pb:rounded-full pb:bg-yellow-500/80 pb:inline-block" />
63
+ <span className="pb:w-3 pb:h-3 pb:rounded-full pb:bg-green-500/80 pb:inline-block" />
64
+ {fileName && (
65
+ <span className="pb:ml-2 pb:text-xs pb:text-gray-300 pb:font-sans pb:font-medium">
66
+ {fileName}
67
+ </span>
68
+ )}
69
+ </div>
70
+
71
+ <div className="pb:flex pb:items-center pb:gap-3">
72
+ {language && (
73
+ <span className="pb:text-xs pb:uppercase pb:font-bold pb:text-gray-200 pb:bg-gray-800/80 pb:px-2.5 pb:py-0.5 pb:rounded-md">
74
+ {language}
75
+ </span>
76
+ )}
77
+ {showCopyButton && (
78
+ <button
79
+ type="button"
80
+ onClick={handleCopy}
81
+ className="pb:flex pb:items-center pb:gap-1.5 pb:px-3 pb:py-1 pb:text-xs pb:font-semibold pb:text-gray-200 pb:hover:text-white pb:bg-gray-800 pb:hover:bg-gray-700 pb:rounded-lg pb:transition-colors pb:cursor-pointer"
82
+ aria-label="Copy code to clipboard"
83
+ >
84
+ {copied ? (
85
+ <>
86
+ <Icon name="check" className="pb:w-3.5 pb:h-3.5 pb:text-peerbots-teal" />
87
+ <span className="pb:text-peerbots-teal">Copied!</span>
88
+ </>
89
+ ) : (
90
+ <>
91
+ <Icon name="clipboard" className="pb:w-3.5 pb:h-3.5" />
92
+ <span>Copy</span>
93
+ </>
94
+ )}
95
+ </button>
96
+ )}
97
+ </div>
98
+ </div>
99
+ ) : (
100
+ /* Floating Copy & Language badge for default / simple variant */
101
+ (showCopyButton || language) && (
102
+ <div className="pb:absolute pb:top-3 pb:right-3 pb:flex pb:items-center pb:gap-2 pb:z-10">
103
+ {language && (
104
+ <span className="pb:text-[11px] pb:uppercase pb:font-bold pb:text-gray-200 pb:bg-gray-800/80 pb:px-2 pb:py-0.5 pb:rounded-md pb:select-none">
105
+ {language}
106
+ </span>
107
+ )}
108
+ {showCopyButton && (
109
+ <button
110
+ type="button"
111
+ onClick={handleCopy}
112
+ className="pb:p-1.5 pb:rounded-md pb:bg-gray-800/70 pb:hover:bg-gray-700 pb:text-gray-300 pb:hover:text-white pb:transition-all pb:duration-150 pb:cursor-pointer pb:border pb:border-gray-700/50"
113
+ aria-label="Copy code to clipboard"
114
+ title={copied ? "Copied!" : "Copy code"}
115
+ >
116
+ {copied ? (
117
+ <Icon name="check" className="pb:w-4 pb:h-4 pb:text-peerbots-teal" />
118
+ ) : (
119
+ <Icon name="clipboard" className="pb:w-4 pb:h-4" />
120
+ )}
121
+ </button>
122
+ )}
123
+ </div>
124
+ )
125
+ )}
126
+
127
+ {/* Code Block Body */}
128
+ <div className="pb:p-4 sm:pb:p-5 pb:overflow-x-auto pb:text-sm sm:pb:text-base pb:leading-relaxed">
129
+ <pre className="pb:font-mono pb:m-0">
130
+ <code>
131
+ {showLineNumbers ? (
132
+ codeLines.map((line, idx) => (
133
+ <div key={idx} className="pb:table-row">
134
+ <span className="pb:table-cell pb:pr-4 pb:text-right pb:select-none pb:text-gray-400 pb:text-xs">
135
+ {idx + 1}
136
+ </span>
137
+ <span className="pb:table-cell">{line}</span>
138
+ </div>
139
+ ))
140
+ ) : (
141
+ code
142
+ )}
143
+ </code>
144
+ </pre>
145
+ </div>
146
+ </div>
147
+ );
148
+ };
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+
3
+ const meta: Meta = {
4
+ title: "Foundations/Colors",
5
+ tags: ["autodocs"],
6
+ };
7
+
8
+ export default meta;
9
+
10
+ export const Variations: StoryObj = {
11
+ render: () => (
12
+ <div className="pb:flex pb:flex-col pb:gap-8 pb:p-6 pb:bg-white pb:max-w-5xl">
13
+ <div>
14
+ <h3 className="pb:text-sm pb:font-bold pb:text-black pb:uppercase pb:tracking-wider pb:border-b pb:pb-2 pb:mb-4">
15
+ Peerbots Canonical Brand Palette
16
+ </h3>
17
+ <p className="pb:text-xs pb:text-gray-700 pb:mb-4">
18
+ Primary brand colors used across the marketing website, competition runner, and core apps.
19
+ </p>
20
+ <div className="pb:grid pb:grid-cols-2 pb:sm:grid-cols-3 pb:md:grid-cols-4 pb:gap-4">
21
+ <ColorItem name="Peerbots Teal" hex="#5fc7cc" variable="var(--pb-color-peerbots-teal)" role="Primary brand accent" />
22
+ <ColorItem name="Peerbots Pink" hex="#e96c8a" variable="var(--pb-color-peerbots-pink)" role="Secondary brand accent & highlights" />
23
+ <ColorItem name="Dark Teal" hex="#3f8588" variable="var(--pb-color-peerbots-darkteal)" role="High-contrast interactive teal" />
24
+ <ColorItem name="Dark Blue" hex="#2e3a59" variable="var(--pb-color-peerbots-darkblue)" role="Deep brand background / headings" />
25
+ <ColorItem name="Olive" hex="#82871f" variable="var(--pb-color-peerbots-olive)" role="Tertiary earth accent" />
26
+ <ColorItem name="Dark Neutral" hex="#1a1a1a" variable="var(--pb-color-peerbots-dark)" role="Headings and dark surfaces" />
27
+ <ColorItem name="Light Neutral" hex="#f8f9fa" variable="var(--pb-color-peerbots-light)" role="Page & card subtle fills" />
28
+ </div>
29
+ </div>
30
+
31
+ <div>
32
+ <h3 className="pb:text-sm pb:font-bold pb:text-black pb:uppercase pb:tracking-wider pb:border-b pb:pb-2 pb:mb-4">
33
+ Application & Controller Tokens
34
+ </h3>
35
+ <p className="pb:text-xs pb:text-gray-700 pb:mb-4">
36
+ High-energy functional tokens used for controller buttons, sliders, and form active states.
37
+ </p>
38
+ <div className="pb:grid pb:grid-cols-2 pb:sm:grid-cols-3 pb:md:grid-cols-4 pb:gap-4">
39
+ <ColorItem name="Primary (Electric)" hex="#46d9d9" variable="var(--pb-color-primary)" role="Controller buttons & active toggles" />
40
+ <ColorItem name="Dark Primary" hex="#0f766e" variable="var(--pb-color-dark-primary)" role="Hover state for electric primary" />
41
+ <ColorItem name="Secondary (Pink)" hex="#e86e8a" variable="var(--pb-color-secondary)" role="Secondary actions & badges" />
42
+ <ColorItem name="Accent Lime" hex="#d9e021" variable="var(--pb-color-accent)" role="High-energy accent" />
43
+ <ColorItem name="Accent Two (Blue)" hex="#4273ff" variable="var(--pb-color-accent-two)" role="Interactive blue accent" />
44
+ <ColorItem name="Danger" hex="#e86e8a" variable="var(--pb-color-danger)" role="Destructive alerts and buttons" />
45
+ <ColorItem name="Sidebar BG" hex="#f9ffff" variable="var(--pb-sidebar-bg)" role="App shell sidebar background" />
46
+ <ColorItem name="Light BG" hex="#d8e7eb" variable="var(--pb-color-light-bg)" role="Soft container backdrop" />
47
+ </div>
48
+ </div>
49
+ </div>
50
+ ),
51
+ };
52
+
53
+ function ColorItem({
54
+ name,
55
+ hex,
56
+ variable,
57
+ role,
58
+ }: {
59
+ name: string;
60
+ hex: string;
61
+ variable: string;
62
+ role: string;
63
+ }) {
64
+ return (
65
+ <div className="pb:flex pb:flex-col pb:gap-2 pb:p-3 pb:rounded-xl pb:border pb:border-gray-100 pb:bg-gray-50/50">
66
+ <div
67
+ className="pb:h-16 pb:w-full pb:rounded-lg pb:shadow-xs pb:border pb:border-black/5"
68
+ style={{ backgroundColor: variable }}
69
+ />
70
+ <div className="pb:flex pb:flex-col">
71
+ <div className="pb:flex pb:items-center pb:justify-between">
72
+ <span className="pb:text-xs pb:font-bold pb:text-gray-900">{name}</span>
73
+ <span className="pb:text-[10px] pb:font-mono pb:text-gray-600">{hex}</span>
74
+ </div>
75
+ <span className="pb:text-[11px] pb:text-gray-700 pb:mt-0.5">{role}</span>
76
+ <code className="pb:text-[10px] pb:text-gray-600 pb:font-mono pb:mt-1 truncate">{variable}</code>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -3,7 +3,7 @@ import { Icon, IconName } from "./Icon";
3
3
  import { Text } from "./Typography";
4
4
 
5
5
  const meta: Meta<typeof Icon> = {
6
- title: "UI/Icon",
6
+ title: "Foundations/Icon",
7
7
  component: Icon,
8
8
  tags: ["autodocs"],
9
9
  argTypes: {
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { cn } from "./utils";
2
+ import { cn } from "../utils";
3
3
  import { IconName, icons } from "./IconRegistry";
4
4
 
5
5
  export type { IconName };
@@ -90,6 +90,9 @@ export const iconPaths = {
90
90
  "M9 8.25H7.5a2.25 2.25 0 0 0-2.25 2.25v9a2.25 2.25 0 0 0 2.25 2.25h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25H15m0-3-3-3m0 0-3 3m3-3V15",
91
91
  home: "m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25",
92
92
  play: "M5 3l14 9-14 9V3z",
93
+ star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
94
+ shield: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",
95
+ chat: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",
93
96
  };
94
97
 
95
98
  /**
@@ -126,64 +129,265 @@ export const complexIcons = {
126
129
  />
127
130
  </g>
128
131
  ),
132
+ face: (
133
+ <>
134
+ <circle cx="12" cy="12" r="10" />
135
+ <path d="M8 14s1.5 2 4 2 4-2 4-2" />
136
+ <line x1="9" y1="9" x2="9.01" y2="9" strokeWidth="3" />
137
+ <line x1="15" y1="9" x2="15.01" y2="9" strokeWidth="3" />
138
+ </>
139
+ ),
140
+ mic: (
141
+ <>
142
+ <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
143
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
144
+ <line x1="12" y1="19" x2="12" y2="22" />
145
+ </>
146
+ ),
147
+ brain: (
148
+ <>
149
+ <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-2.04Z" />
150
+ <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-2.04Z" />
151
+ </>
152
+ ),
153
+ user: (
154
+ <>
155
+ <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
156
+ <circle cx="12" cy="7" r="4" />
157
+ </>
158
+ ),
159
+ users: (
160
+ <>
161
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
162
+ <circle cx="9" cy="7" r="4" />
163
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87" />
164
+ <path d="M16 3.13a4 4 0 0 1 0 7.75" />
165
+ </>
166
+ ),
167
+ globe: (
168
+ <>
169
+ <circle cx="12" cy="12" r="10" />
170
+ <line x1="2" y1="12" x2="22" y2="12" />
171
+ <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
172
+ </>
173
+ ),
174
+ devices: (
175
+ <>
176
+ <rect width="16" height="10" x="2" y="3" rx="2" />
177
+ <path d="M12 17v4" />
178
+ <path d="M8 21h8" />
179
+ </>
180
+ ),
181
+ volume: (
182
+ <>
183
+ <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
184
+ <path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
185
+ <path d="M19.07 4.93a10 10 0 0 1 0 14.14" />
186
+ </>
187
+ ),
188
+ sliders: (
189
+ <>
190
+ <line x1="4" x2="4" y1="21" y2="14" />
191
+ <line x1="4" x2="4" y1="10" y2="3" />
192
+ <line x1="12" x2="12" y1="21" y2="12" />
193
+ <line x1="12" x2="12" y1="8" y2="3" />
194
+ <line x1="20" x2="20" y1="21" y2="16" />
195
+ <line x1="20" x2="20" y1="12" y2="3" />
196
+ <line x1="1" x2="7" y1="14" y2="14" />
197
+ <line x1="9" x2="15" y1="8" y2="8" />
198
+ <line x1="17" x2="23" y1="16" y2="16" />
199
+ </>
200
+ ),
201
+ joystick: (
202
+ <>
203
+ <circle cx="12" cy="7" r="3" />
204
+ <path d="M12 10v7" />
205
+ <rect width="18" height="4" x="3" y="17" rx="2" />
206
+ </>
207
+ ),
208
+ book: (
209
+ <>
210
+ <path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5Z" />
211
+ <path d="M6 6h10" />
212
+ <path d="M6 10h10" />
213
+ </>
214
+ ),
215
+ store: (
216
+ <>
217
+ <path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7" />
218
+ <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
219
+ <path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4" />
220
+ <path d="M2 7h20" />
221
+ </>
222
+ ),
223
+ award: (
224
+ <>
225
+ <circle cx="12" cy="8" r="7" />
226
+ <polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88" />
227
+ </>
228
+ ),
229
+ headset: (
230
+ <>
231
+ <path d="M3 18v-6a9 9 0 0 1 18 0v6" />
232
+ <path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z" />
233
+ </>
234
+ ),
235
+ layout: (
236
+ <>
237
+ <rect width="18" height="18" x="3" y="3" rx="2" />
238
+ <path d="M3 9h18" />
239
+ <path d="M9 21V9" />
240
+ </>
241
+ ),
242
+ video: (
243
+ <>
244
+ <polygon points="23 7 16 12 23 17 23 7" />
245
+ <rect width="14" height="12" x="1" y="6" rx="2" />
246
+ </>
247
+ ),
248
+ facebook: (
249
+ <path
250
+ fill="currentColor"
251
+ fillRule="evenodd"
252
+ d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"
253
+ clipRule="evenodd"
254
+ />
255
+ ),
256
+ instagram: (
257
+ <path
258
+ fill="currentColor"
259
+ fillRule="evenodd"
260
+ d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"
261
+ clipRule="evenodd"
262
+ />
263
+ ),
264
+ linkedin: (
265
+ <path
266
+ fill="currentColor"
267
+ fillRule="evenodd"
268
+ d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.28 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.75M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"
269
+ clipRule="evenodd"
270
+ />
271
+ ),
272
+ twitter: (
273
+ <path
274
+ fill="currentColor"
275
+ d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
276
+ />
277
+ ),
278
+ youtube: (
279
+ <path
280
+ fill="currentColor"
281
+ fillRule="evenodd"
282
+ d="M21.582 6.186a2.806 2.806 0 0 0-1.974-1.986C17.868 3.75 12 3.75 12 3.75s-5.868 0-7.608.45A2.806 2.806 0 0 0 2.418 6.186C2 7.94 2 12 2 12s0 4.06.418 5.814a2.806 2.806 0 0 0 1.974 1.986c1.74.45 7.608.45 7.608.45s5.868 0 7.608-.45a2.806 2.806 0 0 0 1.974-1.986C22 16.06 22 12 22 12s0-4.06-.418-5.814zM9.75 15.568V8.432L15.818 12 9.75 15.568z"
283
+ clipRule="evenodd"
284
+ />
285
+ ),
286
+ bluesky: (
287
+ <path
288
+ fill="currentColor"
289
+ d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566 1.01 1.5 1.97 1.5 4.5c0 2.054.675 6.643 1.05 7.995.675 2.43 2.7 3.375 4.5 3.375-3.375.9-4.875 3.15-2.7 6.3 3.675 5.325 6.9-2.025 7.65-4.275.75 2.25 3.975 9.6 7.65 4.275 2.175-3.15.675-5.4-2.7-6.3 1.8 0 3.825-.945 4.5-3.375.375-1.352 1.05-5.941 1.05-7.995 0-2.53-1.066-3.49-3.702-1.695C16.046 4.747 13.087 8.686 12 10.8z"
290
+ />
291
+ ),
292
+ github: (
293
+ <path
294
+ fill="currentColor"
295
+ fillRule="evenodd"
296
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
297
+ clipRule="evenodd"
298
+ />
299
+ ),
129
300
  } as const;
130
301
 
131
- export type IconName = keyof typeof iconPaths | keyof typeof complexIcons;
302
+ export type IconName =
303
+ | keyof typeof iconPaths
304
+ | keyof typeof complexIcons
305
+ | "smile"
306
+ | "avatar"
307
+ | "voice"
308
+ | "audio"
309
+ | "speech"
310
+ | "expert"
311
+ | "profile"
312
+ | "world"
313
+ | "language"
314
+ | "screen"
315
+ | "laptop"
316
+ | "sound"
317
+ | "speaker"
318
+ | "tune"
319
+ | "adjust"
320
+ | "message"
321
+ | "team"
322
+ | "group"
323
+ | "motion"
324
+ | "gamepad"
325
+ | "curriculum"
326
+ | "learning"
327
+ | "education"
328
+ | "marketplace"
329
+ | "security"
330
+ | "privacy"
331
+ | "badge"
332
+ | "certificate"
333
+ | "support"
334
+ | "help"
335
+ | "x-brand"
336
+ | "bsky";
132
337
 
133
- export const icons: Record<IconName, React.ReactNode> = {
134
- check: <path d={iconPaths.check} />,
135
- x: <path d={iconPaths.x} />,
136
- edit: <path d={iconPaths.edit} />,
137
- close: <path d={iconPaths.close} />,
138
- plus: <path d={iconPaths.plus} />,
139
- arrowDown: <path d={iconPaths.arrowDown} />,
140
- arrowUp: <path d={iconPaths.arrowUp} />,
141
- clipboard: <path d={iconPaths.clipboard} />,
142
- cloudArrowUp: <path d={iconPaths.cloudArrowUp} />,
143
- arrowDownToLine: <path d={iconPaths.arrowDownToLine} />,
144
- folder: <path d={iconPaths.folder} />,
145
- arrowUpFromLine: <path d={iconPaths.arrowUpFromLine} />,
146
- arrowRightOnRectangle: <path d={iconPaths.arrowRightOnRectangle} />,
147
- squares: <path d={iconPaths.squares} />,
148
- tableCells: <path d={iconPaths.tableCells} />,
149
- arrowsRightLeft: <path d={iconPaths.arrowsRightLeft} />,
150
- document: <path d={iconPaths.document} />,
151
- externalLink: <path d={iconPaths.externalLink} />,
152
- lock: <path d={iconPaths.lock} />,
153
- shoppingCart: <path d={iconPaths.shoppingCart} />,
154
- minus: <path d={iconPaths.minus} />,
155
- pencilSquare: <path d={iconPaths.pencilSquare} />,
156
- pencilSquareContinued: <path d={iconPaths.pencilSquareContinued} />,
157
- chevronDownSmall: <path d={iconPaths.chevronDownSmall} />,
158
- chevronUpSmall: <path d={iconPaths.chevronUpSmall} />,
159
- chevronDownTable: <path d={iconPaths.chevronDownTable} />,
160
- chevronUpTable: <path d={iconPaths.chevronUpTable} />,
161
- speakerWave: <path d={iconPaths.speakerWave} />,
162
- zap: <path d={iconPaths.zap} />,
163
- code: <path d={iconPaths.code} />,
164
- questionMarkCircle: <path d={iconPaths.questionMarkCircle} />,
165
- search: <path d={iconPaths.search} />,
166
- exclamationCircle: <path d={iconPaths.exclamationCircle} />,
167
- exclamationTriangle: <path d={iconPaths.exclamationTriangle} />,
168
- checkCircle: <path d={iconPaths.checkCircle} />,
169
- megaphone: <path d={iconPaths.megaphone} />,
170
- xCircle: <path d={iconPaths.xCircle} />,
171
- arrowsPointingIn: <path d={iconPaths.arrowsPointingIn} />,
172
- arrowsPointingOut: <path d={iconPaths.arrowsPointingOut} />,
173
- arrowPath: <path d={iconPaths.arrowPath} />,
174
- arrowUturnLeft: <path d={iconPaths.arrowUturnLeft} />,
175
- magnifyingGlassPlus: <path d={iconPaths.magnifyingGlassPlus} />,
176
- arrowsUpDown: <path d={iconPaths.arrowsUpDown} />,
177
- envelope: <path d={iconPaths.envelope} />,
178
- lockClosed: <path d={iconPaths.lockClosed} />,
179
- userCircle: <path d={iconPaths.userCircle} />,
180
- chevronRight: <path d={iconPaths.chevronRight} />,
181
- arrowDownTray: <path d={iconPaths.arrowDownTray} />,
182
- arrowUpTray: <path d={iconPaths.arrowUpTray} />,
183
- import: <path d={iconPaths.import} />,
184
- export: <path d={iconPaths.export} />,
185
- home: <path d={iconPaths.home} />,
186
- play: <path d={iconPaths.play} />,
187
- eye: complexIcons.eye,
188
- google: complexIcons.google,
338
+ const iconAliases: Record<string, keyof typeof iconPaths | keyof typeof complexIcons> = {
339
+ smile: "face",
340
+ avatar: "face",
341
+ voice: "mic",
342
+ audio: "mic",
343
+ speech: "mic",
344
+ expert: "brain",
345
+ profile: "user",
346
+ world: "globe",
347
+ language: "globe",
348
+ screen: "devices",
349
+ laptop: "devices",
350
+ sound: "volume",
351
+ speaker: "volume",
352
+ tune: "sliders",
353
+ adjust: "sliders",
354
+ message: "chat",
355
+ team: "users",
356
+ group: "users",
357
+ motion: "joystick",
358
+ gamepad: "joystick",
359
+ curriculum: "book",
360
+ learning: "book",
361
+ education: "book",
362
+ marketplace: "store",
363
+ security: "shield",
364
+ privacy: "shield",
365
+ badge: "award",
366
+ certificate: "award",
367
+ support: "headset",
368
+ help: "headset",
369
+ "x-brand": "twitter",
370
+ bsky: "bluesky",
189
371
  };
372
+
373
+ export const getIconNode = (name: IconName): React.ReactNode => {
374
+ const resolvedName = (iconAliases[name] || name) as
375
+ | keyof typeof iconPaths
376
+ | keyof typeof complexIcons;
377
+
378
+ if (resolvedName in complexIcons) {
379
+ return complexIcons[resolvedName as keyof typeof complexIcons];
380
+ }
381
+ if (resolvedName in iconPaths) {
382
+ return <path d={iconPaths[resolvedName as keyof typeof iconPaths]} />;
383
+ }
384
+ return null;
385
+ };
386
+
387
+ export const icons: Record<string, React.ReactNode> = new Proxy(
388
+ {},
389
+ {
390
+ get: (_, prop: string) => getIconNode(prop as IconName),
391
+ has: () => true,
392
+ },
393
+ );
@@ -0,0 +1,38 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { SocialLinks, DEFAULT_PEERBOTS_SOCIAL_LINKS } from "./SocialLinks";
3
+ import React from "react";
4
+
5
+ const meta: Meta<typeof SocialLinks> = {
6
+ title: "Foundations/SocialLinks",
7
+ component: SocialLinks,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof SocialLinks>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ links: DEFAULT_PEERBOTS_SOCIAL_LINKS,
20
+ size: "md",
21
+ variant: "default",
22
+ },
23
+ };
24
+
25
+ export const Variations: Story = {
26
+ render: () => (
27
+ <div className="pb:flex pb:flex-col pb:gap-6 pb:p-6 pb:bg-slate-900 pb:rounded-2xl">
28
+ <div>
29
+ <p className="pb:text-white/60 pb:text-xs pb:mb-2">White Variant (for dark headers/footers)</p>
30
+ <SocialLinks variant="white" size="md" />
31
+ </div>
32
+ <div>
33
+ <p className="pb:text-white/60 pb:text-xs pb:mb-2">Teal Variant</p>
34
+ <SocialLinks variant="teal" size="md" />
35
+ </div>
36
+ </div>
37
+ ),
38
+ };