@shipfox/react-ui 0.13.0 → 0.15.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 (268) hide show
  1. package/.storybook/preview.tsx +7 -0
  2. package/.turbo/turbo-build.log +7 -7
  3. package/.turbo/turbo-check.log +2 -2
  4. package/.turbo/turbo-type.log +1 -1
  5. package/CHANGELOG.md +16 -0
  6. package/dist/components/avatar/avatar.js +1 -1
  7. package/dist/components/avatar/avatar.js.map +1 -1
  8. package/dist/components/button-group/button-group.d.ts +17 -0
  9. package/dist/components/button-group/button-group.d.ts.map +1 -0
  10. package/dist/components/button-group/button-group.js +74 -0
  11. package/dist/components/button-group/button-group.js.map +1 -0
  12. package/dist/components/button-group/button-group.stories.js +644 -0
  13. package/dist/components/button-group/button-group.stories.js.map +1 -0
  14. package/dist/components/button-group/index.d.ts +2 -0
  15. package/dist/components/button-group/index.d.ts.map +1 -0
  16. package/dist/components/button-group/index.js +3 -0
  17. package/dist/components/button-group/index.js.map +1 -0
  18. package/dist/components/code-block/code-block-footer.d.ts.map +1 -1
  19. package/dist/components/code-block/code-block-footer.js +13 -5
  20. package/dist/components/code-block/code-block-footer.js.map +1 -1
  21. package/dist/components/command/command.d.ts +28 -0
  22. package/dist/components/command/command.d.ts.map +1 -0
  23. package/dist/components/command/command.js +190 -0
  24. package/dist/components/command/command.js.map +1 -0
  25. package/dist/components/command/command.stories.js +228 -0
  26. package/dist/components/command/command.stories.js.map +1 -0
  27. package/dist/components/command/index.d.ts +2 -0
  28. package/dist/components/command/index.d.ts.map +1 -0
  29. package/dist/components/command/index.js +3 -0
  30. package/dist/components/command/index.js.map +1 -0
  31. package/dist/components/confetti/confetti.d.ts +21 -0
  32. package/dist/components/confetti/confetti.d.ts.map +1 -0
  33. package/dist/components/confetti/confetti.js +101 -0
  34. package/dist/components/confetti/confetti.js.map +1 -0
  35. package/dist/components/confetti/confetti.stories.js +41 -0
  36. package/dist/components/confetti/confetti.stories.js.map +1 -0
  37. package/dist/components/confetti/index.d.ts +2 -0
  38. package/dist/components/confetti/index.d.ts.map +1 -0
  39. package/dist/components/confetti/index.js +3 -0
  40. package/dist/components/confetti/index.js.map +1 -0
  41. package/dist/components/dashboard/components/analytics-content.d.ts +2 -0
  42. package/dist/components/dashboard/components/analytics-content.d.ts.map +1 -0
  43. package/dist/components/dashboard/components/analytics-content.js +180 -0
  44. package/dist/components/dashboard/components/analytics-content.js.map +1 -0
  45. package/dist/components/dashboard/components/animated-logo.d.ts +4 -0
  46. package/dist/components/dashboard/components/animated-logo.d.ts.map +1 -0
  47. package/dist/components/dashboard/components/animated-logo.js +23 -0
  48. package/dist/components/dashboard/components/animated-logo.js.map +1 -0
  49. package/dist/components/dashboard/components/complete-setup-button.d.ts +4 -0
  50. package/dist/components/dashboard/components/complete-setup-button.d.ts.map +1 -0
  51. package/dist/components/dashboard/components/complete-setup-button.js +28 -0
  52. package/dist/components/dashboard/components/complete-setup-button.js.map +1 -0
  53. package/dist/components/dashboard/components/jobs-content.d.ts +2 -0
  54. package/dist/components/dashboard/components/jobs-content.d.ts.map +1 -0
  55. package/dist/components/dashboard/components/jobs-content.js +69 -0
  56. package/dist/components/dashboard/components/jobs-content.js.map +1 -0
  57. package/dist/components/dashboard/components/mobile-menu.d.ts +2 -0
  58. package/dist/components/dashboard/components/mobile-menu.d.ts.map +1 -0
  59. package/dist/components/dashboard/components/mobile-menu.js +65 -0
  60. package/dist/components/dashboard/components/mobile-menu.js.map +1 -0
  61. package/dist/components/dashboard/components/organization-selector.d.ts +2 -0
  62. package/dist/components/dashboard/components/organization-selector.d.ts.map +1 -0
  63. package/dist/components/dashboard/components/organization-selector.js +92 -0
  64. package/dist/components/dashboard/components/organization-selector.js.map +1 -0
  65. package/dist/components/dashboard/components/top-menu.d.ts +5 -0
  66. package/dist/components/dashboard/components/top-menu.d.ts.map +1 -0
  67. package/dist/components/dashboard/components/top-menu.js +31 -0
  68. package/dist/components/dashboard/components/top-menu.js.map +1 -0
  69. package/dist/components/dashboard/components/topbar-button.d.ts +7 -0
  70. package/dist/components/dashboard/components/topbar-button.d.ts.map +1 -0
  71. package/dist/components/dashboard/components/topbar-button.js +18 -0
  72. package/dist/components/dashboard/components/topbar-button.js.map +1 -0
  73. package/dist/components/dashboard/components/topbar.d.ts +4 -0
  74. package/dist/components/dashboard/components/topbar.d.ts.map +1 -0
  75. package/dist/components/dashboard/components/topbar.js +62 -0
  76. package/dist/components/dashboard/components/topbar.js.map +1 -0
  77. package/dist/components/dashboard/components/user-profile.d.ts +2 -0
  78. package/dist/components/dashboard/components/user-profile.d.ts.map +1 -0
  79. package/dist/components/dashboard/components/user-profile.js +146 -0
  80. package/dist/components/dashboard/components/user-profile.js.map +1 -0
  81. package/dist/components/dashboard/dashboard.d.ts +2 -0
  82. package/dist/components/dashboard/dashboard.d.ts.map +1 -0
  83. package/dist/components/dashboard/dashboard.js +70 -0
  84. package/dist/components/dashboard/dashboard.js.map +1 -0
  85. package/dist/components/dashboard/dashboard.stories.js +23 -0
  86. package/dist/components/dashboard/dashboard.stories.js.map +1 -0
  87. package/dist/components/dashboard/index.d.ts +2 -0
  88. package/dist/components/dashboard/index.d.ts.map +1 -0
  89. package/dist/components/dashboard/index.js +3 -0
  90. package/dist/components/dashboard/index.js.map +1 -0
  91. package/dist/components/form/form.stories.js +6 -1
  92. package/dist/components/form/form.stories.js.map +1 -1
  93. package/dist/components/icon/icon.d.ts +3 -2
  94. package/dist/components/icon/icon.d.ts.map +1 -1
  95. package/dist/components/icon/icon.js +7 -2
  96. package/dist/components/icon/icon.js.map +1 -1
  97. package/dist/components/index.d.ts +9 -0
  98. package/dist/components/index.d.ts.map +1 -1
  99. package/dist/components/index.js +9 -0
  100. package/dist/components/index.js.map +1 -1
  101. package/dist/components/kbd/index.d.ts +2 -0
  102. package/dist/components/kbd/index.d.ts.map +1 -0
  103. package/dist/components/kbd/index.js +3 -0
  104. package/dist/components/kbd/index.js.map +1 -0
  105. package/dist/components/kbd/kbd.d.ts +7 -0
  106. package/dist/components/kbd/kbd.d.ts.map +1 -0
  107. package/dist/components/kbd/kbd.js +18 -0
  108. package/dist/components/kbd/kbd.js.map +1 -0
  109. package/dist/components/kbd/kbd.stories.js +119 -0
  110. package/dist/components/kbd/kbd.stories.js.map +1 -0
  111. package/dist/components/modal/modal.stories.js +227 -168
  112. package/dist/components/modal/modal.stories.js.map +1 -1
  113. package/dist/components/search/index.d.ts +7 -0
  114. package/dist/components/search/index.d.ts.map +1 -0
  115. package/dist/components/search/index.js +8 -0
  116. package/dist/components/search/index.js.map +1 -0
  117. package/dist/components/search/search-context.d.ts +11 -0
  118. package/dist/components/search/search-context.d.ts.map +1 -0
  119. package/dist/components/search/search-context.js +56 -0
  120. package/dist/components/search/search-context.js.map +1 -0
  121. package/dist/components/search/search-inline.d.ts +9 -0
  122. package/dist/components/search/search-inline.d.ts.map +1 -0
  123. package/dist/components/search/search-inline.js +85 -0
  124. package/dist/components/search/search-inline.js.map +1 -0
  125. package/dist/components/search/search-modal.d.ts +25 -0
  126. package/dist/components/search/search-modal.d.ts.map +1 -0
  127. package/dist/components/search/search-modal.js +162 -0
  128. package/dist/components/search/search-modal.js.map +1 -0
  129. package/dist/components/search/search-trigger.d.ts +9 -0
  130. package/dist/components/search/search-trigger.d.ts.map +1 -0
  131. package/dist/components/search/search-trigger.js +37 -0
  132. package/dist/components/search/search-trigger.js.map +1 -0
  133. package/dist/components/search/search-variants.d.ts +14 -0
  134. package/dist/components/search/search-variants.d.ts.map +1 -0
  135. package/dist/components/search/search-variants.js +90 -0
  136. package/dist/components/search/search-variants.js.map +1 -0
  137. package/dist/components/search/search.d.ts +11 -0
  138. package/dist/components/search/search.d.ts.map +1 -0
  139. package/dist/components/search/search.js +35 -0
  140. package/dist/components/search/search.js.map +1 -0
  141. package/dist/components/search/search.stories.js +630 -0
  142. package/dist/components/search/search.stories.js.map +1 -0
  143. package/dist/components/select/index.d.ts +2 -0
  144. package/dist/components/select/index.d.ts.map +1 -0
  145. package/dist/components/select/index.js +3 -0
  146. package/dist/components/select/index.js.map +1 -0
  147. package/dist/components/select/select.d.ts +25 -0
  148. package/dist/components/select/select.d.ts.map +1 -0
  149. package/dist/components/select/select.js +153 -0
  150. package/dist/components/select/select.js.map +1 -0
  151. package/dist/components/select/select.stories.js +393 -0
  152. package/dist/components/select/select.stories.js.map +1 -0
  153. package/dist/components/shiny-text/index.d.ts +2 -0
  154. package/dist/components/shiny-text/index.d.ts.map +1 -0
  155. package/dist/components/shiny-text/index.js +3 -0
  156. package/dist/components/shiny-text/index.js.map +1 -0
  157. package/dist/components/shiny-text/shiny-text.d.ts +10 -0
  158. package/dist/components/shiny-text/shiny-text.d.ts.map +1 -0
  159. package/dist/components/shiny-text/shiny-text.js +17 -0
  160. package/dist/components/shiny-text/shiny-text.js.map +1 -0
  161. package/dist/components/skeleton/index.d.ts +2 -0
  162. package/dist/components/skeleton/index.d.ts.map +1 -0
  163. package/dist/components/skeleton/index.js +3 -0
  164. package/dist/components/skeleton/index.js.map +1 -0
  165. package/dist/components/skeleton/skeleton.d.ts +5 -0
  166. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  167. package/dist/components/skeleton/skeleton.js +11 -0
  168. package/dist/components/skeleton/skeleton.js.map +1 -0
  169. package/dist/components/skeleton/skeleton.stories.js +345 -0
  170. package/dist/components/skeleton/skeleton.stories.js.map +1 -0
  171. package/dist/components/table/data-table.d.ts +70 -0
  172. package/dist/components/table/data-table.d.ts.map +1 -0
  173. package/dist/components/table/data-table.js +159 -0
  174. package/dist/components/table/data-table.js.map +1 -0
  175. package/dist/components/table/index.d.ts +6 -0
  176. package/dist/components/table/index.d.ts.map +1 -0
  177. package/dist/components/table/index.js +6 -0
  178. package/dist/components/table/index.js.map +1 -0
  179. package/dist/components/table/table-column-header.d.ts +79 -0
  180. package/dist/components/table/table-column-header.d.ts.map +1 -0
  181. package/dist/components/table/table-column-header.js +99 -0
  182. package/dist/components/table/table-column-header.js.map +1 -0
  183. package/dist/components/table/table-pagination.d.ts +53 -0
  184. package/dist/components/table/table-pagination.d.ts.map +1 -0
  185. package/dist/components/table/table-pagination.js +139 -0
  186. package/dist/components/table/table-pagination.js.map +1 -0
  187. package/dist/components/table/table.d.ts +11 -0
  188. package/dist/components/table/table.d.ts.map +1 -0
  189. package/dist/components/table/table.js +64 -0
  190. package/dist/components/table/table.js.map +1 -0
  191. package/dist/components/table/table.stories.columns.d.ts +24 -0
  192. package/dist/components/table/table.stories.columns.d.ts.map +1 -0
  193. package/dist/components/table/table.stories.columns.js +310 -0
  194. package/dist/components/table/table.stories.columns.js.map +1 -0
  195. package/dist/components/table/table.stories.components.d.ts +14 -0
  196. package/dist/components/table/table.stories.components.d.ts.map +1 -0
  197. package/dist/components/table/table.stories.components.js +107 -0
  198. package/dist/components/table/table.stories.components.js.map +1 -0
  199. package/dist/components/table/table.stories.data.d.ts +54 -0
  200. package/dist/components/table/table.stories.data.d.ts.map +1 -0
  201. package/dist/components/table/table.stories.data.js +122 -0
  202. package/dist/components/table/table.stories.data.js.map +1 -0
  203. package/dist/components/table/table.stories.js +302 -0
  204. package/dist/components/table/table.stories.js.map +1 -0
  205. package/dist/index.d.ts +1 -0
  206. package/dist/index.d.ts.map +1 -1
  207. package/dist/index.js +1 -0
  208. package/dist/index.js.map +1 -1
  209. package/dist/styles.css +1 -1
  210. package/index.css +79 -0
  211. package/package.json +6 -2
  212. package/src/components/avatar/avatar.tsx +1 -1
  213. package/src/components/button-group/button-group.stories.tsx +361 -0
  214. package/src/components/button-group/button-group.tsx +111 -0
  215. package/src/components/button-group/index.ts +1 -0
  216. package/src/components/code-block/code-block-footer.tsx +19 -2
  217. package/src/components/command/command.stories.tsx +133 -0
  218. package/src/components/command/command.tsx +265 -0
  219. package/src/components/command/index.ts +1 -0
  220. package/src/components/confetti/confetti.stories.tsx +38 -0
  221. package/src/components/confetti/confetti.tsx +140 -0
  222. package/src/components/confetti/index.ts +1 -0
  223. package/src/components/dashboard/components/analytics-content.tsx +102 -0
  224. package/src/components/dashboard/components/animated-logo.tsx +25 -0
  225. package/src/components/dashboard/components/complete-setup-button.tsx +30 -0
  226. package/src/components/dashboard/components/jobs-content.tsx +51 -0
  227. package/src/components/dashboard/components/mobile-menu.tsx +50 -0
  228. package/src/components/dashboard/components/organization-selector.tsx +51 -0
  229. package/src/components/dashboard/components/top-menu.tsx +26 -0
  230. package/src/components/dashboard/components/topbar-button.tsx +27 -0
  231. package/src/components/dashboard/components/topbar.tsx +40 -0
  232. package/src/components/dashboard/components/user-profile.tsx +90 -0
  233. package/src/components/dashboard/dashboard.stories.tsx +25 -0
  234. package/src/components/dashboard/dashboard.tsx +61 -0
  235. package/src/components/dashboard/index.ts +1 -0
  236. package/src/components/form/form.stories.tsx +5 -0
  237. package/src/components/icon/icon.tsx +7 -3
  238. package/src/components/index.ts +9 -0
  239. package/src/components/kbd/index.ts +1 -0
  240. package/src/components/kbd/kbd.stories.tsx +64 -0
  241. package/src/components/kbd/kbd.tsx +32 -0
  242. package/src/components/modal/modal.stories.tsx +58 -4
  243. package/src/components/search/index.ts +28 -0
  244. package/src/components/search/search-context.tsx +78 -0
  245. package/src/components/search/search-inline.tsx +107 -0
  246. package/src/components/search/search-modal.tsx +198 -0
  247. package/src/components/search/search-trigger.tsx +47 -0
  248. package/src/components/search/search-variants.ts +88 -0
  249. package/src/components/search/search.stories.tsx +392 -0
  250. package/src/components/search/search.tsx +47 -0
  251. package/src/components/select/index.ts +1 -0
  252. package/src/components/select/select.stories.tsx +207 -0
  253. package/src/components/select/select.tsx +220 -0
  254. package/src/components/shiny-text/index.ts +1 -0
  255. package/src/components/shiny-text/shiny-text.tsx +21 -0
  256. package/src/components/skeleton/index.ts +1 -0
  257. package/src/components/skeleton/skeleton.stories.tsx +178 -0
  258. package/src/components/skeleton/skeleton.tsx +14 -0
  259. package/src/components/table/data-table.tsx +254 -0
  260. package/src/components/table/index.ts +5 -0
  261. package/src/components/table/table-column-header.tsx +141 -0
  262. package/src/components/table/table-pagination.tsx +161 -0
  263. package/src/components/table/table.stories.columns.tsx +198 -0
  264. package/src/components/table/table.stories.components.tsx +104 -0
  265. package/src/components/table/table.stories.data.ts +117 -0
  266. package/src/components/table/table.stories.tsx +256 -0
  267. package/src/components/table/table.tsx +95 -0
  268. package/src/index.ts +1 -0
@@ -0,0 +1,361 @@
1
+ import type {Meta, StoryObj} from '@storybook/react';
2
+ import {useState} from 'react';
3
+ import {Button} from '../button';
4
+ import {Icon} from '../icon';
5
+ import {Input} from '../input';
6
+ import {Popover, PopoverContent, PopoverTrigger} from '../popover';
7
+ import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '../select';
8
+ import {Textarea} from '../textarea';
9
+ import {Code, Header} from '../typography';
10
+ import {ButtonGroup, ButtonGroupSeparator, ButtonGroupText} from './button-group';
11
+
12
+ const meta = {
13
+ title: 'Components/ButtonGroup',
14
+ component: ButtonGroup,
15
+ tags: ['autodocs'],
16
+ parameters: {
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'A container that groups related buttons together with consistent styling. Automatically styles Button, Input, Select, and Textarea children without requiring manual className overrides. Separators are recommended for visual hierarchy.',
21
+ },
22
+ },
23
+ },
24
+ argTypes: {
25
+ orientation: {
26
+ control: 'select',
27
+ options: ['horizontal', 'vertical'],
28
+ description: 'The orientation of the button group',
29
+ defaultValue: 'horizontal',
30
+ },
31
+ },
32
+ } satisfies Meta<typeof ButtonGroup>;
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof meta>;
37
+
38
+ function SelectExample() {
39
+ const [currency, setCurrency] = useState('$');
40
+
41
+ return (
42
+ <div className="inline-flex gap-8">
43
+ <ButtonGroup className="w-280" aria-label="Currency converter">
44
+ <Select value={currency} onValueChange={setCurrency}>
45
+ <SelectTrigger
46
+ className="w-80 font-mono text-foreground-neutral-subtle"
47
+ aria-label="Select currency"
48
+ >
49
+ <SelectValue />
50
+ </SelectTrigger>
51
+ <SelectContent align="start">
52
+ <SelectItem value="$">$ USD</SelectItem>
53
+ <SelectItem value="€">€ EUR</SelectItem>
54
+ <SelectItem value="£">£ GBP</SelectItem>
55
+ <SelectItem value="¥">¥ JPY</SelectItem>
56
+ </SelectContent>
57
+ </Select>
58
+ <ButtonGroupSeparator />
59
+ <Input placeholder="10.00" pattern="[0-9]*" aria-label="Amount" />
60
+ </ButtonGroup>
61
+ <ButtonGroup aria-label="Send action">
62
+ <Button variant="secondary" aria-label="Send">
63
+ <Icon name="arrowRightLine" className="size-16 text-foreground-neutral-subtle" />
64
+ </Button>
65
+ </ButtonGroup>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ function PopoverExample() {
71
+ const [open, setOpen] = useState(false);
72
+
73
+ return (
74
+ <ButtonGroup aria-label="Copilot actions">
75
+ <ButtonGroupText>
76
+ <Icon name="sparklingLine" className="size-16" />
77
+ Copilot
78
+ </ButtonGroupText>
79
+ <ButtonGroupSeparator />
80
+ <Popover open={open} onOpenChange={setOpen}>
81
+ <PopoverTrigger asChild>
82
+ <Button
83
+ variant="secondary"
84
+ size="sm"
85
+ className="!text-foreground-neutral-subtle"
86
+ aria-label="Open Copilot options"
87
+ >
88
+ <Icon name="arrowDownSLine" className="size-16" />
89
+ </Button>
90
+ </PopoverTrigger>
91
+ <PopoverContent align="end" className="w-320 p-0 rounded-12">
92
+ <div className="px-16 py-12 border-b border-border-neutral-strong">
93
+ <Header variant="h4" className="text-sm font-medium">
94
+ Agent Tasks
95
+ </Header>
96
+ </div>
97
+ <div className="p-16 flex flex-col gap-12">
98
+ <Textarea
99
+ placeholder="Describe your task in natural language."
100
+ className="min-h-80 resize-none"
101
+ aria-label="Task description"
102
+ />
103
+ <div className="flex flex-col gap-8">
104
+ <Code variant="label" className="font-medium text-foreground-neutral-base">
105
+ Start a new task with Copilot
106
+ </Code>
107
+ <Code variant="paragraph" className="text-foreground-neutral-subtle text-xs">
108
+ Describe your task in natural language. Copilot will work in the background and open
109
+ a pull request for your review.
110
+ </Code>
111
+ </div>
112
+ </div>
113
+ </PopoverContent>
114
+ </Popover>
115
+ </ButtonGroup>
116
+ );
117
+ }
118
+
119
+ export const Default: Story = {
120
+ render: () => (
121
+ <div className="flex flex-col gap-24">
122
+ {/* Basic Button Group */}
123
+ <div className="flex flex-col gap-8">
124
+ <Code variant="label" className="text-foreground-neutral-subtle">
125
+ Basic Button Group
126
+ </Code>
127
+ <ButtonGroup aria-label="Text alignment">
128
+ <Button variant="secondary" size="sm" aria-label="Align left">
129
+ <Icon name="rewindFill" className="size-14 text-foreground-neutral-subtle" />
130
+ </Button>
131
+ <ButtonGroupSeparator />
132
+ <Button variant="secondary" size="sm" aria-label="Align center">
133
+ <Icon name="playFill" className="size-14 text-foreground-neutral-subtle" />
134
+ </Button>
135
+ <ButtonGroupSeparator />
136
+ <Button variant="secondary" size="sm" aria-label="Align right">
137
+ <Icon name="speedFill" className="size-14 text-foreground-neutral-subtle" />
138
+ </Button>
139
+ </ButtonGroup>
140
+ </div>
141
+
142
+ {/* Sizes */}
143
+ <div className="flex flex-col gap-12">
144
+ <Code variant="label" className="text-foreground-neutral-subtle">
145
+ Sizes
146
+ </Code>
147
+ <div className="flex flex-col gap-8">
148
+ <ButtonGroup aria-label="Small buttons">
149
+ <Button variant="secondary" size="sm" aria-label="Cut">
150
+ <Icon name="rewindFill" className="size-14 text-foreground-neutral-subtle" />
151
+ </Button>
152
+ <ButtonGroupSeparator />
153
+ <Button variant="secondary" size="sm" aria-label="Copy">
154
+ <Icon name="playFill" className="size-14 text-foreground-neutral-subtle" />
155
+ </Button>
156
+ <ButtonGroupSeparator />
157
+ <Button variant="secondary" size="sm" aria-label="Paste">
158
+ <Icon name="speedFill" className="size-14 text-foreground-neutral-subtle" />
159
+ </Button>
160
+ </ButtonGroup>
161
+ <ButtonGroup aria-label="Medium buttons">
162
+ <Button variant="secondary" size="md" aria-label="Cut">
163
+ <Icon name="rewindFill" className="size-16 text-foreground-neutral-subtle" />
164
+ </Button>
165
+ <ButtonGroupSeparator />
166
+ <Button variant="secondary" size="md" aria-label="Copy">
167
+ <Icon name="playFill" className="size-16 text-foreground-neutral-subtle" />
168
+ </Button>
169
+ <ButtonGroupSeparator />
170
+ <Button variant="secondary" size="md" aria-label="Paste">
171
+ <Icon name="speedFill" className="size-16 text-foreground-neutral-subtle" />
172
+ </Button>
173
+ </ButtonGroup>
174
+ <ButtonGroup aria-label="Large buttons">
175
+ <Button variant="secondary" size="lg" aria-label="Cut">
176
+ <Icon name="rewindFill" className="size-16 text-foreground-neutral-subtle" />
177
+ </Button>
178
+ <ButtonGroupSeparator />
179
+ <Button variant="secondary" size="lg" aria-label="Copy">
180
+ <Icon name="playFill" className="size-16 text-foreground-neutral-subtle" />
181
+ </Button>
182
+ <ButtonGroupSeparator />
183
+ <Button variant="secondary" size="lg" aria-label="Paste">
184
+ <Icon name="speedFill" className="size-16 text-foreground-neutral-subtle" />
185
+ </Button>
186
+ </ButtonGroup>
187
+ </div>
188
+ </div>
189
+
190
+ {/* Orientation */}
191
+ <div className="flex flex-col gap-8">
192
+ <Code variant="label" className="text-foreground-neutral-subtle">
193
+ Orientation
194
+ </Code>
195
+ <div className="flex gap-24 items-start">
196
+ <ButtonGroup aria-label="Horizontal">
197
+ <Button variant="secondary" size="sm" aria-label="Zoom in">
198
+ <Icon name="addLine" className="size-16 text-foreground-neutral-subtle" />
199
+ </Button>
200
+ <ButtonGroupSeparator />
201
+ <Button variant="secondary" size="sm" aria-label="Zoom out">
202
+ <Icon name="subtractLine" className="size-16 text-foreground-neutral-subtle" />
203
+ </Button>
204
+ </ButtonGroup>
205
+ <ButtonGroup orientation="vertical" className="h-fit w-fit" aria-label="Vertical">
206
+ <Button variant="secondary" size="sm" aria-label="Increase">
207
+ <Icon name="addLine" className="size-16 text-foreground-neutral-subtle" />
208
+ </Button>
209
+ <ButtonGroupSeparator orientation="horizontal" />
210
+ <Button variant="secondary" size="sm" aria-label="Decrease">
211
+ <Icon name="subtractLine" className="size-16 text-foreground-neutral-subtle" />
212
+ </Button>
213
+ </ButtonGroup>
214
+ </div>
215
+ </div>
216
+
217
+ {/* Split Button */}
218
+ <div className="flex flex-col gap-8">
219
+ <Code variant="label" className="text-foreground-neutral-subtle">
220
+ Split Button
221
+ </Code>
222
+ <ButtonGroup aria-label="Save actions">
223
+ <Button variant="secondary" size="sm" className=" !text-foreground-neutral-subtle">
224
+ Save
225
+ </Button>
226
+ <ButtonGroupSeparator />
227
+ <Button variant="secondary" size="sm" aria-label="More options">
228
+ <Icon name="arrowDownSLine" className="size-16 text-foreground-neutral-subtle" />
229
+ </Button>
230
+ </ButtonGroup>
231
+ </div>
232
+
233
+ {/* With Input */}
234
+ <div className="flex flex-col gap-8">
235
+ <Code variant="label" className="text-foreground-neutral-subtle">
236
+ With Input
237
+ </Code>
238
+ <ButtonGroup className="w-320" aria-label="Search">
239
+ <Input placeholder="Search..." aria-label="Search input" />
240
+ <ButtonGroupSeparator />
241
+ <Button variant="secondary" aria-label="Submit search">
242
+ <Icon name="searchLine" className="size-16 text-foreground-neutral-subtle" />
243
+ </Button>
244
+ </ButtonGroup>
245
+ </div>
246
+
247
+ {/* Quantity Input */}
248
+ <div className="flex flex-col gap-8">
249
+ <Code variant="label" className="text-foreground-neutral-subtle">
250
+ Quantity Selector
251
+ </Code>
252
+ <ButtonGroup className="w-280" aria-label="Quantity selector">
253
+ <Button variant="secondary" aria-label="Decrease">
254
+ <Icon name="subtractLine" className="size-16 text-foreground-neutral-subtle" />
255
+ </Button>
256
+ <ButtonGroupSeparator />
257
+ <Input placeholder="1" className="text-center" aria-label="Quantity" />
258
+ <ButtonGroupSeparator />
259
+ <Button variant="secondary" aria-label="Increase">
260
+ <Icon name="addLine" className="size-16 text-foreground-neutral-subtle" />
261
+ </Button>
262
+ </ButtonGroup>
263
+ </div>
264
+
265
+ {/* With Select */}
266
+ <div className="flex flex-col gap-8">
267
+ <Code variant="label" className="text-foreground-neutral-subtle">
268
+ With Select
269
+ </Code>
270
+ <SelectExample />
271
+ </div>
272
+
273
+ {/* With Popover */}
274
+ <div className="flex flex-col gap-8">
275
+ <Code variant="label" className="text-foreground-neutral-subtle">
276
+ With Popover
277
+ </Code>
278
+ <PopoverExample />
279
+ </div>
280
+
281
+ {/* Nested Groups */}
282
+ <div className="flex flex-col gap-8">
283
+ <Code variant="label" className="text-foreground-neutral-subtle">
284
+ Nested Groups
285
+ </Code>
286
+ <div className="inline-flex gap-8">
287
+ <ButtonGroup aria-label="Page selection">
288
+ <Button
289
+ variant="transparent"
290
+ className="w-42 !text-foreground-neutral-subtle"
291
+ aria-label="Page 1"
292
+ >
293
+ 1
294
+ </Button>
295
+ <ButtonGroupSeparator />
296
+ <Button
297
+ variant="transparent"
298
+ className="w-42 !text-foreground-neutral-subtle"
299
+ aria-label="Page 2"
300
+ >
301
+ 2
302
+ </Button>
303
+ <ButtonGroupSeparator />
304
+ <Button
305
+ variant="transparent"
306
+ className="w-42 !text-foreground-neutral-subtle"
307
+ aria-label="Page 3"
308
+ >
309
+ 3
310
+ </Button>
311
+ <ButtonGroupSeparator />
312
+ <Button
313
+ variant="transparent"
314
+ className="w-42 !text-foreground-neutral-subtle"
315
+ aria-label="Page 4"
316
+ >
317
+ 4
318
+ </Button>
319
+ <ButtonGroupSeparator />
320
+ <Button
321
+ variant="transparent"
322
+ className="w-42 !text-foreground-neutral-subtle"
323
+ aria-label="Page 5"
324
+ >
325
+ 5
326
+ </Button>
327
+ </ButtonGroup>
328
+ <ButtonGroup aria-label="Pagination controls">
329
+ <Button variant="transparent" aria-label="Previous page">
330
+ <Icon name="arrowLeftSLine" className="size-16 text-foreground-neutral-subtle" />
331
+ </Button>
332
+ <ButtonGroupSeparator />
333
+ <Button variant="transparent" aria-label="Next page">
334
+ <Icon name="arrowRightSLine" className="size-16 text-foreground-neutral-subtle" />
335
+ </Button>
336
+ </ButtonGroup>
337
+ </div>
338
+ </div>
339
+
340
+ {/* Disabled State */}
341
+ <div className="flex flex-col gap-8">
342
+ <Code variant="label" className="text-foreground-neutral-subtle">
343
+ Disabled State
344
+ </Code>
345
+ <ButtonGroup aria-label="Button group with disabled state">
346
+ <Button variant="secondary" size="sm" className=" !text-foreground-neutral-subtle">
347
+ Enabled
348
+ </Button>
349
+ <ButtonGroupSeparator />
350
+ <Button variant="secondary" size="sm" disabled>
351
+ Disabled
352
+ </Button>
353
+ <ButtonGroupSeparator />
354
+ <Button variant="secondary" size="sm" className=" !text-foreground-neutral-subtle">
355
+ Enabled
356
+ </Button>
357
+ </ButtonGroup>
358
+ </div>
359
+ </div>
360
+ ),
361
+ };
@@ -0,0 +1,111 @@
1
+ import {Slot} from '@radix-ui/react-slot';
2
+ import {cva, type VariantProps} from 'class-variance-authority';
3
+ import type {ComponentProps} from 'react';
4
+ import {cn} from 'utils/cn';
5
+
6
+ const buttonGroupVariants = cva(
7
+ [
8
+ 'flex w-fit items-stretch',
9
+ // Apply shadow to the group container instead of individual children
10
+ 'rounded-6 shadow-button-neutral',
11
+ // Focus management
12
+ '[&>*]:focus-visible:z-10 [&>*]:focus-visible:relative',
13
+ // Select trigger sizing
14
+ "[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit",
15
+ // Input flex
16
+ '[&>input]:flex-1',
17
+ // Nested button groups
18
+ 'has-[>[data-slot=button-group]]:gap-8',
19
+ // Remove shadows from all children to prevent inner shadow artifacts
20
+ '[&>*]:shadow-none',
21
+ ],
22
+ {
23
+ variants: {
24
+ orientation: {
25
+ horizontal: [
26
+ // Remove left border-radius and left border from all but first child
27
+ '[&>*:not(:first-child)]:rounded-l-none',
28
+ '[&>*:not(:first-child)]:border-l-0',
29
+ // Remove right border-radius from all but last child
30
+ '[&>*:not(:last-child)]:rounded-r-none',
31
+ ],
32
+ vertical: [
33
+ 'flex-col',
34
+ // Remove top border-radius and top border from all but first child
35
+ '[&>*:not(:first-child)]:rounded-t-none',
36
+ '[&>*:not(:first-child)]:border-t-0',
37
+ // Remove bottom border-radius from all but last child
38
+ '[&>*:not(:last-child)]:rounded-b-none',
39
+ ],
40
+ },
41
+ },
42
+ defaultVariants: {
43
+ orientation: 'horizontal',
44
+ },
45
+ },
46
+ );
47
+
48
+ type ButtonGroupProps = ComponentProps<'div'> & VariantProps<typeof buttonGroupVariants>;
49
+
50
+ function ButtonGroup({className, orientation, ...props}: ButtonGroupProps) {
51
+ return (
52
+ // biome-ignore lint/a11y/useSemanticElements: role="group" is semantically correct for button groups
53
+ <div
54
+ role="group"
55
+ data-slot="button-group"
56
+ data-orientation={orientation}
57
+ className={cn(buttonGroupVariants({orientation}), className)}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+
63
+ type ButtonGroupTextProps = ComponentProps<'div'> & {
64
+ asChild?: boolean;
65
+ };
66
+
67
+ function ButtonGroupText({className, asChild = false, ...props}: ButtonGroupTextProps) {
68
+ const Comp = asChild ? Slot : 'div';
69
+
70
+ return (
71
+ <Comp
72
+ data-slot="button-group-text"
73
+ className={cn(
74
+ 'flex items-center gap-8 rounded-6 px-12',
75
+ 'bg-background-field-base text-foreground-neutral-subtle',
76
+ 'text-sm leading-20 font-medium',
77
+ 'shadow-button-neutral',
78
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-16",
79
+ className,
80
+ )}
81
+ {...props}
82
+ />
83
+ );
84
+ }
85
+
86
+ type ButtonGroupSeparatorProps = ComponentProps<'div'> & {
87
+ orientation?: 'horizontal' | 'vertical';
88
+ };
89
+
90
+ function ButtonGroupSeparator({
91
+ className,
92
+ orientation = 'vertical',
93
+ ...props
94
+ }: ButtonGroupSeparatorProps) {
95
+ return (
96
+ <div
97
+ aria-hidden="true"
98
+ data-slot="button-group-separator"
99
+ data-orientation={orientation}
100
+ className={cn(
101
+ 'shrink-0 self-stretch',
102
+ 'bg-border-neutral-strong',
103
+ orientation === 'vertical' ? 'h-auto w-px' : 'h-px w-auto',
104
+ className,
105
+ )}
106
+ {...props}
107
+ />
108
+ );
109
+ }
110
+
111
+ export {ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants};
@@ -0,0 +1 @@
1
+ export * from './button-group';
@@ -1,7 +1,10 @@
1
1
  import {Slot} from '@radix-ui/react-slot';
2
2
  import {Icon} from 'components/icon/icon';
3
+ import {ShinyText} from 'components/shiny-text';
3
4
  import {Text} from 'components/typography';
5
+ import {useResolvedTheme} from 'hooks/useResolvedTheme';
4
6
  import type {ComponentProps, HTMLAttributes, ReactNode} from 'react';
7
+ import {ShipfoxLoader} from 'shipfox-loader-react';
5
8
  import {cn} from 'utils/cn';
6
9
 
7
10
  export type CodeBlockFooterProps = HTMLAttributes<HTMLDivElement> & {
@@ -23,11 +26,17 @@ export function CodeBlockFooter({
23
26
  ...props
24
27
  }: CodeBlockFooterProps) {
25
28
  const Comp = asChild ? Slot : 'div';
29
+ const resolvedTheme = useResolvedTheme();
26
30
 
27
31
  const defaultIcon =
28
32
  icon ??
29
33
  (state === 'running' ? (
30
- <Icon name="shipfox" className="size-20" aria-hidden="true" />
34
+ <ShipfoxLoader
35
+ size={20}
36
+ animation="circular"
37
+ color={resolvedTheme === 'dark' ? 'white' : 'orange'}
38
+ background={resolvedTheme === 'dark' ? 'dark' : 'light'}
39
+ />
31
40
  ) : (
32
41
  <Icon
33
42
  name="checkCircleSolid"
@@ -57,7 +66,15 @@ export function CodeBlockFooter({
57
66
  <CodeBlockFooterIcon className="text-tag-success-icon">{defaultIcon}</CodeBlockFooterIcon>
58
67
  {(message || description) && (
59
68
  <CodeBlockFooterContent>
60
- {message && <CodeBlockFooterMessage>{message}</CodeBlockFooterMessage>}
69
+ {message && (
70
+ <CodeBlockFooterMessage>
71
+ {state === 'running' && typeof message === 'string' ? (
72
+ <ShinyText text={message} speed={3} />
73
+ ) : (
74
+ message
75
+ )}
76
+ </CodeBlockFooterMessage>
77
+ )}
61
78
  {description && <CodeBlockFooterDescription>{description}</CodeBlockFooterDescription>}
62
79
  </CodeBlockFooterContent>
63
80
  )}
@@ -0,0 +1,133 @@
1
+ import type {Meta, StoryObj} from '@storybook/react';
2
+ import {useState} from 'react';
3
+ import {Icon} from '../icon';
4
+ import {Popover, PopoverContent, PopoverTrigger} from '../popover';
5
+ import {
6
+ Command,
7
+ CommandEmpty,
8
+ CommandGroup,
9
+ CommandInput,
10
+ CommandItem,
11
+ CommandList,
12
+ CommandSeparator,
13
+ CommandShortcut,
14
+ CommandTrigger,
15
+ } from './command';
16
+
17
+ const meta = {
18
+ title: 'Components/Command',
19
+ component: Command,
20
+ tags: ['autodocs'],
21
+ } satisfies Meta<typeof Command>;
22
+
23
+ export default meta;
24
+
25
+ type Story = StoryObj<typeof meta>;
26
+
27
+ export const Default: Story = {
28
+ render: () => (
29
+ <Command className="rounded-10 shadow-tooltip max-w-400">
30
+ <CommandInput placeholder="Type a command or search..." />
31
+ <CommandList>
32
+ <CommandEmpty>No results found.</CommandEmpty>
33
+ <CommandGroup heading="Suggestions">
34
+ <CommandItem>
35
+ <Icon name="calendar2Line" className="size-16 mr-8" />
36
+ <span>Calendar</span>
37
+ </CommandItem>
38
+ <CommandItem>
39
+ <Icon name="emotion2Line" className="size-16 mr-8" />
40
+ <span>Search Emoji</span>
41
+ </CommandItem>
42
+ <CommandItem>
43
+ <Icon name="calculatorLine" className="size-16 mr-8" />
44
+ <span>Calculator</span>
45
+ </CommandItem>
46
+ </CommandGroup>
47
+ <CommandSeparator />
48
+ <CommandGroup heading="Settings">
49
+ <CommandItem>
50
+ <Icon name="user3Line" className="size-16 mr-8" />
51
+ <span>Profile</span>
52
+ <CommandShortcut>⌘P</CommandShortcut>
53
+ </CommandItem>
54
+ <CommandItem>
55
+ <Icon name="mailLine" className="size-16 mr-8" />
56
+ <span>Mail</span>
57
+ <CommandShortcut>⌘M</CommandShortcut>
58
+ </CommandItem>
59
+ <CommandItem>
60
+ <Icon name="settings3Line" className="size-16 mr-8" />
61
+ <span>Settings</span>
62
+ <CommandShortcut>⌘S</CommandShortcut>
63
+ </CommandItem>
64
+ </CommandGroup>
65
+ </CommandList>
66
+ </Command>
67
+ ),
68
+ };
69
+
70
+ export const Combobox: Story = {
71
+ render: () => {
72
+ const frameworks = [
73
+ {value: 'next.js', label: 'Next.js', icon: 'reactjsLine' as const},
74
+ {value: 'sveltekit', label: 'SvelteKit', icon: 'svelteLine' as const},
75
+ {value: 'nuxt.js', label: 'Nuxt.js', icon: 'vuejsLine' as const},
76
+ {value: 'remix', label: 'Remix', icon: 'reactjsLine' as const},
77
+ {value: 'astro', label: 'Astro', icon: 'rocketLine' as const},
78
+ {value: 'vue', label: 'Vue', icon: 'vuejsLine' as const},
79
+ {value: 'react', label: 'React', icon: 'reactjsLine' as const},
80
+ ];
81
+
82
+ function ComboboxDemo() {
83
+ const [open, setOpen] = useState(false);
84
+ const [value, setValue] = useState('');
85
+
86
+ return (
87
+ <div className="flex flex-col gap-16">
88
+ <Popover open={open} onOpenChange={setOpen}>
89
+ <PopoverTrigger asChild>
90
+ <CommandTrigger className="w-280" placeholder="Select framework...">
91
+ {value ? frameworks.find((framework) => framework.value === value)?.label : null}
92
+ </CommandTrigger>
93
+ </PopoverTrigger>
94
+ <PopoverContent className="w-280 p-0" align="start">
95
+ <Command>
96
+ <CommandInput placeholder="Search framework..." />
97
+ <CommandList>
98
+ <CommandEmpty>No framework found.</CommandEmpty>
99
+ <CommandGroup>
100
+ {frameworks.map((framework) => (
101
+ <CommandItem
102
+ key={framework.value}
103
+ value={framework.value}
104
+ onSelect={(currentValue) => {
105
+ setValue(currentValue === value ? '' : currentValue);
106
+ setOpen(false);
107
+ }}
108
+ >
109
+ <Icon
110
+ name="check"
111
+ className={`size-16 mr-8 ${value === framework.value ? 'opacity-100' : 'opacity-0'}`}
112
+ />
113
+ <Icon name={framework.icon} className="size-16 mr-8" />
114
+ {framework.label}
115
+ </CommandItem>
116
+ ))}
117
+ </CommandGroup>
118
+ </CommandList>
119
+ </Command>
120
+ </PopoverContent>
121
+ </Popover>
122
+ {value && (
123
+ <p className="text-sm text-foreground-neutral-muted">
124
+ Selected: {frameworks.find((f) => f.value === value)?.label}
125
+ </p>
126
+ )}
127
+ </div>
128
+ );
129
+ }
130
+
131
+ return <ComboboxDemo />;
132
+ },
133
+ };