@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
@@ -1,8 +1,8 @@
1
1
  import React from "react";
2
2
  import { Slider as BaseSlider } from "@base-ui/react";
3
- import { cn } from "./utils";
3
+ import { cn } from "../utils";
4
4
 
5
- export interface SliderProps {
5
+ export interface SliderProps extends React.AriaAttributes {
6
6
  min?: number;
7
7
  max?: number;
8
8
  step?: number;
@@ -28,6 +28,7 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
28
28
  onChange,
29
29
  name,
30
30
  icon,
31
+ "aria-label": ariaLabel,
31
32
  ...props
32
33
  },
33
34
  ref,
@@ -81,7 +82,10 @@ export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
81
82
  <BaseSlider.Track className="pb:relative pb:bg-gray-200 pb:rounded-full pb:w-full pb:h-1.5 pb:overflow-hidden pb:transition-colors group-pb:hover:bg-gray-300">
82
83
  <BaseSlider.Indicator className="pb:absolute pb:bg-primary pb:rounded-full pb:h-full" />
83
84
  </BaseSlider.Track>
84
- <BaseSlider.Thumb className="pb:z-10 pb:block pb:w-4.5 pb:h-4.5 pb:bg-white pb:shadow-md pb:rounded-full pb:border-2 pb:border-primary focus:pb:outline-none focus:pb:ring-4 focus:pb:ring-primary/20 pb:cursor-grab active:pb:cursor-grabbing pb:hover:scale-110 active:pb:scale-95 pb:transition-transform" />
85
+ <BaseSlider.Thumb
86
+ aria-label={ariaLabel || (name ? name : "Slider")}
87
+ className="pb:z-10 pb:block pb:w-4.5 pb:h-4.5 pb:bg-white pb:shadow-md pb:rounded-full pb:border-2 pb:border-primary focus:pb:outline-none focus:pb:ring-4 focus:pb:ring-primary/20 pb:cursor-grab active:pb:cursor-grabbing pb:hover:scale-110 active:pb:scale-95 pb:transition-transform"
88
+ />
85
89
  </BaseSlider.Control>
86
90
  </BaseSlider.Root>
87
91
  </div>
@@ -0,0 +1,136 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import SliderWithNumberField from "./SliderWithNumberField";
3
+ import React, { useState } from "react";
4
+ import { Heading, Text } from "../foundations/Typography";
5
+
6
+ const meta: Meta<typeof SliderWithNumberField> = {
7
+ title: "Forms/SliderWithNumberField",
8
+ component: SliderWithNumberField,
9
+ argTypes: {
10
+ onChange: { action: "changed" },
11
+ min: { control: { type: "number" } },
12
+ max: { control: { type: "number" } },
13
+ step: { control: { type: "number" } },
14
+ disabled: { control: "boolean" },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof SliderWithNumberField>;
20
+
21
+ const InteractiveSliderWithNumberField = (
22
+ props: React.ComponentProps<typeof SliderWithNumberField>,
23
+ ) => {
24
+ const [value, setValue] = useState(props.value || props.defaultValue || 0);
25
+ return (
26
+ <SliderWithNumberField
27
+ {...props}
28
+ value={value}
29
+ onChange={(v) => {
30
+ setValue(v);
31
+ props.onChange?.(v as number, new Event("change")); // Match signature
32
+ }}
33
+ />
34
+ );
35
+ };
36
+
37
+ export const Default: Story = {
38
+ args: {
39
+ min: 0,
40
+ max: 100,
41
+ defaultValue: 50,
42
+ },
43
+ render: (args) => (
44
+ <div className="pb:p-6 pb:bg-white pb:rounded-2xl pb:border pb:border-gray-200 pb:shadow-xs pb:w-[420px] pb:space-y-2">
45
+ <div className="pb:flex pb:justify-between pb:items-center pb:mb-1">
46
+ <Text size="sm" weight="bold">Output Volume</Text>
47
+ <Text size="xs" color="muted">0 – 100%</Text>
48
+ </div>
49
+ <InteractiveSliderWithNumberField
50
+ aria-label="Output volume"
51
+ {...(args as React.ComponentProps<typeof SliderWithNumberField>)}
52
+ />
53
+ </div>
54
+ ),
55
+ };
56
+
57
+ export const WithStepperButtons: Story = {
58
+ render: () => (
59
+ <div className="pb:p-6 pb:bg-white pb:rounded-2xl pb:border pb:border-gray-200 pb:shadow-xs pb:w-[460px] pb:space-y-2">
60
+ <div className="pb:flex pb:justify-between pb:items-center pb:mb-1">
61
+ <Text size="sm" weight="bold">Head Tilt Angle</Text>
62
+ <Text size="xs" color="muted">-45° to +45°</Text>
63
+ </div>
64
+ <InteractiveSliderWithNumberField
65
+ aria-label="Head tilt angle"
66
+ min={-45}
67
+ max={45}
68
+ defaultValue={0}
69
+ step={1}
70
+ showButtons={true}
71
+ />
72
+ </div>
73
+ ),
74
+ };
75
+
76
+ export const DecimalPrecision: Story = {
77
+ render: () => (
78
+ <div className="pb:p-6 pb:bg-white pb:rounded-2xl pb:border pb:border-gray-200 pb:shadow-xs pb:w-[420px] pb:space-y-2">
79
+ <div className="pb:flex pb:justify-between pb:items-center pb:mb-1">
80
+ <Text size="sm" weight="bold">Speaking Pitch Multiplier</Text>
81
+ <Text size="xs" color="muted">0.5x – 2.0x (step 0.05)</Text>
82
+ </div>
83
+ <InteractiveSliderWithNumberField
84
+ aria-label="Speaking pitch multiplier"
85
+ min={0.5}
86
+ max={2.0}
87
+ step={0.05}
88
+ defaultValue={1.0}
89
+ />
90
+ </div>
91
+ ),
92
+ };
93
+
94
+ export const Variations: Story = {
95
+ render: () => (
96
+ <div className="pb:flex pb:flex-col pb:gap-8 pb:p-4 pb:max-w-lg">
97
+ <div className="pb:space-y-4">
98
+ <span className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:block">
99
+ Robot Motor & Audio Settings
100
+ </span>
101
+ <div className="pb:space-y-6 pb:p-6 pb:bg-gray-50 pb:rounded-2xl pb:border pb:border-gray-200">
102
+ <div className="pb:space-y-1.5">
103
+ <Text size="xs" weight="bold">Motor Sensitivity</Text>
104
+ <InteractiveSliderWithNumberField
105
+ aria-label="Motor sensitivity"
106
+ min={1}
107
+ max={10}
108
+ defaultValue={7}
109
+ />
110
+ </div>
111
+
112
+ <div className="pb:space-y-1.5">
113
+ <Text size="xs" weight="bold">Eye Blink Frequency (Hz)</Text>
114
+ <InteractiveSliderWithNumberField
115
+ aria-label="Eye blink frequency"
116
+ min={0.1}
117
+ max={5.0}
118
+ step={0.1}
119
+ defaultValue={1.2}
120
+ showButtons={true}
121
+ />
122
+ </div>
123
+
124
+ <div className="pb:space-y-1.5">
125
+ <Text size="xs" weight="bold" color="muted">Disabled Axis (Locked)</Text>
126
+ <InteractiveSliderWithNumberField
127
+ aria-label="Locked axis"
128
+ disabled
129
+ defaultValue={0}
130
+ />
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ ),
136
+ };
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { Slider } from "./Slider";
3
3
  import { NumberField } from "./NumberField";
4
- import { cn } from "./utils";
4
+ import { cn } from "../utils";
5
5
 
6
- export interface SliderWithNumberFieldProps {
6
+ export interface SliderWithNumberFieldProps extends React.AriaAttributes {
7
7
  min?: number;
8
8
  max?: number;
9
9
  step?: number;
@@ -13,6 +13,7 @@ export interface SliderWithNumberFieldProps {
13
13
  onChange?: (value: number, event: Event) => void;
14
14
  className?: string;
15
15
  inputWidth?: string;
16
+ showButtons?: boolean;
16
17
  }
17
18
 
18
19
  export const SliderWithNumberField = React.forwardRef<
@@ -29,7 +30,9 @@ export const SliderWithNumberField = React.forwardRef<
29
30
  defaultValue,
30
31
  disabled,
31
32
  onChange,
32
- inputWidth = "w-20",
33
+ inputWidth,
34
+ showButtons = false,
35
+ "aria-label": ariaLabel,
33
36
  ...props
34
37
  },
35
38
  ref,
@@ -41,16 +44,23 @@ export const SliderWithNumberField = React.forwardRef<
41
44
  }
42
45
  };
43
46
 
47
+ const defaultWidth = showButtons ? "pb:w-28" : "pb:w-20";
48
+ const widthClass = inputWidth
49
+ ? inputWidth.startsWith("pb:")
50
+ ? inputWidth
51
+ : `pb:${inputWidth}`
52
+ : defaultWidth;
53
+
44
54
  return (
45
55
  <div
46
56
  ref={ref}
47
57
  className={cn(
48
- "pb:flex pb:flex-row pb:items-center pb:gap-4 pb:w-full pb:min-w-0 pb:sm:flex-nowrap pb:flex-wrap",
58
+ "pb:flex pb:items-center pb:gap-3 pb:w-full pb:min-w-0",
49
59
  className,
50
60
  )}
51
61
  {...props}
52
62
  >
53
- <div className="pb:flex-grow pb:min-w-[120px]">
63
+ <div className="pb:flex-1 pb:min-w-0">
54
64
  <Slider
55
65
  min={min}
56
66
  max={max}
@@ -58,11 +68,12 @@ export const SliderWithNumberField = React.forwardRef<
58
68
  value={value}
59
69
  defaultValue={defaultValue}
60
70
  disabled={disabled}
71
+ aria-label={ariaLabel ? `${ariaLabel} slider` : "Slider value"}
61
72
  onChange={handleChange}
62
73
  className="pb:w-full"
63
74
  />
64
75
  </div>
65
- <div className={cn(inputWidth, "pb:shrink-0")}>
76
+ <div className={cn(widthClass, "pb:shrink-0")}>
66
77
  <NumberField
67
78
  value={value}
68
79
  defaultValue={defaultValue}
@@ -70,12 +81,13 @@ export const SliderWithNumberField = React.forwardRef<
70
81
  max={max}
71
82
  step={step}
72
83
  disabled={disabled}
84
+ aria-label={ariaLabel ? `${ariaLabel} input` : "Numeric input"}
73
85
  onChange={(val, event) => {
74
86
  if (val !== null) {
75
87
  handleChange(val, event);
76
88
  }
77
89
  }}
78
- showButtons={false}
90
+ showButtons={showButtons}
79
91
  />
80
92
  </div>
81
93
  </div>
@@ -1,10 +1,10 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { Switch } from "./Switch";
3
3
  import { useState } from "react";
4
- import { Heading, Text } from "./Typography";
4
+ import { Heading, Text } from "../foundations/Typography";
5
5
 
6
6
  const meta: Meta<typeof Switch> = {
7
- title: "UI/Switch",
7
+ title: "Forms/Switch",
8
8
  component: Switch,
9
9
  tags: ["autodocs"],
10
10
  argTypes: {
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { Switch as BaseSwitch } from "@base-ui/react";
3
- import { cn } from "./utils";
3
+ import { cn } from "../utils";
4
4
 
5
5
  export interface SwitchProps extends React.AriaAttributes {
6
6
  checked?: boolean;
@@ -1,12 +1,12 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { TabRadio, TabRadioOption } from "./TabRadio";
3
3
  import { useState } from "react";
4
- import { Icon } from "./Icon";
5
- import { Heading, Text } from "./Typography";
4
+ import { Icon } from "../foundations/Icon";
5
+ import { Heading, Text } from "../foundations/Typography";
6
6
  import React from "react";
7
7
 
8
8
  const meta = {
9
- title: "UI/TabRadio",
9
+ title: "Forms/TabRadio",
10
10
  component: TabRadio,
11
11
  parameters: {
12
12
  layout: "centered",
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from "react";
2
- import { cn } from "./utils";
2
+ import { cn } from "../utils";
3
3
  import { Toggle, ToggleGroup } from "@base-ui/react";
4
4
 
5
5
  export interface TabRadioOption<T extends string> {
@@ -1,9 +1,9 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { TextArea } from "./TextArea";
3
- import { Heading, Text } from "./Typography";
3
+ import { Heading, Text } from "../foundations/Typography";
4
4
 
5
5
  const meta: Meta<typeof TextArea> = {
6
- title: "UI/TextArea",
6
+ title: "Forms/TextArea",
7
7
  component: TextArea,
8
8
  tags: ["autodocs"],
9
9
  argTypes: {},
@@ -30,13 +30,14 @@ export const Variations: Story = {
30
30
  <Text variant="small" className="pb:font-bold">
31
31
  With Predefined Value
32
32
  </Text>
33
- <TextArea defaultValue="This is some predefined text in the textarea." />
33
+ <TextArea aria-label="Predefined message" defaultValue="This is some predefined text in the textarea." />
34
34
  </div>
35
35
  <div className="pb:space-y-1">
36
36
  <Text variant="small" className="pb:font-bold">
37
37
  Large (10 rows)
38
38
  </Text>
39
39
  <TextArea
40
+ aria-label="Extended description"
40
41
  rows={10}
41
42
  placeholder="A larger textarea for more content..."
42
43
  />
@@ -53,6 +54,7 @@ export const Variations: Story = {
53
54
  Disabled
54
55
  </Text>
55
56
  <TextArea
57
+ aria-label="Disabled message"
56
58
  disabled
57
59
  placeholder="Cannot type here"
58
60
  defaultValue="Read only content"
@@ -1,6 +1,6 @@
1
1
  import { Input as BaseInput } from "@base-ui/react";
2
2
  import React, { ComponentPropsWithoutRef } from "react";
3
- import { cn } from "./utils";
3
+ import { cn } from "../utils";
4
4
 
5
5
  export type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
6
6
 
@@ -0,0 +1,15 @@
1
+ export * from "./Button";
2
+ export * from "./Input";
3
+ export * from "./Select";
4
+ export * from "./Slider";
5
+ export * from "./SliderWithNumberField";
6
+ export * from "./Dropdown";
7
+ export * from "./Switch";
8
+ export * from "./Label";
9
+ export * from "./TabRadio";
10
+ export * from "./NumberField";
11
+ export * from "./Field";
12
+ export * from "./TextArea";
13
+ export * from "./Checkbox";
14
+ export * from "./PeerbotsColorSelector";
15
+ export * from "./ChipGroup";
@@ -0,0 +1,39 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Anchor } from "./Anchor";
3
+ import React from "react";
4
+
5
+ const meta: Meta<typeof Anchor> = {
6
+ title: "Foundations/Anchor",
7
+ component: Anchor,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ tags: ["autodocs"],
12
+ };
13
+
14
+ export default meta;
15
+
16
+ type Story = StoryObj<typeof Anchor>;
17
+
18
+ export const Default: Story = {
19
+ args: {
20
+ children: "Learn more about Peerbots",
21
+ href: "https://peerbots.org",
22
+ target: "_blank",
23
+ color: "teal",
24
+ underline: "hover",
25
+ },
26
+ };
27
+
28
+ export const Variations: Story = {
29
+ render: () => (
30
+ <div className="pb:flex pb:flex-col pb:gap-4 pb:p-6 pb:bg-white pb:max-w-md">
31
+ <Anchor href="#" color="default">Default Link</Anchor>
32
+ <Anchor href="#" color="teal">Teal Brand Link</Anchor>
33
+ <Anchor href="#" color="pink">Pink Brand Link</Anchor>
34
+ <Anchor href="#" color="muted">Muted Help Link</Anchor>
35
+ <Anchor href="#" color="teal" underline="always">Always Underlined Link</Anchor>
36
+ <Anchor href="#" color="default" variant="ghost">Ghost Interactive Link</Anchor>
37
+ </div>
38
+ ),
39
+ };
@@ -0,0 +1,96 @@
1
+ import * as React from "react";
2
+ import { cn } from "../utils";
3
+ import { ComponentColor } from "../types";
4
+
5
+ export interface AnchorProps
6
+ extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
7
+ /**
8
+ * Palette color or semantic intent.
9
+ * @default "default"
10
+ */
11
+ color?: ComponentColor | "muted" | "default";
12
+ /**
13
+ * Underline text decoration control.
14
+ * @default "hover"
15
+ */
16
+ underline?: "hover" | "always" | "none";
17
+ /**
18
+ * Interaction style variant.
19
+ * @default "default"
20
+ */
21
+ variant?: "default" | "ghost";
22
+ /** Polymorphic component wrapper (e.g. react-router Link, Next.js Link) */
23
+ as?: React.ElementType;
24
+ /** Custom render prop */
25
+ render?: (
26
+ props: React.AnchorHTMLAttributes<HTMLAnchorElement>,
27
+ ) => React.ReactElement;
28
+ }
29
+
30
+ export type LinkProps = AnchorProps;
31
+
32
+ const colorClasses: Record<string, string> = {
33
+ default: "pb:text-gray-900 pb:hover:text-peerbots-darkteal",
34
+ neutral: "pb:text-gray-700 pb:hover:text-gray-950",
35
+ muted: "pb:text-gray-600 pb:hover:text-gray-900",
36
+ teal: "pb:text-peerbots-darkteal pb:hover:text-teal-950 pb:font-semibold",
37
+ pink: "pb:text-rose-800 pb:hover:text-rose-950 pb:font-semibold",
38
+ primary: "pb:text-dark-primary pb:hover:text-teal-950 pb:font-semibold",
39
+ darkteal: "pb:text-peerbots-darkteal pb:hover:text-teal-950 pb:font-semibold",
40
+ darkblue: "pb:text-peerbots-darkblue pb:hover:text-teal-900 pb:font-semibold",
41
+ danger: "pb:text-red-700 pb:hover:text-red-900 pb:font-semibold",
42
+ };
43
+
44
+ const underlineClasses = {
45
+ hover: "pb:hover:underline pb:underline-offset-4 pb:decoration-current",
46
+ always: "pb:underline pb:underline-offset-4 pb:decoration-current",
47
+ none: "pb:no-underline",
48
+ };
49
+
50
+ const Anchor = React.forwardRef<HTMLAnchorElement, AnchorProps>(
51
+ (
52
+ {
53
+ className,
54
+ href,
55
+ target,
56
+ color = "default",
57
+ underline = "hover",
58
+ variant = "default",
59
+ as: Component = "a",
60
+ render,
61
+ children,
62
+ ...props
63
+ },
64
+ ref,
65
+ ) => {
66
+ const commonClass = cn(
67
+ "pb:transition-colors pb:duration-150 pb:cursor-pointer pb:font-medium pb:inline-flex pb:items-center pb:gap-1",
68
+ colorClasses[color] || colorClasses.default,
69
+ underlineClasses[underline],
70
+ variant === "ghost" && "pb:px-2 pb:py-1 pb:rounded-lg pb:hover:bg-gray-100",
71
+ className,
72
+ );
73
+
74
+ const anchorProps = {
75
+ href,
76
+ target,
77
+ className: commonClass,
78
+ ...props,
79
+ };
80
+
81
+ if (render) {
82
+ return render(anchorProps);
83
+ }
84
+
85
+ return (
86
+ <Component ref={ref} {...anchorProps}>
87
+ {children}
88
+ </Component>
89
+ );
90
+ },
91
+ );
92
+
93
+ Anchor.displayName = "Anchor";
94
+
95
+ export const Link = Anchor;
96
+ export { Anchor };
@@ -0,0 +1,86 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { CodeBlock } from "./CodeBlock";
3
+ import React from "react";
4
+
5
+ const meta: Meta<typeof CodeBlock> = {
6
+ title: "Foundations/CodeBlock",
7
+ component: CodeBlock,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ };
12
+
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof CodeBlock>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ code: "npm install @peerbots/core",
20
+ language: "bash",
21
+ variant: "default",
22
+ showLineNumbers: false,
23
+ },
24
+ };
25
+
26
+ export const WindowVariant: Story = {
27
+ args: {
28
+ code: `import { Button, Card, Heading } from "@peerbots/core";
29
+
30
+ export function WelcomeCard() {
31
+ return (
32
+ <Card variant="elevated" padding="lg">
33
+ <Heading level={2} variant="marketing-teal">Welcome to Peerbots</Heading>
34
+ <Button variant="marketing-teal">Get Started</Button>
35
+ </Card>
36
+ );
37
+ }`,
38
+ language: "tsx",
39
+ fileName: "WelcomeCard.tsx",
40
+ variant: "window",
41
+ showLineNumbers: true,
42
+ },
43
+ };
44
+
45
+ export const MultiLineWithNumbers: Story = {
46
+ args: {
47
+ code: `const config = {
48
+ apiKey: process.env.PEERBOTS_API_KEY,
49
+ endpoint: "https://api.peerbots.org/v1",
50
+ timeout: 5000,
51
+ };
52
+
53
+ export default config;`,
54
+ language: "typescript",
55
+ variant: "default",
56
+ showLineNumbers: true,
57
+ },
58
+ };
59
+
60
+ export const Variations: Story = {
61
+ render: () => (
62
+ <div className="pb:w-[540px] pb:space-y-6">
63
+ <div>
64
+ <h4 className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:mb-2">
65
+ Standard Clean CodeBlock (Default)
66
+ </h4>
67
+ <CodeBlock
68
+ code="git clone https://github.com/peerbots/peerbots-core.git"
69
+ language="bash"
70
+ />
71
+ </div>
72
+
73
+ <div>
74
+ <h4 className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:mb-2">
75
+ Window / File Box Variant
76
+ </h4>
77
+ <CodeBlock
78
+ code="echo 'Running robot face session...'"
79
+ language="bash"
80
+ fileName="start.sh"
81
+ variant="window"
82
+ />
83
+ </div>
84
+ </div>
85
+ ),
86
+ };