@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,135 @@
1
+ import React from "react";
2
+ import { Popover as BasePopover } from "@base-ui/react";
3
+ import { cn } from "../utils";
4
+ import { Icon } from "../foundations/Icon";
5
+ import { SurfaceVariant } from "../types";
6
+
7
+ export interface PopoverProps {
8
+ /** The element that triggers the popover when clicked */
9
+ trigger: React.ReactNode;
10
+ /** Popover body content */
11
+ children: React.ReactNode;
12
+ /** Optional header title */
13
+ title?: React.ReactNode;
14
+ /** Controlled open state */
15
+ open?: boolean;
16
+ /** Initial open state in uncontrolled mode */
17
+ defaultOpen?: boolean;
18
+ /** Callback fired when open state changes */
19
+ onOpenChange?: (open: boolean) => void;
20
+ /** Side where the popover appears */
21
+ side?: "top" | "bottom" | "left" | "right";
22
+ /** Alignment relative to the trigger */
23
+ align?: "start" | "center" | "end";
24
+ /** Distance from the trigger in pixels */
25
+ sideOffset?: number;
26
+ /** Surface visual variant */
27
+ variant?: SurfaceVariant | "default";
28
+ /** Whether to show a close button in the header */
29
+ showCloseButton?: boolean;
30
+ /** Whether to display a pointing arrow */
31
+ showArrow?: boolean;
32
+ className?: string;
33
+ }
34
+
35
+ const variantStyles: Record<string, string> = {
36
+ surface: "pb:bg-white pb:border pb:border-gray-100 pb:shadow-xl",
37
+ default: "pb:bg-white pb:border pb:border-gray-100 pb:shadow-xl",
38
+ glass:
39
+ "pb:bg-white/90 pb:backdrop-blur-2xl pb:border pb:border-white/50 pb:shadow-2xl",
40
+ outline: "pb:bg-white pb:border-2 pb:border-gray-300 pb:shadow-md",
41
+ flat: "pb:bg-gray-50 pb:border pb:border-gray-200 pb:shadow-lg",
42
+ };
43
+
44
+ export function Popover({
45
+ trigger,
46
+ children,
47
+ title,
48
+ open,
49
+ defaultOpen,
50
+ onOpenChange,
51
+ side = "bottom",
52
+ align = "center",
53
+ sideOffset = 8,
54
+ variant = "surface",
55
+ showCloseButton = true,
56
+ showArrow = true,
57
+ className,
58
+ }: PopoverProps) {
59
+ return (
60
+ <BasePopover.Root
61
+ open={open}
62
+ defaultOpen={defaultOpen}
63
+ onOpenChange={(nextOpen) => {
64
+ onOpenChange?.(nextOpen);
65
+ }}
66
+ >
67
+ {React.isValidElement(trigger) ? (
68
+ <BasePopover.Trigger render={trigger as React.ReactElement} />
69
+ ) : (
70
+ <BasePopover.Trigger>{trigger}</BasePopover.Trigger>
71
+ )}
72
+
73
+ <BasePopover.Portal>
74
+ <BasePopover.Positioner
75
+ side={side}
76
+ align={align}
77
+ sideOffset={sideOffset}
78
+ >
79
+ <BasePopover.Popup
80
+ className={cn(
81
+ "pb:z-50 pb:rounded-2xl pb:p-4 pb:min-w-[240px] pb:max-w-sm pb:text-gray-900 pb:transition-all pb:duration-150 data-[state=open]:pb:animate-in data-[state=closed]:pb:animate-out data-[state=closed]:pb:fade-out-0 data-[state=open]:pb:fade-in-0 data-[state=closed]:pb:zoom-out-95 data-[state=open]:pb:zoom-in-95",
82
+ variantStyles[variant] || variantStyles.surface,
83
+ className,
84
+ )}
85
+ >
86
+ {(title || showCloseButton) && (
87
+ <div className="pb:flex pb:items-center pb:justify-between pb:gap-3 pb:border-b pb:border-gray-100 pb:pb-2.5 pb:mb-3">
88
+ {title ? (
89
+ <div className="pb:text-xs pb:font-bold pb:text-gray-900 pb:uppercase pb:tracking-wider">
90
+ {title}
91
+ </div>
92
+ ) : (
93
+ <div />
94
+ )}
95
+
96
+ {showCloseButton && (
97
+ <BasePopover.Close
98
+ aria-label="Close popover"
99
+ className="pb:p-1 pb:rounded-lg pb:text-gray-400 pb:hover:text-gray-700 pb:hover:bg-gray-100 pb:transition-colors pb:cursor-pointer"
100
+ >
101
+ <Icon name="xCircle" className="pb:w-4 pb:h-4" />
102
+ </BasePopover.Close>
103
+ )}
104
+ </div>
105
+ )}
106
+
107
+ <div className="pb:text-sm">{children}</div>
108
+
109
+ {showArrow && (
110
+ <BasePopover.Arrow
111
+ className={cn(
112
+ variant === "glass"
113
+ ? "pb:fill-white/90 pb:stroke-white/50"
114
+ : "pb:fill-white pb:stroke-gray-100",
115
+ )}
116
+ />
117
+ )}
118
+ </BasePopover.Popup>
119
+ </BasePopover.Positioner>
120
+ </BasePopover.Portal>
121
+ </BasePopover.Root>
122
+ );
123
+ }
124
+
125
+ export const PopoverTitle = (
126
+ props: React.ComponentProps<typeof BasePopover.Title>,
127
+ ) => <BasePopover.Title {...props} />;
128
+
129
+ export const PopoverDescription = (
130
+ props: React.ComponentProps<typeof BasePopover.Description>,
131
+ ) => <BasePopover.Description {...props} />;
132
+
133
+ export const PopoverClose = (
134
+ props: React.ComponentProps<typeof BasePopover.Close>,
135
+ ) => <BasePopover.Close {...props} />;
@@ -0,0 +1,88 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { Skeleton } from "./Skeleton";
3
+
4
+ const meta: Meta<typeof Skeleton> = {
5
+ title: "Feedback/Skeleton",
6
+ component: Skeleton,
7
+ tags: ["autodocs"],
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Skeleton>;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <div className="pb:flex pb:items-center pb:gap-4 pb:p-6 pb:bg-white pb:rounded-2xl pb:border pb:border-gray-200 pb:shadow-xs pb:w-[380px]">
19
+ <Skeleton className="pb:h-12 pb:w-12 pb:rounded-full pb:shrink-0" />
20
+ <div className="pb:space-y-2 pb:flex-1">
21
+ <Skeleton className="pb:h-4 pb:w-3/4 pb:rounded-md" />
22
+ <Skeleton className="pb:h-3 pb:w-1/2 pb:rounded-md" />
23
+ </div>
24
+ </div>
25
+ ),
26
+ };
27
+
28
+ export const CardPlaceholder: Story = {
29
+ render: () => (
30
+ <div className="pb:p-6 pb:bg-white pb:rounded-2xl pb:border pb:border-gray-200 pb:shadow-sm pb:w-[320px] pb:space-y-4">
31
+ {/* Media thumbnail */}
32
+ <Skeleton className="pb:h-36 pb:w-full pb:rounded-xl" />
33
+ {/* Content lines */}
34
+ <div className="pb:space-y-2">
35
+ <Skeleton className="pb:h-4 pb:w-4/5 pb:rounded-md" />
36
+ <Skeleton className="pb:h-3 pb:w-full pb:rounded-md" />
37
+ <Skeleton className="pb:h-3 pb:w-2/3 pb:rounded-md" />
38
+ </div>
39
+ {/* Footer action button */}
40
+ <div className="pb:pt-2 pb:flex pb:justify-between pb:items-center">
41
+ <Skeleton className="pb:h-8 pb:w-24 pb:rounded-lg" />
42
+ <Skeleton className="pb:h-8 pb:w-8 pb:rounded-full" />
43
+ </div>
44
+ </div>
45
+ ),
46
+ };
47
+
48
+ export const Variations: Story = {
49
+ render: () => (
50
+ <div className="pb:flex pb:flex-col pb:gap-8 pb:max-w-md">
51
+ <div>
52
+ <span className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:block pb:mb-3">
53
+ Avatar & Profile Header
54
+ </span>
55
+ <div className="pb:flex pb:items-center pb:gap-4">
56
+ <Skeleton className="pb:h-14 pb:w-14 pb:rounded-full" />
57
+ <div className="pb:space-y-2 pb:flex-1">
58
+ <Skeleton className="pb:h-4 pb:w-48 pb:rounded-md" />
59
+ <Skeleton className="pb:h-3 pb:w-32 pb:rounded-md" />
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div>
65
+ <span className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:block pb:mb-3">
66
+ Paragraph Text Lines
67
+ </span>
68
+ <div className="pb:space-y-2.5">
69
+ <Skeleton className="pb:h-3.5 pb:w-full pb:rounded-md" />
70
+ <Skeleton className="pb:h-3.5 pb:w-11/12 pb:rounded-md" />
71
+ <Skeleton className="pb:h-3.5 pb:w-4/5 pb:rounded-md" />
72
+ <Skeleton className="pb:h-3.5 pb:w-3/5 pb:rounded-md" />
73
+ </div>
74
+ </div>
75
+
76
+ <div>
77
+ <span className="pb:text-xs pb:font-bold pb:text-gray-700 pb:uppercase pb:tracking-wider pb:block pb:mb-3">
78
+ Interactive Controls & Buttons
79
+ </span>
80
+ <div className="pb:flex pb:gap-3">
81
+ <Skeleton className="pb:h-10 pb:w-28 pb:rounded-xl" />
82
+ <Skeleton className="pb:h-10 pb:w-28 pb:rounded-xl" />
83
+ <Skeleton className="pb:h-10 pb:w-10 pb:rounded-xl" />
84
+ </div>
85
+ </div>
86
+ </div>
87
+ ),
88
+ };
@@ -1,5 +1,5 @@
1
1
  import { HTMLAttributes } from "react";
2
- import { cn } from "./utils";
2
+ import { cn } from "../utils";
3
3
 
4
4
  export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
5
5
  className?: string;
@@ -8,7 +8,7 @@ export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
8
8
  export function Skeleton({ className, ...props }: SkeletonProps) {
9
9
  return (
10
10
  <div
11
- className={cn("pb:animate-pulse pb:rounded-md pb:bg-gray-200/50", className)}
11
+ className={cn("pb:animate-pulse pb:rounded-md pb:bg-gray-200", className)}
12
12
  {...props}
13
13
  />
14
14
  );
@@ -0,0 +1,77 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Spinner } from "./Spinner";
3
+
4
+ const meta: Meta<typeof Spinner> = {
5
+ title: "Feedback/Spinner",
6
+ component: Spinner,
7
+ argTypes: {
8
+ size: {
9
+ control: "select",
10
+ options: ["xs", "sm", "md", "lg"],
11
+ },
12
+ color: {
13
+ control: "select",
14
+ options: ["primary", "teal", "pink", "neutral", "white"],
15
+ },
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+ type Story = StoryObj<typeof Spinner>;
21
+
22
+ export const Default: Story = {
23
+ args: {
24
+ size: "md",
25
+ color: "teal",
26
+ },
27
+ };
28
+
29
+ export const Sizes: Story = {
30
+ render: () => (
31
+ <div className="pb:flex pb:items-center pb:gap-6">
32
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
33
+ <Spinner size="xs" color="teal" />
34
+ <span className="pb:text-xs pb:text-gray-500">xs</span>
35
+ </div>
36
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
37
+ <Spinner size="sm" color="teal" />
38
+ <span className="pb:text-xs pb:text-gray-500">sm</span>
39
+ </div>
40
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
41
+ <Spinner size="md" color="teal" />
42
+ <span className="pb:text-xs pb:text-gray-500">md</span>
43
+ </div>
44
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
45
+ <Spinner size="lg" color="teal" />
46
+ <span className="pb:text-xs pb:text-gray-500">lg</span>
47
+ </div>
48
+ </div>
49
+ ),
50
+ };
51
+
52
+ export const Colors: Story = {
53
+ render: () => (
54
+ <div className="pb:flex pb:items-center pb:gap-6 pb:p-4 pb:bg-gray-50 pb:rounded-xl">
55
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
56
+ <Spinner size="md" color="primary" />
57
+ <span className="pb:text-xs pb:text-gray-500">primary</span>
58
+ </div>
59
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
60
+ <Spinner size="md" color="teal" />
61
+ <span className="pb:text-xs pb:text-gray-500">teal</span>
62
+ </div>
63
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
64
+ <Spinner size="md" color="pink" />
65
+ <span className="pb:text-xs pb:text-gray-500">pink</span>
66
+ </div>
67
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2">
68
+ <Spinner size="md" color="neutral" />
69
+ <span className="pb:text-xs pb:text-gray-500">neutral</span>
70
+ </div>
71
+ <div className="pb:flex pb:flex-col pb:items-center pb:gap-2 pb:bg-gray-800 pb:p-2 pb:rounded-lg">
72
+ <Spinner size="md" color="white" />
73
+ <span className="pb:text-xs pb:text-white">white</span>
74
+ </div>
75
+ </div>
76
+ ),
77
+ };
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import { cn } from "../utils";
3
+ import { BrandColor, ComponentSize } from "../types";
4
+
5
+ export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ /** Size variant of the spinner */
7
+ size?: ComponentSize;
8
+ /** Color theme of the spinner */
9
+ color?: BrandColor | "primary" | "neutral";
10
+ /** Screen-reader accessible label */
11
+ label?: string;
12
+ }
13
+
14
+ const sizeClasses: Record<ComponentSize, string> = {
15
+ xs: "pb:w-3.5 pb:h-3.5 pb:border-[1.5px]",
16
+ sm: "pb:w-5 pb:h-5 pb:border-2",
17
+ md: "pb:w-8 pb:h-8 pb:border-[2.5px]",
18
+ lg: "pb:w-12 pb:h-12 pb:border-4",
19
+ xl: "pb:w-16 pb:h-16 pb:border-4",
20
+ };
21
+
22
+ const colorClasses: Record<string, string> = {
23
+ primary: "pb:border-primary pb:border-t-transparent",
24
+ teal: "pb:border-peerbots-teal pb:border-t-transparent",
25
+ pink: "pb:border-peerbots-pink pb:border-t-transparent",
26
+ darkteal: "pb:border-peerbots-darkteal pb:border-t-transparent",
27
+ darkblue: "pb:border-peerbots-darkblue pb:border-t-transparent",
28
+ neutral: "pb:border-gray-500 pb:border-t-transparent",
29
+ white: "pb:border-white pb:border-t-transparent",
30
+ };
31
+
32
+ export const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(
33
+ (
34
+ {
35
+ size = "md",
36
+ color = "teal",
37
+ label = "Loading...",
38
+ className,
39
+ ...props
40
+ },
41
+ ref,
42
+ ) => {
43
+ return (
44
+ <div
45
+ ref={ref}
46
+ role="status"
47
+ aria-label={label}
48
+ className={cn(
49
+ "pb:inline-block pb:rounded-full pb:animate-spin pb:shrink-0",
50
+ sizeClasses[size] || sizeClasses.md,
51
+ colorClasses[color] || colorClasses.teal,
52
+ className,
53
+ )}
54
+ {...props}
55
+ >
56
+ <span className="pb:sr-only">{label}</span>
57
+ </div>
58
+ );
59
+ },
60
+ );
61
+
62
+ Spinner.displayName = "Spinner";
@@ -1,10 +1,10 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { Tooltip, HelperTooltip } from "./Tooltip";
3
- import { Button } from "./Button";
4
- import { Heading } from "./Typography";
3
+ import { Button } from "../forms/Button";
4
+ import { Heading } from "../foundations/Typography";
5
5
 
6
6
  const meta: Meta<typeof Tooltip> = {
7
- title: "UI/Tooltip",
7
+ title: "Feedback/Tooltip",
8
8
  component: Tooltip,
9
9
  tags: ["autodocs"],
10
10
  argTypes: {
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { Tooltip as BaseTooltip } from "@base-ui/react";
3
- import { cn } from "./utils";
4
- import { Icon } from "./Icon";
3
+ import { cn } from "../utils";
4
+ import { Icon } from "../foundations/Icon";
5
5
 
6
6
  export interface TooltipProps {
7
7
  content: React.ReactNode;
@@ -0,0 +1,7 @@
1
+ export * from "./Tooltip";
2
+ export * from "./Dialog";
3
+ export * from "./Popover";
4
+ export * from "./Skeleton";
5
+ export * from "./Alert";
6
+ export * from "./EmptyState";
7
+ export * from "./Spinner";
@@ -0,0 +1,181 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "./Button";
3
+ import { Icon } from "../foundations/Icon";
4
+ import { Heading, Text } from "../foundations/Typography";
5
+ import React from "react";
6
+
7
+ const meta: Meta<typeof Button> = {
8
+ title: "Forms/Button",
9
+ component: Button,
10
+ parameters: {
11
+ layout: "centered",
12
+ },
13
+ tags: ["autodocs"],
14
+ argTypes: {
15
+ variant: {
16
+ control: "select",
17
+ options: ["solid", "soft", "outline", "ghost", "link"],
18
+ description: "Visual fill style / treatment",
19
+ },
20
+ color: {
21
+ control: "select",
22
+ options: ["primary", "neutral", "teal", "pink", "darkblue", "danger", "success", "warning"],
23
+ description: "Color palette or semantic intent",
24
+ },
25
+ size: {
26
+ control: "select",
27
+ options: ["xs", "sm", "md", "lg", "xl"],
28
+ description: "Button scale / padding",
29
+ },
30
+ radius: {
31
+ control: "select",
32
+ options: ["none", "sm", "md", "lg", "pill"],
33
+ description: "Border radius geometry",
34
+ },
35
+ disabled: { control: "boolean" },
36
+ isLoading: { control: "boolean" },
37
+ onClick: { action: "clicked" },
38
+ },
39
+ };
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof Button>;
43
+
44
+ export const Default: Story = {
45
+ args: {
46
+ children: "Button",
47
+ variant: "solid",
48
+ color: "primary",
49
+ size: "md",
50
+ radius: "md",
51
+ },
52
+ };
53
+
54
+ export const Recipes: Story = {
55
+ render: () => (
56
+ <div className="pb:flex pb:flex-col pb:gap-8 pb:max-w-3xl">
57
+ <div>
58
+ <Heading level={4} className="pb:text-sm pb:font-bold pb:text-gray-900 pb:mb-1">
59
+ Standard Form Actions Pair
60
+ </Heading>
61
+ <Text size="sm" color="muted" className="pb:mb-3">
62
+ Pair one high-emphasis primary action with one neutral secondary action.
63
+ </Text>
64
+ <div className="pb:flex pb:items-center pb:gap-3">
65
+ <Button color="primary">Save Changes</Button>
66
+ <Button color="neutral">Cancel</Button>
67
+ </div>
68
+ </div>
69
+
70
+ <div>
71
+ <Heading level={4} className="pb:text-sm pb:font-bold pb:text-gray-900 pb:mb-1">
72
+ Brand CTAs (Teal & Pink Pill)
73
+ </Heading>
74
+ <Text size="sm" color="muted" className="pb:mb-3">
75
+ Use explicit brand colors with pill radius for hero and marketing CTAs.
76
+ </Text>
77
+ <div className="pb:flex pb:items-center pb:gap-3">
78
+ <Button color="teal" radius="pill" size="lg" rightIcon={<Icon name="chevronRight" />}>
79
+ Get Started
80
+ </Button>
81
+ <Button color="pink" radius="pill" size="lg">
82
+ Join Competition
83
+ </Button>
84
+ </div>
85
+ </div>
86
+
87
+ <div>
88
+ <Heading level={4} className="pb:text-sm pb:font-bold pb:text-gray-900 pb:mb-1">
89
+ Destructive Action Styles
90
+ </Heading>
91
+ <Text size="sm" color="muted" className="pb:mb-3">
92
+ Solid for final confirmation dialogs; ghost for table row inline actions.
93
+ </Text>
94
+ <div className="pb:flex pb:items-center pb:gap-3">
95
+ <Button color="danger">Delete Robot</Button>
96
+ <Button color="danger" variant="outline">Delete Face</Button>
97
+ <Button color="danger" variant="ghost">Remove</Button>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ ),
102
+ };
103
+
104
+ export const FillVariantsMatrix: Story = {
105
+ render: () => (
106
+ <div className="pb:flex pb:flex-col pb:gap-6 pb:p-6 pb:bg-gray-50 pb:rounded-2xl pb:max-w-4xl">
107
+ <div>
108
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Solid</span>
109
+ <div className="pb:flex pb:flex-wrap pb:gap-3">
110
+ <Button variant="solid" color="primary">Primary</Button>
111
+ <Button variant="solid" color="neutral">Neutral</Button>
112
+ <Button variant="solid" color="teal">Teal</Button>
113
+ <Button variant="solid" color="pink">Pink</Button>
114
+ <Button variant="solid" color="darkblue">Dark Blue</Button>
115
+ <Button variant="solid" color="danger">Danger</Button>
116
+ <Button variant="solid" color="success">Success</Button>
117
+ </div>
118
+ </div>
119
+
120
+ <div>
121
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Soft / Subtle</span>
122
+ <div className="pb:flex pb:flex-wrap pb:gap-3">
123
+ <Button variant="soft" color="primary">Primary</Button>
124
+ <Button variant="soft" color="teal">Teal</Button>
125
+ <Button variant="soft" color="pink">Pink</Button>
126
+ <Button variant="soft" color="darkblue">Dark Blue</Button>
127
+ <Button variant="soft" color="danger">Danger</Button>
128
+ <Button variant="soft" color="success">Success</Button>
129
+ </div>
130
+ </div>
131
+
132
+ <div>
133
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Outline</span>
134
+ <div className="pb:flex pb:flex-wrap pb:gap-3">
135
+ <Button variant="outline" color="neutral">Neutral</Button>
136
+ <Button variant="outline" color="teal">Teal</Button>
137
+ <Button variant="outline" color="pink">Pink</Button>
138
+ <Button variant="outline" color="danger">Danger</Button>
139
+ </div>
140
+ </div>
141
+
142
+ <div>
143
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Ghost</span>
144
+ <div className="pb:flex pb:flex-wrap pb:gap-3">
145
+ <Button variant="ghost" color="neutral">Neutral</Button>
146
+ <Button variant="ghost" color="teal">Teal</Button>
147
+ <Button variant="ghost" color="pink">Pink</Button>
148
+ <Button variant="ghost" color="danger">Danger</Button>
149
+ </div>
150
+ </div>
151
+ </div>
152
+ ),
153
+ };
154
+
155
+ export const SizesAndShapes: Story = {
156
+ render: () => (
157
+ <div className="pb:flex pb:flex-col pb:gap-6 pb:p-6 pb:bg-white pb:rounded-2xl pb:max-w-4xl">
158
+ <div>
159
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Sizes</span>
160
+ <div className="pb:flex pb:flex-wrap pb:items-end pb:gap-3">
161
+ <Button size="xs" color="teal">Extra Small</Button>
162
+ <Button size="sm" color="teal">Small</Button>
163
+ <Button size="md" color="teal">Medium (Default)</Button>
164
+ <Button size="lg" color="teal">Large</Button>
165
+ <Button size="xl" color="teal">Extra Large</Button>
166
+ </div>
167
+ </div>
168
+
169
+ <div>
170
+ <span className="pb:text-xs pb:font-bold pb:text-gray-500 pb:uppercase pb:block pb:mb-2">Border Radii (Shapes)</span>
171
+ <div className="pb:flex pb:flex-wrap pb:items-center pb:gap-3">
172
+ <Button radius="none" color="pink">None (0px)</Button>
173
+ <Button radius="sm" color="pink">Small</Button>
174
+ <Button radius="md" color="pink">Medium (Default)</Button>
175
+ <Button radius="lg" color="pink">Large</Button>
176
+ <Button radius="pill" color="pink">Pill (Full)</Button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ ),
181
+ };