untitledui 0.1.1

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/dist/commands/add.js +339 -0
  2. package/dist/commands/init.js +436 -0
  3. package/dist/helper/download-tar-api.js +129 -0
  4. package/dist/helper/download-tar.js +81 -0
  5. package/dist/helper/find-css-file.js +19 -0
  6. package/dist/helper/formatText.js +37 -0
  7. package/dist/helper/get-components-api.js +47 -0
  8. package/dist/helper/get-components-list.js +62 -0
  9. package/dist/helper/get-components.js +19 -0
  10. package/dist/helper/get-config.js +163 -0
  11. package/dist/helper/get-package-info.js +99 -0
  12. package/dist/helper/get-pkg-manager.js +16 -0
  13. package/dist/helper/get-project.js +176 -0
  14. package/dist/helper/install-template.js +29 -0
  15. package/dist/helper/match-color-css.js +82 -0
  16. package/dist/helper/update-color-css.js +134 -0
  17. package/dist/index.js +25 -0
  18. package/dist/package.json +50 -0
  19. package/dist/res/components.json +520 -0
  20. package/dist/res/config.json +3 -0
  21. package/package.json +61 -0
  22. package/templates/default/.prettierrc +10 -0
  23. package/templates/default/README.md +36 -0
  24. package/templates/default/eslint.config.mjs +58 -0
  25. package/templates/default/next.config.ts +6 -0
  26. package/templates/default/package.json +57 -0
  27. package/templates/default/postcss.config.js +5 -0
  28. package/templates/default/public/favicon.ico +0 -0
  29. package/templates/default/public/marketing/smiling-girl.png +0 -0
  30. package/templates/default/public/marketing/spirals.webp +0 -0
  31. package/templates/default/src/app/home-screen.tsx +109 -0
  32. package/templates/default/src/app/layout.tsx +42 -0
  33. package/templates/default/src/app/not-found.tsx +40 -0
  34. package/templates/default/src/app/page.tsx +3 -0
  35. package/templates/default/src/components/foundations/dot-icon.tsx +27 -0
  36. package/templates/default/src/components/foundations/featured-icon/featured-icons.tsx +153 -0
  37. package/templates/default/src/components/foundations/logo/UntitledLogo.tsx +63 -0
  38. package/templates/default/src/components/foundations/logo/UntitledLogoMinimal.tsx +164 -0
  39. package/templates/default/src/components/foundations/payment-icons/amex-icon.tsx +19 -0
  40. package/templates/default/src/components/foundations/payment-icons/apple-pay-icon.tsx +27 -0
  41. package/templates/default/src/components/foundations/payment-icons/discover-icon.tsx +34 -0
  42. package/templates/default/src/components/foundations/payment-icons/index.tsx +10 -0
  43. package/templates/default/src/components/foundations/payment-icons/mastercard-icon.tsx +39 -0
  44. package/templates/default/src/components/foundations/payment-icons/paypal-icon.tsx +45 -0
  45. package/templates/default/src/components/foundations/payment-icons/stripe-icon.tsx +27 -0
  46. package/templates/default/src/components/foundations/payment-icons/union-pay-icon.tsx +37 -0
  47. package/templates/default/src/components/foundations/payment-icons/visa-icon.tsx +27 -0
  48. package/templates/default/src/components/marketing/header-navigation/base-components/nav-menu-item.tsx +41 -0
  49. package/templates/default/src/components/marketing/header-navigation/components/header.tsx +245 -0
  50. package/templates/default/src/components/marketing/header-navigation/dropdown-header-navigation.tsx +53 -0
  51. package/templates/default/src/components/shared/avatar/avatar-label-group.tsx +32 -0
  52. package/templates/default/src/components/shared/avatar/avatar-profile-photo.tsx +84 -0
  53. package/templates/default/src/components/shared/avatar/avatar.tsx +131 -0
  54. package/templates/default/src/components/shared/avatar/base-components/avatar-add-button.tsx +33 -0
  55. package/templates/default/src/components/shared/avatar/base-components/avatar-company-icon.tsx +26 -0
  56. package/templates/default/src/components/shared/avatar/base-components/avatar-online-indicator.tsx +31 -0
  57. package/templates/default/src/components/shared/avatar/base-components/index.ts +4 -0
  58. package/templates/default/src/components/shared/avatar/base-components/verified-tick.tsx +34 -0
  59. package/templates/default/src/components/shared/avatar/utils.ts +12 -0
  60. package/templates/default/src/components/shared/badges/badge-groups.tsx +176 -0
  61. package/templates/default/src/components/shared/badges/badge-types.ts +264 -0
  62. package/templates/default/src/components/shared/badges/badges.tsx +479 -0
  63. package/templates/default/src/components/shared/button-group/button-group.tsx +97 -0
  64. package/templates/default/src/components/shared/buttons/app-store-buttons-outline.tsx +454 -0
  65. package/templates/default/src/components/shared/buttons/app-store-buttons.tsx +806 -0
  66. package/templates/default/src/components/shared/buttons/button-utility.tsx +87 -0
  67. package/templates/default/src/components/shared/buttons/button.tsx +284 -0
  68. package/templates/default/src/components/shared/buttons/close-button.tsx +39 -0
  69. package/templates/default/src/components/shared/buttons/social-button.tsx +135 -0
  70. package/templates/default/src/components/shared/buttons/social-logos.tsx +115 -0
  71. package/templates/default/src/components/shared/checkbox/checkbox.tsx +120 -0
  72. package/templates/default/src/components/shared/dropdown/dropdown.tsx +138 -0
  73. package/templates/default/src/components/shared/input-dropdown/combobox.tsx +161 -0
  74. package/templates/default/src/components/shared/input-dropdown/dropdown-item.tsx +98 -0
  75. package/templates/default/src/components/shared/input-dropdown/input-dropdown.tsx +172 -0
  76. package/templates/default/src/components/shared/input-dropdown/multi-select.tsx +373 -0
  77. package/templates/default/src/components/shared/input-dropdown/popover.tsx +36 -0
  78. package/templates/default/src/components/shared/input-dropdown/select.tsx +63 -0
  79. package/templates/default/src/components/shared/inputs/file-upload-trigger.tsx +74 -0
  80. package/templates/default/src/components/shared/inputs/form/form.tsx +10 -0
  81. package/templates/default/src/components/shared/inputs/hint-text.tsx +34 -0
  82. package/templates/default/src/components/shared/inputs/input/index.tsx +189 -0
  83. package/templates/default/src/components/shared/inputs/input/input-payment.tsx +134 -0
  84. package/templates/default/src/components/shared/inputs/input/input-with-button.tsx +69 -0
  85. package/templates/default/src/components/shared/inputs/input/input-with-dropdown.tsx +178 -0
  86. package/templates/default/src/components/shared/inputs/input/input-with-prefix.tsx +74 -0
  87. package/templates/default/src/components/shared/inputs/label.tsx +46 -0
  88. package/templates/default/src/components/shared/inputs/textarea/textarea.tsx +82 -0
  89. package/templates/default/src/components/shared/progress-indicators/progress-circles.tsx +176 -0
  90. package/templates/default/src/components/shared/progress-indicators/progress-indicators.tsx +86 -0
  91. package/templates/default/src/components/shared/progress-indicators/simple-circle.tsx +29 -0
  92. package/templates/default/src/components/shared/radio-buttons/radio-buttons.tsx +125 -0
  93. package/templates/default/src/components/shared/radio-groups/radio-group-avatar.tsx +62 -0
  94. package/templates/default/src/components/shared/radio-groups/radio-group-checkbox.tsx +72 -0
  95. package/templates/default/src/components/shared/radio-groups/radio-group-icon-card.tsx +95 -0
  96. package/templates/default/src/components/shared/radio-groups/radio-group-icon-simple.tsx +70 -0
  97. package/templates/default/src/components/shared/radio-groups/radio-group-payment-icon.tsx +71 -0
  98. package/templates/default/src/components/shared/radio-groups/radio-group-radio-button.tsx +76 -0
  99. package/templates/default/src/components/shared/radio-groups/radio-groups.tsx +8 -0
  100. package/templates/default/src/components/shared/slider/slider.tsx +76 -0
  101. package/templates/default/src/components/shared/tags/base-components/tag-checkbox.tsx +47 -0
  102. package/templates/default/src/components/shared/tags/base-components/tag-close-x.tsx +34 -0
  103. package/templates/default/src/components/shared/tags/tags.tsx +162 -0
  104. package/templates/default/src/components/shared/toggle/toggle.tsx +140 -0
  105. package/templates/default/src/components/shared/tooltips/tooltips.tsx +140 -0
  106. package/templates/default/src/components/utils/index.ts +48 -0
  107. package/templates/default/src/components/utils/isDeepEqual.ts +31 -0
  108. package/templates/default/src/components/utils/isReactComponent.ts +22 -0
  109. package/templates/default/src/components/utils/mergeRefs.ts +19 -0
  110. package/templates/default/src/components/utils/useBreakpoint.ts +36 -0
  111. package/templates/default/src/components/utils/uuid.ts +9 -0
  112. package/templates/default/src/fonts/GeistMonoVF.woff +0 -0
  113. package/templates/default/src/fonts/GeistVF.woff +0 -0
  114. package/templates/default/src/hooks/use-resize-observer.tsx +55 -0
  115. package/templates/default/src/providers/theme.tsx +11 -0
  116. package/templates/default/src/styles/colors.css +805 -0
  117. package/templates/default/src/styles/globals.css +86 -0
  118. package/templates/default/src/styles/text-styles.css +177 -0
  119. package/templates/default/src/styles/theme.css +1310 -0
  120. package/templates/default/src/styles/typography.css +428 -0
  121. package/templates/default/tsconfig.json +27 -0
@@ -0,0 +1,479 @@
1
+ "use client";
2
+
3
+ import type { MouseEventHandler, ReactNode } from "react";
4
+ import { X as CloseX } from "@untitledui/icons";
5
+ import Dot from "@/components/foundations/dot-icon";
6
+ import { cx } from "@/components/utils";
7
+ import type { BadgeColors, BadgeTypeToColorMap, BadgeTypes, FlagTypes, IconComponentType, Sizes } from "./badge-types";
8
+ import { badgeTypes } from "./badge-types";
9
+
10
+ export const filledColors: Record<BadgeColors, { root: string; addon: string; addonButton: string }> = {
11
+ gray: {
12
+ root: "bg-utility-gray-50 text-utility-gray-700 ring-utility-gray-200",
13
+ addon: "text-utility-gray-500",
14
+ addonButton: "hover:bg-utility-gray-100 text-utility-gray-400 hover:text-utility-gray-500",
15
+ },
16
+ brand: {
17
+ root: "bg-utility-brand-50 text-utility-brand-700 ring-utility-brand-200",
18
+ addon: "text-utility-brand-500",
19
+ addonButton: "hover:bg-utility-brand-100 text-utility-brand-400 hover:text-utility-brand-500",
20
+ },
21
+ error: {
22
+ root: "bg-utility-error-50 text-utility-error-700 ring-utility-error-200",
23
+ addon: "text-utility-error-500",
24
+ addonButton: "hover:bg-utility-error-100 text-utility-error-400 hover:text-utility-error-500",
25
+ },
26
+ warning: {
27
+ root: "bg-utility-warning-50 text-utility-warning-700 ring-utility-warning-200",
28
+ addon: "text-utility-warning-500",
29
+ addonButton: "hover:bg-utility-warning-100 text-utility-warning-400 hover:text-utility-warning-500",
30
+ },
31
+ success: {
32
+ root: "bg-utility-success-50 text-utility-success-700 ring-utility-success-200",
33
+ addon: "text-utility-success-500",
34
+ addonButton: "hover:bg-utility-success-100 text-utility-success-400 hover:text-utility-success-500",
35
+ },
36
+ "gray-blue": {
37
+ root: "bg-utility-gray-blue-50 text-utility-gray-blue-700 ring-utility-gray-blue-200",
38
+ addon: "text-utility-gray-blue-500",
39
+ addonButton: "hover:bg-utility-gray-blue-100 text-utility-gray-blue-400 hover:text-utility-gray-blue-500",
40
+ },
41
+ "blue-light": {
42
+ root: "bg-utility-blue-light-50 text-utility-blue-light-700 ring-utility-blue-light-200",
43
+ addon: "text-utility-blue-light-500",
44
+ addonButton: "hover:bg-utility-blue-light-100 text-utility-blue-light-400 hover:text-utility-blue-light-500",
45
+ },
46
+ blue: {
47
+ root: "bg-utility-blue-50 text-utility-blue-700 ring-utility-blue-200",
48
+ addon: "text-utility-blue-500",
49
+ addonButton: "hover:bg-utility-blue-100 text-utility-blue-400 hover:text-utility-blue-500",
50
+ },
51
+ indigo: {
52
+ root: "bg-utility-indigo-50 text-utility-indigo-700 ring-utility-indigo-200",
53
+ addon: "text-utility-indigo-500",
54
+ addonButton: "hover:bg-utility-indigo-100 text-utility-indigo-400 hover:text-utility-indigo-500",
55
+ },
56
+ purple: {
57
+ root: "bg-utility-purple-50 text-utility-purple-700 ring-utility-purple-200",
58
+ addon: "text-utility-purple-500",
59
+ addonButton: "hover:bg-utility-purple-100 text-utility-purple-400 hover:text-utility-purple-500",
60
+ },
61
+ pink: {
62
+ root: "bg-utility-pink-50 text-utility-pink-700 ring-utility-pink-200",
63
+ addon: "text-utility-pink-500",
64
+ addonButton: "hover:bg-utility-pink-100 text-utility-pink-400 hover:text-utility-pink-500",
65
+ },
66
+ orange: {
67
+ root: "bg-utility-orange-50 text-utility-orange-700 ring-utility-orange-200",
68
+ addon: "text-utility-orange-500",
69
+ addonButton: "hover:bg-utility-orange-100 text-utility-orange-400 hover:text-utility-orange-500",
70
+ },
71
+ };
72
+
73
+ const addonOnlyColors = Object.fromEntries(Object.entries(filledColors).map(([key, value]) => [key, { root: "", addon: value.addon }])) as Record<
74
+ BadgeColors,
75
+ { root: string; addon: string }
76
+ >;
77
+
78
+ const withPillTypes = {
79
+ [badgeTypes.pillColor]: {
80
+ common: "size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset",
81
+ styles: filledColors,
82
+ },
83
+ [badgeTypes.badgeColor]: {
84
+ common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset",
85
+ styles: filledColors,
86
+ },
87
+ [badgeTypes.badgeModern]: {
88
+ common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset shadow-xs",
89
+ styles: {
90
+ gray: {
91
+ root: "bg-primary text-secondary ring-border-primary",
92
+ addon: "text-gray-500",
93
+ addonButton: "hover:bg-utility-gray-100 text-utility-gray-400 hover:text-utility-gray-500",
94
+ },
95
+ },
96
+ },
97
+ };
98
+
99
+ const withBadgeTypes = {
100
+ [badgeTypes.pillColor]: {
101
+ common: "size-max flex items-center whitespace-nowrap rounded-full ring-1 ring-inset",
102
+ styles: filledColors,
103
+ },
104
+ [badgeTypes.badgeColor]: {
105
+ common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset",
106
+ styles: filledColors,
107
+ },
108
+ [badgeTypes.badgeModern]: {
109
+ common: "size-max flex items-center whitespace-nowrap rounded-md ring-1 ring-inset bg-primary text-secondary ring-border-primary shadow-xs",
110
+ styles: addonOnlyColors,
111
+ },
112
+ };
113
+
114
+ export type BadgeColor<T extends BadgeTypes> = BadgeTypeToColorMap<typeof withPillTypes>[T];
115
+
116
+ interface BadgeProps<T extends BadgeTypes> {
117
+ type?: T;
118
+ size?: Sizes;
119
+ color?: BadgeColor<T>;
120
+ children: ReactNode;
121
+ className?: string;
122
+ }
123
+
124
+ export const Badge = <T extends BadgeTypes>(props: BadgeProps<T>) => {
125
+ const { type = "pill-color", size = "md", color = "gray", children } = props;
126
+ const colors = withPillTypes[type];
127
+
128
+ const pillSizes = {
129
+ sm: {
130
+ root: "py-0.5 px-2 tt-xs-md",
131
+ },
132
+ md: {
133
+ root: "py-0.5 px-2.5 tt-sm-md",
134
+ },
135
+ lg: {
136
+ root: "py-1 px-3 tt-sm-md",
137
+ },
138
+ };
139
+ const badgeSizes = {
140
+ sm: {
141
+ root: "py-0.5 px-1.5 tt-xs-md",
142
+ },
143
+ md: {
144
+ root: "py-0.5 px-2 tt-sm-md",
145
+ },
146
+ lg: {
147
+ root: "py-1 px-2.5 tt-sm-md rounded-lg",
148
+ },
149
+ };
150
+
151
+ const sizes = {
152
+ [badgeTypes.pillColor]: pillSizes,
153
+ [badgeTypes.badgeColor]: badgeSizes,
154
+ [badgeTypes.badgeModern]: badgeSizes,
155
+ };
156
+
157
+ return <span className={cx(colors.common, sizes[type][size].root, (colors.styles[color] as { root: string }).root, props.className)}>{children}</span>;
158
+ };
159
+
160
+ interface BadgeWithDotProps<T extends BadgeTypes> {
161
+ type?: T;
162
+ size?: Sizes;
163
+ color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];
164
+ className?: string;
165
+ children: ReactNode;
166
+ }
167
+
168
+ export const BadgeWithDot = <T extends BadgeTypes>(props: BadgeWithDotProps<T>) => {
169
+ const { size = "md", color = "gray", type = "pill-color", className, children } = props;
170
+
171
+ const colors = withBadgeTypes[type];
172
+
173
+ const pillSizes = {
174
+ sm: {
175
+ root: "gap-1 py-0.5 pl-1.5 pr-2 tt-xs-md",
176
+ },
177
+ md: {
178
+ root: "gap-1.5 py-0.5 pl-2 pr-2.5 tt-sm-md",
179
+ },
180
+ lg: {
181
+ root: "gap-1.5 py-1 pl-2.5 pr-3 tt-sm-md",
182
+ },
183
+ };
184
+ const badgeSizes = {
185
+ sm: {
186
+ root: "gap-1 py-0.5 px-1.5 tt-xs-md",
187
+ },
188
+ md: {
189
+ root: "gap-1.5 py-0.5 px-2 tt-sm-md",
190
+ },
191
+ lg: {
192
+ root: "gap-1.5 py-1 px-2.5 tt-sm-md rounded-lg",
193
+ },
194
+ };
195
+
196
+ const sizes = {
197
+ [badgeTypes.pillColor]: pillSizes,
198
+ [badgeTypes.badgeColor]: badgeSizes,
199
+ [badgeTypes.badgeModern]: badgeSizes,
200
+ };
201
+
202
+ return (
203
+ <span className={cx(colors.common, sizes[type][size].root, colors.styles[color].root, className)}>
204
+ <Dot className={colors.styles[color].addon} size="sm" />
205
+ {children}
206
+ </span>
207
+ );
208
+ };
209
+
210
+ interface BadgeWithIconProps<T extends BadgeTypes> {
211
+ type?: T;
212
+ size?: Sizes;
213
+ color?: BadgeTypeToColorMap<typeof withBadgeTypes>[T];
214
+ iconLeading?: IconComponentType;
215
+ iconTrailing?: IconComponentType;
216
+ children: ReactNode;
217
+ className?: string;
218
+ }
219
+
220
+ export const BadgeWithIcon = <T extends BadgeTypes>(props: BadgeWithIconProps<T>) => {
221
+ const { size = "md", color = "gray", type = "pill-color", iconLeading: IconLeading, iconTrailing: IconTrailing, children, className } = props;
222
+
223
+ const colors = withBadgeTypes[type];
224
+
225
+ const icon = IconLeading ? "leading" : "trailing";
226
+
227
+ const pillSizes = {
228
+ sm: {
229
+ trailing: "gap-0.5 py-0.5 pl-2 pr-1.5 tt-xs-md",
230
+ leading: "gap-0.5 py-0.5 pr-2 pl-1.5 tt-xs-md",
231
+ },
232
+ md: {
233
+ trailing: "gap-1 py-0.5 pl-2.5 pr-2 tt-sm-md",
234
+ leading: "gap-1 py-0.5 pr-2.5 pl-2 tt-sm-md",
235
+ },
236
+ lg: {
237
+ trailing: "gap-1 py-1 pl-3 pr-2.5 tt-sm-md",
238
+ leading: "gap-1 py-1 pr-3 pl-2.5 tt-sm-md",
239
+ },
240
+ };
241
+ const badgeSizes = {
242
+ sm: {
243
+ trailing: "gap-0.5 py-0.5 pl-2 pr-1.5 tt-xs-md",
244
+ leading: "gap-0.5 py-0.5 pr-2 pl-1.5 tt-xs-md",
245
+ },
246
+ md: {
247
+ trailing: "gap-1 py-0.5 pl-2 pr-1.5 tt-sm-md",
248
+ leading: "gap-1 py-0.5 pr-2 pl-1.5 tt-sm-md",
249
+ },
250
+ lg: {
251
+ trailing: "gap-1 py-1 pl-2.5 pr-2 tt-sm-md rounded-lg",
252
+ leading: "gap-1 py-1 pr-2.5 pl-2 tt-sm-md rounded-lg",
253
+ },
254
+ };
255
+
256
+ const sizes = {
257
+ [badgeTypes.pillColor]: pillSizes,
258
+ [badgeTypes.badgeColor]: badgeSizes,
259
+ [badgeTypes.badgeModern]: badgeSizes,
260
+ };
261
+
262
+ return (
263
+ <span className={cx(colors.common, sizes[type][size][icon], colors.styles[color].root, className)}>
264
+ {IconLeading && <IconLeading className={cx(colors.styles[color].addon, "size-3")} strokeWidth={3} />}
265
+ {children}
266
+ {IconTrailing && <IconTrailing className={cx(colors.styles[color].addon, "size-3")} strokeWidth={3} />}
267
+ </span>
268
+ );
269
+ };
270
+
271
+ interface BadgeWithFlagProps<T extends BadgeTypes> {
272
+ type?: T;
273
+ size?: Sizes;
274
+ flag?: FlagTypes;
275
+ color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
276
+ children: ReactNode;
277
+ }
278
+
279
+ export const BadgeWithFlag = <T extends BadgeTypes>(props: BadgeWithFlagProps<T>) => {
280
+ const { size = "md", color = "gray", flag = "AU", type = "pill-color", children } = props;
281
+
282
+ const colors = withPillTypes[type];
283
+
284
+ const pillSizes = {
285
+ sm: {
286
+ root: "gap-1 py-0.5 pl-[3px] pr-2 tt-xs-md",
287
+ },
288
+ md: {
289
+ root: "gap-1.5 py-0.5 pl-1 pr-2.5 tt-sm-md",
290
+ },
291
+ lg: {
292
+ root: "gap-1.5 py-1 pl-1.5 pr-3 tt-sm-md",
293
+ },
294
+ };
295
+ const badgeSizes = {
296
+ sm: {
297
+ root: "gap-1 py-0.5 pl-1 pr-1.5 tt-xs-md",
298
+ },
299
+ md: {
300
+ root: "gap-1.5 py-0.5 pl-1.5 pr-2 tt-sm-md",
301
+ },
302
+ lg: {
303
+ root: "gap-1.5 py-1 pl-2 pr-2.5 tt-sm-md rounded-lg",
304
+ },
305
+ };
306
+
307
+ const sizes = {
308
+ [badgeTypes.pillColor]: pillSizes,
309
+ [badgeTypes.badgeColor]: badgeSizes,
310
+ [badgeTypes.badgeModern]: badgeSizes,
311
+ };
312
+
313
+ return (
314
+ <span className={cx(colors.common, sizes[type][size].root, colors.styles[color].root)}>
315
+ <img src={`https://untitledui.com/images/flags/${flag}.svg`} className="size-4 max-w-none rounded-full" />
316
+ {children}
317
+ </span>
318
+ );
319
+ };
320
+
321
+ interface BadgeWithImageProps<T extends BadgeTypes> {
322
+ type?: T;
323
+ size?: Sizes;
324
+ imgSrc: string;
325
+ color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
326
+ children: ReactNode;
327
+ }
328
+
329
+ export const BadgeWithImage = <T extends BadgeTypes>(props: BadgeWithImageProps<T>) => {
330
+ const { size = "md", color = "gray", type = "pill-color", imgSrc, children } = props;
331
+
332
+ const colors = withPillTypes[type];
333
+
334
+ const pillSizes = {
335
+ sm: {
336
+ root: "gap-1 py-0.5 pl-[3px] pr-2 tt-xs-md",
337
+ },
338
+ md: {
339
+ root: "gap-1.5 py-0.5 pl-1 pr-2.5 tt-sm-md",
340
+ },
341
+ lg: {
342
+ root: "gap-1.5 py-1 pl-1.5 pr-3 tt-sm-md",
343
+ },
344
+ };
345
+ const badgeSizes = {
346
+ sm: {
347
+ root: "gap-1 py-0.5 pl-1 pr-1.5 tt-xs-md",
348
+ },
349
+ md: {
350
+ root: "gap-1.5 py-0.5 pl-1.5 pr-2 tt-sm-md",
351
+ },
352
+ lg: {
353
+ root: "gap-1.5 py-1 pl-2 pr-2.5 tt-sm-md rounded-lg",
354
+ },
355
+ };
356
+
357
+ const sizes = {
358
+ [badgeTypes.pillColor]: pillSizes,
359
+ [badgeTypes.badgeColor]: badgeSizes,
360
+ [badgeTypes.badgeModern]: badgeSizes,
361
+ };
362
+
363
+ return (
364
+ <span className={cx(colors.common, sizes[type][size].root, colors.styles[color].root)}>
365
+ <img src={imgSrc} className="size-4 max-w-none rounded-full" />
366
+ {children}
367
+ </span>
368
+ );
369
+ };
370
+
371
+ interface BadgeWithButtonProps<T extends BadgeTypes> {
372
+ type?: T;
373
+ size?: Sizes;
374
+ icon?: IconComponentType;
375
+ color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
376
+ onButtonClick?: MouseEventHandler<HTMLButtonElement>;
377
+ children: ReactNode;
378
+ }
379
+
380
+ export const BadgeWithButton = <T extends BadgeTypes>(props: BadgeWithButtonProps<T>) => {
381
+ const { size = "md", color = "gray", type = "pill-color", icon: Icon = CloseX, children } = props;
382
+
383
+ const colors = withPillTypes[type];
384
+
385
+ const pillSizes = {
386
+ sm: {
387
+ root: "gap-0.5 py-0.5 pl-2 pr-[3px] tt-xs-md",
388
+ },
389
+ md: {
390
+ root: "gap-0.5 py-0.5 pl-2.5 pr-1 tt-sm-md",
391
+ },
392
+ lg: {
393
+ root: "gap-0.5 py-1 pl-3 pr-1.5 tt-sm-md",
394
+ },
395
+ };
396
+ const badgeSizes = {
397
+ sm: {
398
+ root: "gap-0.5 py-0.5 pl-1.5 pr-[3px] tt-xs-md",
399
+ },
400
+ md: {
401
+ root: "gap-0.5 py-0.5 pl-2 pr-1 tt-sm-md",
402
+ },
403
+ lg: {
404
+ root: "gap-0.5 py-1 pl-2.5 pr-1.5 tt-sm-md rounded-lg",
405
+ },
406
+ };
407
+
408
+ const sizes = {
409
+ [badgeTypes.pillColor]: pillSizes,
410
+ [badgeTypes.badgeColor]: badgeSizes,
411
+ [badgeTypes.badgeModern]: badgeSizes,
412
+ };
413
+
414
+ return (
415
+ <span className={cx(colors.common, sizes[type][size].root, colors.styles[color].root)}>
416
+ {children}
417
+ <button
418
+ type="button"
419
+ className={cx(
420
+ "flex cursor-pointer items-center justify-center p-0.5 outline-focus-ring transition duration-100 ease-linear focus:outline-2",
421
+ colors.styles[color].addonButton,
422
+ type === "pill-color" ? "rounded-full" : "rounded-[3px]",
423
+ )}
424
+ onClick={props.onButtonClick}
425
+ >
426
+ <Icon className="size-3 stroke-[3px] transition-inherit-all" />
427
+ </button>
428
+ </span>
429
+ );
430
+ };
431
+
432
+ interface BadgeIconProps<T extends BadgeTypes> {
433
+ type?: T;
434
+ size?: Sizes;
435
+ icon: IconComponentType;
436
+ color?: BadgeTypeToColorMap<typeof withPillTypes>[T];
437
+ children?: ReactNode;
438
+ }
439
+
440
+ export const BadgeIcon = <T extends BadgeTypes>(props: BadgeIconProps<T>) => {
441
+ const { size = "md", color = "gray", type = "pill-color", icon: Icon } = props;
442
+
443
+ const colors = withPillTypes[type];
444
+
445
+ const pillSizes = {
446
+ sm: {
447
+ root: "p-[5px]",
448
+ },
449
+ md: {
450
+ root: "p-1.5",
451
+ },
452
+ lg: {
453
+ root: "p-2",
454
+ },
455
+ };
456
+ const badgeSizes = {
457
+ sm: {
458
+ root: "p-[5px]",
459
+ },
460
+ md: {
461
+ root: "p-1.5",
462
+ },
463
+ lg: {
464
+ root: "p-2 rounded-lg",
465
+ },
466
+ };
467
+
468
+ const sizes = {
469
+ [badgeTypes.pillColor]: pillSizes,
470
+ [badgeTypes.badgeColor]: badgeSizes,
471
+ [badgeTypes.badgeModern]: badgeSizes,
472
+ };
473
+
474
+ return (
475
+ <span className={cx(colors.common, sizes[type][size].root, colors.styles[color].root)}>
476
+ <Icon className={cx("size-3 stroke-[3px]", colors.styles[color].addon)} />
477
+ </span>
478
+ );
479
+ };
@@ -0,0 +1,97 @@
1
+ "use client";
2
+
3
+ import { type FC, type PropsWithChildren, type ReactNode, type RefAttributes, createContext, isValidElement, useContext } from "react";
4
+ import { ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps } from "react-aria-components";
5
+ import { cx, sortCx } from "@/components/utils";
6
+ import { isReactComponent } from "@/components/utils/isReactComponent";
7
+
8
+ export const styles = sortCx({
9
+ common: {
10
+ root: [
11
+ "group/button-group inline-flex h-max cursor-pointer items-center bg-primary font-semibold whitespace-nowrap text-secondary shadow-skeumorphic ring-1 ring-border-primary outline-brand transition duration-100 ease-linear ring-inset",
12
+ // Hover and focus styles
13
+ "hover:bg-primary_hover hover:text-secondary_hover focus:z-10 focus:outline-2 focus:outline-offset-2",
14
+ // Disabled styles
15
+ "disabled:cursor-not-allowed disabled:bg-primary disabled:text-disabled",
16
+ // Selected styles
17
+ "selected:bg-active selected:disabled:bg-disabled_subtle",
18
+ ].join(" "),
19
+ icon: "pointer-events-none text-fg-quaternary transition-[inherit] group-hover/button-group:text-fg-quaternary_hover group-disabled/button-group:text-fg-disabled_subtle",
20
+ },
21
+
22
+ sizes: {
23
+ sm: {
24
+ root: "gap-1.5 px-3.5 py-2 text-sm leading-sm not-last:pr-[calc(calc(var(--spacing)*3.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3 data-icon-only:p-2",
25
+ icon: "size-5",
26
+ },
27
+ md: {
28
+ root: "gap-2 px-4 py-2.5 text-sm leading-sm not-last:pr-[calc(calc(var(--spacing)*4)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3.5 data-icon-only:px-3",
29
+ icon: "size-5",
30
+ },
31
+ lg: {
32
+ root: "gap-2 px-4.5 py-2.5 text-md leading-md not-last:pr-[calc(calc(var(--spacing)*4.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-4 data-icon-only:p-3",
33
+ icon: "size-5",
34
+ },
35
+ },
36
+ });
37
+
38
+ type ButtonSize = keyof typeof styles.sizes;
39
+
40
+ const ButtonGroupContext = createContext<{ size: ButtonSize }>({ size: "md" });
41
+
42
+ interface ButtonGroupItemProps extends ToggleButtonProps, RefAttributes<HTMLButtonElement> {
43
+ iconLeading?: FC<{ className?: string }> | ReactNode;
44
+ iconTrailing?: FC<{ className?: string }> | ReactNode;
45
+ onClick?: () => void;
46
+ className?: string;
47
+ }
48
+
49
+ export const ButtonGroupItem = ({
50
+ iconLeading: IconLeading,
51
+ iconTrailing: IconTrailing,
52
+ children,
53
+ className,
54
+ ...otherProps
55
+ }: PropsWithChildren<ButtonGroupItemProps>) => {
56
+ const context = useContext(ButtonGroupContext);
57
+
58
+ if (!context) {
59
+ throw new Error("ButtonGroupItem must be used within a ButtonGroup component");
60
+ }
61
+
62
+ const { size } = context;
63
+
64
+ const isIcon = (IconLeading || IconTrailing) && !children;
65
+
66
+ return (
67
+ <ToggleButton
68
+ {...otherProps}
69
+ data-icon-only={isIcon ? true : undefined}
70
+ data-icon-leading={IconLeading ? true : undefined}
71
+ className={cx(styles.common.root, styles.sizes[size].root, className)}
72
+ >
73
+ {isReactComponent(IconLeading) && <IconLeading className={cx(styles.common.icon, styles.sizes[size].icon)} />}
74
+ {isValidElement(IconLeading) && IconLeading}
75
+
76
+ {children}
77
+
78
+ {isReactComponent(IconTrailing) && <IconTrailing className={cx(styles.common.icon, styles.sizes[size].icon)} />}
79
+ {isValidElement(IconTrailing) && IconTrailing}
80
+ </ToggleButton>
81
+ );
82
+ };
83
+
84
+ interface ButtonGroupProps extends Omit<ToggleButtonGroupProps, "orientation">, RefAttributes<HTMLDivElement> {
85
+ size?: ButtonSize;
86
+ className?: string;
87
+ }
88
+
89
+ export const ButtonGroup = ({ children, size = "md", className, ...otherProps }: ButtonGroupProps) => {
90
+ return (
91
+ <ButtonGroupContext.Provider value={{ size }}>
92
+ <ToggleButtonGroup className={cx("relative z-0 inline-flex w-max -space-x-px rounded-lg shadow-xs", className)} {...otherProps}>
93
+ {children}
94
+ </ToggleButtonGroup>
95
+ </ButtonGroupContext.Provider>
96
+ );
97
+ };