@shipfox/react-ui 0.3.0 → 0.4.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 (290) hide show
  1. package/.storybook/preview.tsx +1 -1
  2. package/.turbo/turbo-build.log +2 -2
  3. package/.turbo/turbo-check.log +2 -2
  4. package/.turbo/turbo-type.log +1 -1
  5. package/CHANGELOG.md +11 -0
  6. package/dist/components/alert/alert.d.ts +2 -2
  7. package/dist/components/alert/alert.js +3 -3
  8. package/dist/components/alert/alert.js.map +1 -1
  9. package/dist/components/alert/alert.stories.js +2 -2
  10. package/dist/components/alert/alert.stories.js.map +1 -1
  11. package/dist/components/avatar/avatar-group.js +3 -3
  12. package/dist/components/avatar/avatar-group.js.map +1 -1
  13. package/dist/components/avatar/avatar.d.ts +4 -1
  14. package/dist/components/avatar/avatar.d.ts.map +1 -1
  15. package/dist/components/avatar/avatar.js +7 -8
  16. package/dist/components/avatar/avatar.js.map +1 -1
  17. package/dist/components/avatar/avatar.stories.js +15 -3
  18. package/dist/components/avatar/avatar.stories.js.map +1 -1
  19. package/dist/components/badge/badge.d.ts +48 -0
  20. package/dist/components/badge/badge.d.ts.map +1 -0
  21. package/dist/components/badge/badge.js +72 -0
  22. package/dist/components/badge/badge.js.map +1 -0
  23. package/dist/components/badge/badge.stories.js +802 -0
  24. package/dist/components/badge/badge.stories.js.map +1 -0
  25. package/dist/components/badge/icon-badge.d.ts +9 -0
  26. package/dist/components/badge/icon-badge.d.ts.map +1 -0
  27. package/dist/components/badge/icon-badge.js +32 -0
  28. package/dist/components/badge/icon-badge.js.map +1 -0
  29. package/dist/components/badge/index.d.ts +5 -0
  30. package/dist/components/badge/index.d.ts.map +1 -0
  31. package/dist/components/badge/index.js +6 -0
  32. package/dist/components/badge/index.js.map +1 -0
  33. package/dist/components/badge/status-badge.d.ts +9 -0
  34. package/dist/components/badge/status-badge.d.ts.map +1 -0
  35. package/dist/components/badge/status-badge.js +29 -0
  36. package/dist/components/badge/status-badge.js.map +1 -0
  37. package/dist/components/badge/user-badge.d.ts +8 -0
  38. package/dist/components/badge/user-badge.d.ts.map +1 -0
  39. package/dist/components/badge/user-badge.js +24 -0
  40. package/dist/components/badge/user-badge.js.map +1 -0
  41. package/dist/components/{button.d.ts → button/button.d.ts} +1 -1
  42. package/dist/components/button/button.d.ts.map +1 -0
  43. package/dist/components/{button.js → button/button.js} +2 -2
  44. package/dist/components/button/button.js.map +1 -0
  45. package/dist/components/{button.stories.js → button/button.stories.js} +1 -1
  46. package/dist/components/button/button.stories.js.map +1 -0
  47. package/dist/components/button/index.d.ts +2 -0
  48. package/dist/components/button/index.d.ts.map +1 -0
  49. package/dist/components/button/index.js +3 -0
  50. package/dist/components/button/index.js.map +1 -0
  51. package/dist/components/checkbox/checkbox-label.d.ts +14 -0
  52. package/dist/components/checkbox/checkbox-label.d.ts.map +1 -0
  53. package/dist/components/checkbox/checkbox-label.js +82 -0
  54. package/dist/components/checkbox/checkbox-label.js.map +1 -0
  55. package/dist/components/checkbox/checkbox-links.d.ts +18 -0
  56. package/dist/components/checkbox/checkbox-links.d.ts.map +1 -0
  57. package/dist/components/checkbox/checkbox-links.js +58 -0
  58. package/dist/components/checkbox/checkbox-links.js.map +1 -0
  59. package/dist/components/checkbox/checkbox.d.ts +9 -0
  60. package/dist/components/checkbox/checkbox.d.ts.map +1 -0
  61. package/dist/components/checkbox/checkbox.js +49 -0
  62. package/dist/components/checkbox/checkbox.js.map +1 -0
  63. package/dist/components/checkbox/checkbox.stories.js +566 -0
  64. package/dist/components/checkbox/checkbox.stories.js.map +1 -0
  65. package/dist/components/checkbox/index.d.ts +4 -0
  66. package/dist/components/checkbox/index.d.ts.map +1 -0
  67. package/dist/components/checkbox/index.js +5 -0
  68. package/dist/components/checkbox/index.js.map +1 -0
  69. package/dist/components/code-block/code-block-footer.d.ts +26 -0
  70. package/dist/components/code-block/code-block-footer.d.ts.map +1 -0
  71. package/dist/components/code-block/code-block-footer.js +86 -0
  72. package/dist/components/code-block/code-block-footer.js.map +1 -0
  73. package/dist/components/code-block/code-block.d.ts +50 -0
  74. package/dist/components/code-block/code-block.d.ts.map +1 -0
  75. package/dist/components/code-block/code-block.js +142 -0
  76. package/dist/components/code-block/code-block.js.map +1 -0
  77. package/dist/components/code-block/code-block.stories.js +341 -0
  78. package/dist/components/code-block/code-block.stories.js.map +1 -0
  79. package/dist/components/code-block/code-content.d.ts +11 -0
  80. package/dist/components/code-block/code-content.d.ts.map +1 -0
  81. package/dist/components/code-block/code-content.js +29 -0
  82. package/dist/components/code-block/code-content.js.map +1 -0
  83. package/dist/components/code-block/code-copy-button.d.ts +11 -0
  84. package/dist/components/code-block/code-copy-button.d.ts.map +1 -0
  85. package/dist/components/code-block/code-copy-button.js +49 -0
  86. package/dist/components/code-block/code-copy-button.js.map +1 -0
  87. package/dist/components/code-block/code-tabs.d.ts +16 -0
  88. package/dist/components/code-block/code-tabs.d.ts.map +1 -0
  89. package/dist/components/code-block/code-tabs.js +98 -0
  90. package/dist/components/code-block/code-tabs.js.map +1 -0
  91. package/dist/components/code-block/index.d.ts +4 -0
  92. package/dist/components/code-block/index.d.ts.map +1 -0
  93. package/dist/components/code-block/index.js +5 -0
  94. package/dist/components/code-block/index.js.map +1 -0
  95. package/dist/components/dynamic-item/dynamic-item.d.ts +13 -0
  96. package/dist/components/dynamic-item/dynamic-item.d.ts.map +1 -0
  97. package/dist/components/dynamic-item/dynamic-item.js +43 -0
  98. package/dist/components/dynamic-item/dynamic-item.js.map +1 -0
  99. package/dist/components/dynamic-item/dynamic-item.stories.js +375 -0
  100. package/dist/components/dynamic-item/dynamic-item.stories.js.map +1 -0
  101. package/dist/components/dynamic-item/index.d.ts +2 -0
  102. package/dist/components/dynamic-item/index.d.ts.map +1 -0
  103. package/dist/components/dynamic-item/index.js +3 -0
  104. package/dist/components/dynamic-item/index.js.map +1 -0
  105. package/dist/components/icon/custom/index.d.ts +2 -0
  106. package/dist/components/icon/custom/index.d.ts.map +1 -1
  107. package/dist/components/icon/custom/index.js +2 -0
  108. package/dist/components/icon/custom/index.js.map +1 -1
  109. package/dist/components/icon/custom/slack-logo.d.ts +6 -0
  110. package/dist/components/icon/custom/slack-logo.d.ts.map +1 -0
  111. package/dist/components/icon/custom/slack-logo.js +34 -0
  112. package/dist/components/icon/custom/slack-logo.js.map +1 -0
  113. package/dist/components/icon/custom/stripe-logo.d.ts +8 -0
  114. package/dist/components/icon/custom/stripe-logo.d.ts.map +1 -0
  115. package/dist/components/icon/custom/stripe-logo.js +24 -0
  116. package/dist/components/icon/custom/stripe-logo.js.map +1 -0
  117. package/dist/components/icon/icon.d.ts +11 -2
  118. package/dist/components/icon/icon.d.ts.map +1 -1
  119. package/dist/components/icon/icon.js +12 -3
  120. package/dist/components/icon/icon.js.map +1 -1
  121. package/dist/components/icon/icon.stories.js +6 -3
  122. package/dist/components/icon/icon.stories.js.map +1 -1
  123. package/dist/components/index.d.ts +9 -1
  124. package/dist/components/index.d.ts.map +1 -1
  125. package/dist/components/index.js +10 -2
  126. package/dist/components/index.js.map +1 -1
  127. package/dist/components/inline-tips/inline-tips.d.ts +1 -1
  128. package/dist/components/inline-tips/inline-tips.d.ts.map +1 -1
  129. package/dist/components/inline-tips/inline-tips.js +1 -1
  130. package/dist/components/inline-tips/inline-tips.js.map +1 -1
  131. package/dist/components/inline-tips/inline-tips.stories.js +5 -5
  132. package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
  133. package/dist/components/input/index.d.ts +2 -0
  134. package/dist/components/input/index.d.ts.map +1 -0
  135. package/dist/components/input/index.js +3 -0
  136. package/dist/components/input/index.js.map +1 -0
  137. package/dist/components/input/input.d.ts.map +1 -0
  138. package/dist/components/{input.js → input/input.js} +2 -2
  139. package/dist/components/input/input.js.map +1 -0
  140. package/dist/components/{input.stories.js → input/input.stories.js} +1 -1
  141. package/dist/components/input/input.stories.js.map +1 -0
  142. package/dist/components/item/index.d.ts +2 -0
  143. package/dist/components/item/index.d.ts.map +1 -0
  144. package/dist/components/item/index.js +3 -0
  145. package/dist/components/item/index.js.map +1 -0
  146. package/dist/components/item/item.d.ts +32 -0
  147. package/dist/components/item/item.d.ts.map +1 -0
  148. package/dist/components/item/item.js +120 -0
  149. package/dist/components/item/item.js.map +1 -0
  150. package/dist/components/item/item.stories.js +232 -0
  151. package/dist/components/item/item.stories.js.map +1 -0
  152. package/dist/components/label/index.d.ts +2 -0
  153. package/dist/components/label/index.d.ts.map +1 -0
  154. package/dist/components/label/index.js +3 -0
  155. package/dist/components/label/index.js.map +1 -0
  156. package/dist/components/label/label.d.ts +7 -0
  157. package/dist/components/label/label.d.ts.map +1 -0
  158. package/dist/components/label/label.js +13 -0
  159. package/dist/components/label/label.js.map +1 -0
  160. package/dist/components/label/label.stories.js +105 -0
  161. package/dist/components/label/label.stories.js.map +1 -0
  162. package/dist/components/moving-border/moving-border.d.ts +9 -0
  163. package/dist/components/moving-border/moving-border.d.ts.map +1 -0
  164. package/dist/components/moving-border/moving-border.js +54 -0
  165. package/dist/components/moving-border/moving-border.js.map +1 -0
  166. package/dist/components/textarea/textarea.js +1 -1
  167. package/dist/components/textarea/textarea.js.map +1 -1
  168. package/dist/components/theme/index.d.ts +2 -0
  169. package/dist/components/theme/index.d.ts.map +1 -0
  170. package/dist/components/theme/index.js +3 -0
  171. package/dist/components/theme/index.js.map +1 -0
  172. package/dist/components/{theme-provider.d.ts → theme/theme-provider.d.ts} +1 -1
  173. package/dist/components/theme/theme-provider.d.ts.map +1 -0
  174. package/dist/components/{theme-provider.js → theme/theme-provider.js} +1 -1
  175. package/dist/components/theme/theme-provider.js.map +1 -0
  176. package/dist/components/toast/index.d.ts +3 -0
  177. package/dist/components/toast/index.d.ts.map +1 -0
  178. package/dist/components/toast/index.js +4 -0
  179. package/dist/components/toast/index.js.map +1 -0
  180. package/dist/components/toast/toast-custom.d.ts +19 -0
  181. package/dist/components/toast/toast-custom.d.ts.map +1 -0
  182. package/dist/components/toast/toast-custom.js +134 -0
  183. package/dist/components/toast/toast-custom.js.map +1 -0
  184. package/dist/components/toast/toast.d.ts +5 -0
  185. package/dist/components/toast/toast.d.ts.map +1 -0
  186. package/dist/components/toast/toast.js +40 -0
  187. package/dist/components/toast/toast.js.map +1 -0
  188. package/dist/components/toast/toast.stories.js +326 -0
  189. package/dist/components/toast/toast.stories.js.map +1 -0
  190. package/dist/components/tooltip/index.d.ts +2 -0
  191. package/dist/components/tooltip/index.d.ts.map +1 -0
  192. package/dist/components/tooltip/index.js +3 -0
  193. package/dist/components/tooltip/index.js.map +1 -0
  194. package/dist/components/tooltip/tooltip.d.ts +18 -5
  195. package/dist/components/tooltip/tooltip.d.ts.map +1 -1
  196. package/dist/components/tooltip/tooltip.js +63 -3
  197. package/dist/components/tooltip/tooltip.js.map +1 -1
  198. package/dist/components/tooltip/tooltip.stories.js +560 -0
  199. package/dist/components/tooltip/tooltip.stories.js.map +1 -0
  200. package/dist/hooks/index.d.ts +3 -0
  201. package/dist/hooks/index.d.ts.map +1 -1
  202. package/dist/hooks/index.js +3 -0
  203. package/dist/hooks/index.js.map +1 -1
  204. package/dist/hooks/useResolvedTheme.d.ts +2 -0
  205. package/dist/hooks/useResolvedTheme.d.ts.map +1 -0
  206. package/dist/hooks/useResolvedTheme.js +24 -0
  207. package/dist/hooks/useResolvedTheme.js.map +1 -0
  208. package/dist/hooks/useShikiHighlight.d.ts +28 -0
  209. package/dist/hooks/useShikiHighlight.d.ts.map +1 -0
  210. package/dist/hooks/useShikiHighlight.js +106 -0
  211. package/dist/hooks/useShikiHighlight.js.map +1 -0
  212. package/dist/hooks/useShikiStyleInjection.d.ts +2 -0
  213. package/dist/hooks/useShikiStyleInjection.d.ts.map +1 -0
  214. package/dist/hooks/useShikiStyleInjection.js +34 -0
  215. package/dist/hooks/useShikiStyleInjection.js.map +1 -0
  216. package/index.css +101 -9
  217. package/package.json +6 -3
  218. package/src/assets/illustration-1.svg +92 -0
  219. package/src/assets/illustration-2.svg +14 -0
  220. package/src/assets/illustration-gradient.svg +7049 -0
  221. package/src/components/alert/alert.stories.tsx +2 -2
  222. package/src/components/alert/alert.tsx +3 -3
  223. package/src/components/avatar/avatar-group.tsx +3 -3
  224. package/src/components/avatar/avatar.stories.tsx +9 -2
  225. package/src/components/avatar/avatar.tsx +10 -6
  226. package/src/components/badge/badge.stories.tsx +468 -0
  227. package/src/components/badge/badge.tsx +147 -0
  228. package/src/components/badge/icon-badge.tsx +43 -0
  229. package/src/components/badge/index.ts +4 -0
  230. package/src/components/badge/status-badge.tsx +43 -0
  231. package/src/components/badge/user-badge.tsx +34 -0
  232. package/src/components/{button.tsx → button/button.tsx} +1 -1
  233. package/src/components/button/index.ts +1 -0
  234. package/src/components/checkbox/checkbox-label.tsx +125 -0
  235. package/src/components/checkbox/checkbox-links.tsx +90 -0
  236. package/src/components/checkbox/checkbox.stories.tsx +375 -0
  237. package/src/components/checkbox/checkbox.tsx +71 -0
  238. package/src/components/checkbox/index.ts +3 -0
  239. package/src/components/code-block/code-block-footer.tsx +173 -0
  240. package/src/components/code-block/code-block.stories.tsx +323 -0
  241. package/src/components/code-block/code-block.tsx +283 -0
  242. package/src/components/code-block/code-content.tsx +60 -0
  243. package/src/components/code-block/code-copy-button.tsx +73 -0
  244. package/src/components/code-block/code-tabs.tsx +170 -0
  245. package/src/components/code-block/index.ts +3 -0
  246. package/src/components/dynamic-item/dynamic-item.stories.tsx +261 -0
  247. package/src/components/dynamic-item/dynamic-item.tsx +68 -0
  248. package/src/components/dynamic-item/index.ts +1 -0
  249. package/src/components/icon/custom/index.ts +2 -0
  250. package/src/components/icon/custom/slack-logo.tsx +35 -0
  251. package/src/components/icon/custom/stripe-logo.tsx +27 -0
  252. package/src/components/icon/icon.stories.tsx +3 -1
  253. package/src/components/icon/icon.tsx +19 -1
  254. package/src/components/index.ts +9 -1
  255. package/src/components/inline-tips/inline-tips.stories.tsx +3 -3
  256. package/src/components/inline-tips/inline-tips.tsx +2 -2
  257. package/src/components/input/index.ts +1 -0
  258. package/src/components/{input.tsx → input/input.tsx} +1 -1
  259. package/src/components/item/index.ts +1 -0
  260. package/src/components/item/item.stories.tsx +150 -0
  261. package/src/components/item/item.tsx +182 -0
  262. package/src/components/label/index.ts +1 -0
  263. package/src/components/label/label.stories.tsx +67 -0
  264. package/src/components/label/label.tsx +15 -0
  265. package/src/components/moving-border/moving-border.tsx +67 -0
  266. package/src/components/textarea/textarea.tsx +1 -1
  267. package/src/components/theme/index.ts +1 -0
  268. package/src/components/toast/index.ts +2 -0
  269. package/src/components/toast/toast-custom.tsx +154 -0
  270. package/src/components/toast/toast.stories.tsx +369 -0
  271. package/src/components/toast/toast.tsx +41 -0
  272. package/src/components/tooltip/index.ts +1 -0
  273. package/src/components/tooltip/tooltip.stories.tsx +284 -0
  274. package/src/components/tooltip/tooltip.tsx +79 -10
  275. package/src/hooks/index.ts +3 -0
  276. package/src/hooks/useResolvedTheme.ts +34 -0
  277. package/src/hooks/useShikiHighlight.ts +140 -0
  278. package/src/hooks/useShikiStyleInjection.ts +34 -0
  279. package/dist/components/button.d.ts.map +0 -1
  280. package/dist/components/button.js.map +0 -1
  281. package/dist/components/button.stories.js.map +0 -1
  282. package/dist/components/input.d.ts.map +0 -1
  283. package/dist/components/input.js.map +0 -1
  284. package/dist/components/input.stories.js.map +0 -1
  285. package/dist/components/theme-provider.d.ts.map +0 -1
  286. package/dist/components/theme-provider.js.map +0 -1
  287. /package/dist/components/{input.d.ts → input/input.d.ts} +0 -0
  288. /package/src/components/{button.stories.tsx → button/button.stories.tsx} +0 -0
  289. /package/src/components/{input.stories.tsx → input/input.stories.tsx} +0 -0
  290. /package/src/components/{theme-provider.tsx → theme/theme-provider.tsx} +0 -0
@@ -0,0 +1,323 @@
1
+ import type {Meta, StoryObj} from '@storybook/react';
2
+ import {
3
+ CodeBlock,
4
+ CodeBlockBody,
5
+ CodeBlockContent,
6
+ CodeBlockCopyButton,
7
+ CodeBlockFilename,
8
+ CodeBlockFiles,
9
+ CodeBlockFooter,
10
+ CodeBlockHeader,
11
+ CodeBlockItem,
12
+ } from 'components/code-block';
13
+ import {CodeTabs} from 'components/code-block/code-tabs';
14
+
15
+ const meta = {
16
+ title: 'Components/CodeBlock',
17
+ component: CodeBlock,
18
+ parameters: {
19
+ layout: 'padded',
20
+ },
21
+ } satisfies Meta<typeof CodeBlock>;
22
+
23
+ export default meta;
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ const exampleCode = `jobs:
27
+ build:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v3
31
+ - name: Build
32
+ run: npm run build`;
33
+
34
+ const diffCode = `jobs:
35
+ build:
36
+ - runs-on: ubuntu-latest
37
+ + runs-on: shipfox-2vcpu-ubuntu-2404`;
38
+
39
+ export const Default: Story = {
40
+ args: {
41
+ data: [
42
+ {
43
+ language: 'yaml',
44
+ filename: '.github/workflows/<workflow-name>.yml',
45
+ code: exampleCode,
46
+ },
47
+ ],
48
+ defaultValue: 'yaml',
49
+ },
50
+ render: (args) => (
51
+ <CodeBlock {...args}>
52
+ <CodeBlockHeader>
53
+ <CodeBlockFiles>
54
+ {(item) => <CodeBlockFilename value={item.language}>{item.filename}</CodeBlockFilename>}
55
+ </CodeBlockFiles>
56
+ <CodeBlockCopyButton />
57
+ </CodeBlockHeader>
58
+ <CodeBlockBody>
59
+ {(item) => (
60
+ <CodeBlockItem value={item.language}>
61
+ <CodeBlockContent language={item.language}>{item.code}</CodeBlockContent>
62
+ </CodeBlockItem>
63
+ )}
64
+ </CodeBlockBody>
65
+ </CodeBlock>
66
+ ),
67
+ };
68
+
69
+ export const WithDiff: Story = {
70
+ args: {
71
+ data: [
72
+ {
73
+ language: 'yaml',
74
+ filename: '.github/workflows/<workflow-name>.yml',
75
+ code: diffCode,
76
+ },
77
+ ],
78
+ defaultValue: 'yaml',
79
+ },
80
+ render: (args) => (
81
+ <CodeBlock {...args}>
82
+ <CodeBlockHeader>
83
+ <CodeBlockFiles>
84
+ {(item) => <CodeBlockFilename value={item.language}>{item.filename}</CodeBlockFilename>}
85
+ </CodeBlockFiles>
86
+ <CodeBlockCopyButton />
87
+ </CodeBlockHeader>
88
+ <CodeBlockBody>
89
+ {(item) => (
90
+ <CodeBlockItem value={item.language}>
91
+ <CodeBlockContent language={item.language}>{item.code}</CodeBlockContent>
92
+ </CodeBlockItem>
93
+ )}
94
+ </CodeBlockBody>
95
+ </CodeBlock>
96
+ ),
97
+ };
98
+
99
+ export const WithFooterRunning: Story = {
100
+ args: {
101
+ data: [
102
+ {
103
+ language: 'yaml',
104
+ filename: '.github/workflows/<workflow-name>.yml',
105
+ code: diffCode,
106
+ },
107
+ ],
108
+ defaultValue: 'yaml',
109
+ },
110
+ render: (args) => (
111
+ <CodeBlock {...args}>
112
+ <CodeBlockHeader>
113
+ <CodeBlockFiles>
114
+ {(item) => <CodeBlockFilename value={item.language}>{item.filename}</CodeBlockFilename>}
115
+ </CodeBlockFiles>
116
+ <CodeBlockCopyButton />
117
+ </CodeBlockHeader>
118
+ <CodeBlockBody>
119
+ {(item) => (
120
+ <CodeBlockItem value={item.language}>
121
+ <CodeBlockContent language={item.language}>{item.code}</CodeBlockContent>
122
+ </CodeBlockItem>
123
+ )}
124
+ </CodeBlockBody>
125
+ <CodeBlockFooter
126
+ state="running"
127
+ message="Waiting for Shipfox runner event…"
128
+ description="This usually takes 30-60 seconds after you commit the workflow file."
129
+ />
130
+ </CodeBlock>
131
+ ),
132
+ };
133
+
134
+ export const WithFooterDone: Story = {
135
+ args: {
136
+ data: [
137
+ {
138
+ language: 'yaml',
139
+ filename: '.github/workflows/<workflow-name>.yml',
140
+ code: diffCode,
141
+ },
142
+ ],
143
+ defaultValue: 'yaml',
144
+ },
145
+ render: (args) => (
146
+ <CodeBlock {...args}>
147
+ <CodeBlockHeader>
148
+ <CodeBlockFiles>
149
+ {(item) => <CodeBlockFilename value={item.language}>{item.filename}</CodeBlockFilename>}
150
+ </CodeBlockFiles>
151
+ <CodeBlockCopyButton />
152
+ </CodeBlockHeader>
153
+ <CodeBlockBody>
154
+ {(item) => (
155
+ <CodeBlockItem value={item.language}>
156
+ <CodeBlockContent language={item.language}>{item.code}</CodeBlockContent>
157
+ </CodeBlockItem>
158
+ )}
159
+ </CodeBlockBody>
160
+ <CodeBlockFooter state="done" message="Runner connected!" />
161
+ </CodeBlock>
162
+ ),
163
+ };
164
+
165
+ const multipleFilesCode = {
166
+ 'src/utils/format.ts': `export function formatDate(date: Date): string {
167
+ return new Intl.DateTimeFormat('en-US', {
168
+ year: 'numeric',
169
+ month: 'long',
170
+ day: 'numeric',
171
+ }).format(date);
172
+ }
173
+
174
+ export function formatCurrency(amount: number): string {
175
+ return new Intl.NumberFormat('en-US', {
176
+ style: 'currency',
177
+ currency: 'USD',
178
+ }).format(amount);
179
+ }`,
180
+ 'src/api/client.ts': `import type {User} from './types';
181
+
182
+ export class ApiClient {
183
+ private baseUrl: string;
184
+
185
+ constructor(baseUrl: string) {
186
+ this.baseUrl = baseUrl;
187
+ }
188
+
189
+ async getUser(id: string): Promise<User> {
190
+ const response = await fetch(\`\${this.baseUrl}/users/\${id}\`);
191
+ if (!response.ok) {
192
+ throw new Error('Failed to fetch user');
193
+ }
194
+ return response.json();
195
+ }
196
+ }`,
197
+ 'src/components/Button.tsx': `import type {ComponentProps} from 'react';
198
+
199
+ export function Button({
200
+ children,
201
+ variant = 'primary',
202
+ ...props
203
+ }: ComponentProps<'button'> & {
204
+ variant?: 'primary' | 'secondary';
205
+ }) {
206
+ return (
207
+ <button
208
+ className={\`btn btn-\${variant}\`}
209
+ {...props}
210
+ >
211
+ {children}
212
+ </button>
213
+ );
214
+ }`,
215
+ };
216
+
217
+ export const MultipleFiles: Story = {
218
+ args: {
219
+ data: [],
220
+ defaultValue: '',
221
+ },
222
+ render: () => (
223
+ <CodeTabs
224
+ codes={multipleFilesCode}
225
+ defaultValue="src/api/client.ts"
226
+ syntaxHighlighting={true}
227
+ lang="typescript"
228
+ lineNumbers={true}
229
+ />
230
+ ),
231
+ };
232
+
233
+ export const WithoutLineNumbers: Story = {
234
+ args: {
235
+ data: [
236
+ {
237
+ language: 'yaml',
238
+ filename: '.github/workflows/<workflow-name>.yml',
239
+ code: exampleCode,
240
+ },
241
+ ],
242
+ defaultValue: 'yaml',
243
+ },
244
+ render: (args) => (
245
+ <CodeBlock {...args}>
246
+ <CodeBlockHeader>
247
+ <CodeBlockFiles>
248
+ {(item) => <CodeBlockFilename value={item.language}>{item.filename}</CodeBlockFilename>}
249
+ </CodeBlockFiles>
250
+ <CodeBlockCopyButton />
251
+ </CodeBlockHeader>
252
+ <CodeBlockBody>
253
+ {(item) => (
254
+ <CodeBlockItem value={item.language} lineNumbers={false}>
255
+ <CodeBlockContent language={item.language}>{item.code}</CodeBlockContent>
256
+ </CodeBlockItem>
257
+ )}
258
+ </CodeBlockBody>
259
+ </CodeBlock>
260
+ ),
261
+ };
262
+
263
+ const npmCode = `npm install @shipfox/tooling`;
264
+ const yarnCode = `yarn add @shipfox/tooling`;
265
+ const pnpmCode = `pnpm add @shipfox/tooling`;
266
+
267
+ export const Snippet: Story = {
268
+ args: {
269
+ data: [],
270
+ defaultValue: '',
271
+ },
272
+ render: () => (
273
+ <CodeTabs
274
+ codes={{
275
+ npm: npmCode,
276
+ yarn: yarnCode,
277
+ pnpm: pnpmCode,
278
+ }}
279
+ defaultValue="npm"
280
+ />
281
+ ),
282
+ };
283
+
284
+ const syntaxHighlightingCode = {
285
+ 'index.ts': `export function hello(name: string = 'World'): void {
286
+ // Say hello to the provided name
287
+ console.log(\`Hello, \${name}!\`);
288
+ }
289
+
290
+ export function greetEveryone(names: string[]): void {
291
+ for (const name of names) {
292
+ hello(name);
293
+ }
294
+ }
295
+
296
+ export type Greeting = {
297
+ language: string;
298
+ message: string;
299
+ };
300
+
301
+ export const greetings: Greeting[] = [
302
+ { language: 'en', message: 'Hello' },
303
+ { language: 'fr', message: 'Bonjour' },
304
+ { language: 'es', message: 'Hola' },
305
+ { language: 'de', message: 'Hallo' },
306
+ ];
307
+
308
+ export function printGreetings(): void {
309
+ for (const { language, message } of greetings) {
310
+ console.log(\`\${message}, \${language}!\`);
311
+ }
312
+ }`,
313
+ };
314
+
315
+ export const SyntaxHighlighting: StoryObj<typeof CodeTabs> = {
316
+ args: {
317
+ codes: syntaxHighlightingCode,
318
+ defaultValue: 'index.ts',
319
+ syntaxHighlighting: true,
320
+ lang: 'typescript',
321
+ },
322
+ render: (args) => <CodeTabs {...args} />,
323
+ };
@@ -0,0 +1,283 @@
1
+ import {useControllableState} from '@radix-ui/react-use-controllable-state';
2
+ import {CodeContent} from 'components/code-block/code-content';
3
+ import {CodeCopyButton} from 'components/code-block/code-copy-button';
4
+ import {useResolvedTheme} from 'hooks/useResolvedTheme';
5
+ import {useShikiHighlight} from 'hooks/useShikiHighlight';
6
+ import {useShikiStyleInjection} from 'hooks/useShikiStyleInjection';
7
+ import type {ComponentProps, HTMLAttributes, ReactNode} from 'react';
8
+ import {createContext, useContext} from 'react';
9
+ import {cn} from 'utils/cn';
10
+
11
+ export type BundledLanguage = string;
12
+
13
+ export type CodeBlockData = {
14
+ language: string;
15
+ filename: string;
16
+ code: string;
17
+ };
18
+
19
+ type CodeBlockContextType = {
20
+ value: string | undefined;
21
+ onValueChange: ((value: string) => void) | undefined;
22
+ data: CodeBlockData[];
23
+ };
24
+
25
+ const CodeBlockContext = createContext<CodeBlockContextType>({
26
+ value: undefined,
27
+ onValueChange: undefined,
28
+ data: [],
29
+ });
30
+
31
+ export type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
32
+ defaultValue?: string;
33
+ value?: string;
34
+ onValueChange?: (value: string) => void;
35
+ data: CodeBlockData[];
36
+ };
37
+
38
+ export function CodeBlock({
39
+ value: controlledValue,
40
+ onValueChange: controlledOnValueChange,
41
+ defaultValue,
42
+ className,
43
+ data,
44
+ ...props
45
+ }: CodeBlockProps) {
46
+ const [value, onValueChange] = useControllableState({
47
+ defaultProp: defaultValue ?? '',
48
+ prop: controlledValue,
49
+ onChange: controlledOnValueChange,
50
+ });
51
+
52
+ return (
53
+ <CodeBlockContext.Provider value={{value, onValueChange, data}}>
54
+ <div
55
+ className={cn(
56
+ 'size-full overflow-hidden rounded-12 bg-background-components-pressed dark:bg-background-contrast-base shadow-button-neutral',
57
+ className,
58
+ )}
59
+ {...props}
60
+ />
61
+ </CodeBlockContext.Provider>
62
+ );
63
+ }
64
+
65
+ export type CodeBlockHeaderProps = HTMLAttributes<HTMLDivElement>;
66
+
67
+ export function CodeBlockHeader({className, ...props}: CodeBlockHeaderProps) {
68
+ return (
69
+ <div
70
+ className={cn(
71
+ 'flex w-full flex-row items-center gap-12 overflow-clip bg-background-components-pressed dark:bg-background-contrast-base px-16 py-8',
72
+ className,
73
+ )}
74
+ {...props}
75
+ />
76
+ );
77
+ }
78
+
79
+ export type CodeBlockFilesProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
80
+ children: (item: CodeBlockData) => ReactNode;
81
+ };
82
+
83
+ export function CodeBlockFiles({className, children, ...props}: CodeBlockFilesProps) {
84
+ const {data} = useContext(CodeBlockContext);
85
+
86
+ return (
87
+ <div className={cn('flex grow flex-row items-center gap-12', className)} {...props}>
88
+ {data.map((item, index) => (
89
+ <div key={item.language || index}>{children(item)}</div>
90
+ ))}
91
+ </div>
92
+ );
93
+ }
94
+
95
+ export type CodeBlockFilenameProps = HTMLAttributes<HTMLDivElement> & {
96
+ value?: string;
97
+ };
98
+
99
+ export function CodeBlockFilename({className, value, children, ...props}: CodeBlockFilenameProps) {
100
+ const {value: activeValue} = useContext(CodeBlockContext);
101
+
102
+ if (value !== activeValue) {
103
+ return null;
104
+ }
105
+
106
+ return (
107
+ <div
108
+ className={cn(
109
+ 'flex min-h-0 min-w-0 flex-1 items-center overflow-hidden text-ellipsis whitespace-nowrap text-xs leading-20 font-code text-foreground-neutral-muted',
110
+ className,
111
+ )}
112
+ {...props}
113
+ >
114
+ {children}
115
+ </div>
116
+ );
117
+ }
118
+
119
+ export type CodeBlockCopyButtonProps = ComponentProps<'button'> & {
120
+ onCopy?: () => void;
121
+ onError?: (error: Error) => void;
122
+ timeout?: number;
123
+ };
124
+
125
+ export function CodeBlockCopyButton({
126
+ onCopy,
127
+ onError,
128
+ timeout = 2000,
129
+ children,
130
+ className,
131
+ ...props
132
+ }: CodeBlockCopyButtonProps) {
133
+ const {data, value} = useContext(CodeBlockContext);
134
+ const code = data.find((item) => item.language === value)?.code ?? '';
135
+
136
+ return (
137
+ <CodeCopyButton
138
+ content={code}
139
+ onCopy={() => onCopy?.()}
140
+ onError={onError}
141
+ timeout={timeout}
142
+ className={className}
143
+ {...props}
144
+ >
145
+ {children}
146
+ </CodeCopyButton>
147
+ );
148
+ }
149
+
150
+ type CodeBlockFallbackProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
151
+ children: string;
152
+ };
153
+
154
+ function CodeBlockFallback({children, className, ...props}: CodeBlockFallbackProps) {
155
+ const lines = children?.toString().split('\n') ?? [];
156
+ return (
157
+ <pre
158
+ className={cn('w-full font-code', className)}
159
+ {...(props as HTMLAttributes<HTMLPreElement>)}
160
+ >
161
+ <code>
162
+ {lines.map((line) => {
163
+ const isDiffRemove = line.trim().startsWith('-');
164
+ const isDiffAdd = line.trim().startsWith('+');
165
+ const diffClass = isDiffRemove ? 'diff remove' : isDiffAdd ? 'diff add' : '';
166
+
167
+ return (
168
+ <span className={cn('line', diffClass)} key={line}>
169
+ {line}
170
+ </span>
171
+ );
172
+ })}
173
+ </code>
174
+ </pre>
175
+ );
176
+ }
177
+
178
+ export type CodeBlockBodyProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
179
+ children: (item: CodeBlockData) => ReactNode;
180
+ };
181
+
182
+ export function CodeBlockBody({children, ...props}: CodeBlockBodyProps) {
183
+ const {data} = useContext(CodeBlockContext);
184
+
185
+ return (
186
+ <div {...props}>
187
+ {data.map((item, index) => (
188
+ <div key={item.language || index}>{children(item)}</div>
189
+ ))}
190
+ </div>
191
+ );
192
+ }
193
+
194
+ export type CodeBlockItemProps = HTMLAttributes<HTMLDivElement> & {
195
+ value: string;
196
+ lineNumbers?: boolean;
197
+ };
198
+
199
+ export function CodeBlockItem({
200
+ children,
201
+ lineNumbers = true,
202
+ className,
203
+ value,
204
+ ...props
205
+ }: CodeBlockItemProps) {
206
+ const {value: activeValue} = useContext(CodeBlockContext);
207
+
208
+ if (value !== activeValue) {
209
+ return null;
210
+ }
211
+
212
+ return (
213
+ <div
214
+ className={cn('flex w-full shrink-0 items-start overflow-clip px-4 pb-4 pt-0', className)}
215
+ {...props}
216
+ >
217
+ <div
218
+ className={cn(
219
+ 'flex min-h-0 min-w-0 flex-1 shrink-0 rounded-8 border border-border-contrast-bottom bg-background-neutral-base dark:bg-background-contrast-subtle font-code',
220
+ '[&_pre]:py-12 [&_pre]:font-code',
221
+ '[&_code]:w-full [&_code]:grid [&_code]:overflow-x-auto [&_code]:bg-transparent [&_code]:font-code [&_code]:text-xs [&_code]:leading-20 [&_code]:text-foreground-neutral-base',
222
+ '[&_.line]:block [&_.line]:px-12 [&_.line]:w-full [&_.line]:relative [&_.line]:font-code [&_.line]:min-h-[1.25rem]',
223
+ lineNumbers &&
224
+ '[&_code]:[counter-reset:line] [&_code]:[counter-increment:line_0] [&_.line]:before:content-[counter(line)] [&_.line]:before:inline-block [&_.line]:before:[counter-increment:line] [&_.line]:before:w-16 [&_.line]:before:mr-16 [&_.line]:before:text-xs [&_.line]:before:text-right [&_.line]:before:text-foreground-neutral-subtle [&_.line]:before:font-code [&_.line]:before:select-none',
225
+ '[&_.line.diff]:after:absolute [&_.line.diff]:after:left-0 [&_.line.diff]:after:top-0 [&_.line.diff]:after:bottom-0 [&_.line.diff]:after:w-1',
226
+ '[&_.line.diff.add]:bg-emerald-50 [&_.line.diff.add]:text-green-700 [&_.line.diff.add]:after:bg-emerald-500',
227
+ '[&_.line.diff.remove]:bg-rose-50 [&_.line.diff.remove]:text-red-700 [&_.line.diff.remove]:after:bg-rose-500',
228
+ 'dark:[&_.line.diff.add]:bg-emerald-500/10 dark:[&_.line.diff.add]:text-emerald-400',
229
+ 'dark:[&_.line.diff.remove]:bg-rose-500/10 dark:[&_.line.diff.remove]:text-rose-400',
230
+ )}
231
+ >
232
+ {children}
233
+ </div>
234
+ </div>
235
+ );
236
+ }
237
+
238
+ export type CodeBlockContentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
239
+ themes?: {
240
+ light: string;
241
+ dark: string;
242
+ };
243
+ language?: BundledLanguage;
244
+ syntaxHighlighting?: boolean;
245
+ children: string;
246
+ };
247
+
248
+ export function CodeBlockContent({
249
+ children,
250
+ themes = {
251
+ light: 'vitesse-light',
252
+ dark: 'vitesse-dark',
253
+ },
254
+ language = 'typescript',
255
+ syntaxHighlighting = false,
256
+ ...props
257
+ }: CodeBlockContentProps) {
258
+ const resolvedTheme = useResolvedTheme();
259
+
260
+ useShikiStyleInjection(syntaxHighlighting);
261
+
262
+ const {highlightedCode, isLoading} = useShikiHighlight({
263
+ code: children,
264
+ lang: language,
265
+ themes,
266
+ resolvedTheme,
267
+ syntaxHighlighting,
268
+ });
269
+
270
+ if (!syntaxHighlighting || isLoading) {
271
+ return <CodeBlockFallback {...props}>{children}</CodeBlockFallback>;
272
+ }
273
+
274
+ return (
275
+ <CodeContent
276
+ code={children}
277
+ highlightedCode={highlightedCode}
278
+ isLoading={isLoading}
279
+ syntaxHighlighting={syntaxHighlighting}
280
+ {...props}
281
+ />
282
+ );
283
+ }
@@ -0,0 +1,60 @@
1
+ import type {HTMLAttributes} from 'react';
2
+ import {cn} from 'utils/cn';
3
+
4
+ type CodeContentProps = HTMLAttributes<HTMLElement> & {
5
+ code: string;
6
+ highlightedCode?: string;
7
+ isLoading: boolean;
8
+ syntaxHighlighting: boolean;
9
+ lineNumbers?: boolean;
10
+ };
11
+
12
+ export function CodeContent({
13
+ code,
14
+ highlightedCode,
15
+ isLoading,
16
+ syntaxHighlighting,
17
+ lineNumbers = false,
18
+ className,
19
+ ...props
20
+ }: CodeContentProps) {
21
+ const shouldShowHighlighted = syntaxHighlighting && !isLoading && highlightedCode;
22
+
23
+ if (shouldShowHighlighted) {
24
+ return (
25
+ <div
26
+ className={cn(
27
+ 'shiki-override w-full overflow-x-auto font-code [&_pre]:m-0 [&_pre]:p-0 [&_pre]:bg-transparent [&_pre]:font-code [&_code]:font-code [&_code]:bg-transparent [&_code]:text-xs [&_code]:leading-20 [&_code]:text-foreground-neutral-base [&_code]:grid',
28
+ lineNumbers &&
29
+ '[&_code]:[counter-reset:line] [&_code]:[counter-increment:line_0] [&_.line]:before:content-[counter(line)] [&_.line]:before:inline-block [&_.line]:before:[counter-increment:line] [&_.line]:before:w-16 [&_.line]:before:mr-16 [&_.line]:before:text-xs [&_.line]:before:text-right [&_.line]:before:text-foreground-neutral-subtle [&_.line]:before:font-code [&_.line]:before:select-none',
30
+ '[&_.line]:block [&_.line]:px-12 [&_.line]:w-full [&_.line]:relative [&_.line]:font-code [&_.line]:text-xs [&_.line]:leading-20 [&_.line]:min-h-[1.25rem]',
31
+ className,
32
+ )}
33
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki outputs HTML for syntax highlighting
34
+ dangerouslySetInnerHTML={{__html: highlightedCode}}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ const lines = code.split('\n');
41
+
42
+ return (
43
+ <pre className={cn('m-0 p-0 bg-transparent font-code', className)} {...props}>
44
+ <code
45
+ className={cn(
46
+ 'w-full overflow-x-auto bg-transparent font-code text-xs leading-20 text-foreground-neutral-base',
47
+ 'grid',
48
+ lineNumbers &&
49
+ '[counter-reset:line] [counter-increment:line_0] [&_.line]:before:content-[counter(line)] [&_.line]:before:inline-block [&_.line]:before:[counter-increment:line] [&_.line]:before:w-16 [&_.line]:before:mr-16 [&_.line]:before:text-xs [&_.line]:before:text-right [&_.line]:before:text-foreground-neutral-subtle [&_.line]:before:font-code [&_.line]:before:select-none',
50
+ )}
51
+ >
52
+ {lines.map((line) => (
53
+ <span className="line px-12 w-full relative font-code text-xs leading-20" key={line}>
54
+ {line}
55
+ </span>
56
+ ))}
57
+ </code>
58
+ </pre>
59
+ );
60
+ }