@tuturuuu/ui 0.19.0 → 0.20.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 (55) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/biome.json +1 -1
  3. package/package.json +62 -50
  4. package/src/components/ui/badge.tsx +2 -0
  5. package/src/components/ui/button.tsx +2 -0
  6. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +48 -4
  7. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +22 -0
  8. package/src/components/ui/custom/animated-slot-text.tsx +20 -0
  9. package/src/components/ui/custom/common-footer.tsx +262 -237
  10. package/src/components/ui/custom/language-dropdown-item.tsx +3 -10
  11. package/src/components/ui/custom/language-toggle.test.tsx +30 -0
  12. package/src/components/ui/custom/language-toggle.tsx +11 -10
  13. package/src/components/ui/custom/locale-preference.ts +32 -0
  14. package/src/components/ui/custom/settings/appearance-settings.tsx +5 -13
  15. package/src/components/ui/custom/settings-dialog-shell.tsx +40 -9
  16. package/src/components/ui/custom/structure.tsx +12 -0
  17. package/src/components/ui/custom/system-language-dropdown-item.tsx +3 -6
  18. package/src/components/ui/custom/workspace-access/adapters.test.ts +10 -1
  19. package/src/components/ui/custom/workspace-access/adapters.ts +36 -4
  20. package/src/components/ui/custom/workspace-access/member-filter-utils.test.ts +14 -0
  21. package/src/components/ui/custom/workspace-access/member-filter-utils.ts +8 -0
  22. package/src/components/ui/custom/workspace-access/types.ts +20 -0
  23. package/src/components/ui/custom/workspace-access/workspace-access-context.test.tsx +111 -0
  24. package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +15 -5
  25. package/src/components/ui/custom/workspace-access/workspace-access-invite-dialog.tsx +155 -48
  26. package/src/components/ui/custom/workspace-access/workspace-access-member-profile-dialog.tsx +92 -0
  27. package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +171 -76
  28. package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +11 -2
  29. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +11 -11
  30. package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +180 -22
  31. package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +29 -15
  32. package/src/components/ui/custom/workspace-access/workspace-access-permission-checklist.tsx +56 -10
  33. package/src/components/ui/custom/workspace-access/workspace-access-permission-preview.test.ts +33 -0
  34. package/src/components/ui/custom/workspace-access/workspace-access-permission-preview.tsx +24 -1
  35. package/src/components/ui/custom/workspace-access/workspace-access-responsive.test.ts +64 -0
  36. package/src/components/ui/custom/workspace-access/workspace-access-role-editor-dialog.tsx +28 -16
  37. package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +35 -11
  38. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +23 -11
  39. package/src/components/ui/custom/workspace-select-helpers.ts +5 -3
  40. package/src/components/ui/custom/workspace-select.tsx +8 -2
  41. package/src/components/ui/finance/shared/charts/monthly-total-chart-client.tsx +1 -1
  42. package/src/components/ui/finance/shared/charts/monthly-total-chart.tsx +1 -1
  43. package/src/components/ui/storefront/cart-summary.tsx +12 -2
  44. package/src/components/ui/storefront/storefront-surface.test.tsx +21 -0
  45. package/src/components/ui/storefront/storefront-surface.tsx +6 -0
  46. package/src/components/ui/text-editor/__tests__/collaboration-binding.test.tsx +161 -0
  47. package/src/components/ui/text-editor/editor.tsx +267 -235
  48. package/src/globals.css +166 -0
  49. package/src/hooks/__tests__/use-workspace-identity-mutation.test.tsx +181 -0
  50. package/src/hooks/use-workspace-identity-mutation.ts +136 -0
  51. package/vendor/xlsx/LICENSE +201 -0
  52. package/vendor/xlsx/types/index.d.ts +1065 -0
  53. package/vendor/xlsx/xlsx.js +28103 -0
  54. package/vendor/xlsx/xlsx.mjs +28228 -0
  55. package/vendor/xlsx-0.20.3.tgz +0 -0
@@ -2,6 +2,9 @@
2
2
 
3
3
  import {
4
4
  Crown,
5
+ Ellipsis,
6
+ Pencil,
7
+ Plus,
5
8
  ShieldUser,
6
9
  User as UserIcon,
7
10
  UserMinus,
@@ -12,18 +15,23 @@ import { Avatar, AvatarFallback, AvatarImage } from '@tuturuuu/ui/avatar';
12
15
  import { Badge } from '@tuturuuu/ui/badge';
13
16
  import { Button } from '@tuturuuu/ui/button';
14
17
  import {
15
- Select,
16
- SelectContent,
17
- SelectItem,
18
- SelectTrigger,
19
- SelectValue,
20
- } from '@tuturuuu/ui/select';
18
+ DropdownMenu,
19
+ DropdownMenuContent,
20
+ DropdownMenuItem,
21
+ DropdownMenuLabel,
22
+ DropdownMenuSeparator,
23
+ DropdownMenuSub,
24
+ DropdownMenuSubContent,
25
+ DropdownMenuSubTrigger,
26
+ DropdownMenuTrigger,
27
+ } from '@tuturuuu/ui/dropdown-menu';
21
28
  import { getInitials } from '@tuturuuu/utils/name-helper';
22
29
  import moment from 'moment';
23
30
  import { useLocale, useTranslations } from 'next-intl';
24
31
  import {
25
32
  getAvailableRolesForMember,
26
33
  getMemberDisplayName,
34
+ shouldShowProtectedMemberStatus,
27
35
  } from './member-filter-utils';
28
36
  import type { WorkspaceAccessLabels, WorkspaceAccessRole } from './types';
29
37
 
@@ -35,12 +43,15 @@ type GuestContext = InternalApiEnhancedWorkspaceMember & {
35
43
  };
36
44
 
37
45
  type Props = {
46
+ canEditProfiles: boolean;
38
47
  canManageMembers: boolean;
39
48
  canManageRoles: boolean;
49
+ defaultAdminEnabled: boolean;
40
50
  isMutating: boolean;
41
51
  labels: WorkspaceAccessLabels;
42
52
  member: InternalApiEnhancedWorkspaceMember;
43
53
  onAssignRole: (payload: { roleId: string; userId: string }) => void;
54
+ onEditMemberProfile: (member: InternalApiEnhancedWorkspaceMember) => void;
44
55
  onRemoveMember: (payload: {
45
56
  email?: null | string;
46
57
  userId?: null | string;
@@ -50,12 +61,15 @@ type Props = {
50
61
  };
51
62
 
52
63
  export function WorkspaceAccessMemberRow({
64
+ canEditProfiles,
53
65
  canManageMembers,
54
66
  canManageRoles,
67
+ defaultAdminEnabled,
55
68
  isMutating,
56
69
  labels,
57
70
  member,
58
71
  onAssignRole,
72
+ onEditMemberProfile,
59
73
  onRemoveMember,
60
74
  onRemoveRole,
61
75
  roles,
@@ -67,6 +81,21 @@ export function WorkspaceAccessMemberRow({
67
81
  const memberName = getMemberDisplayName(member, t('common.unknown'));
68
82
  const guest = member as GuestContext;
69
83
  const canRemoveRoles = canManageRoles && memberId && !member.pending;
84
+ const canEditProfile =
85
+ canEditProfiles &&
86
+ canManageMembers &&
87
+ !guest.direct_board_guest &&
88
+ Boolean(memberId || member.email);
89
+ const inheritsAdministrator =
90
+ defaultAdminEnabled &&
91
+ !member.pending &&
92
+ !guest.direct_board_guest &&
93
+ member.workspace_member_type !== 'GUEST';
94
+ const hasActions =
95
+ Boolean(canRemoveRoles) ||
96
+ canEditProfile ||
97
+ (canManageMembers && !member.is_creator) ||
98
+ shouldShowProtectedMemberStatus({ isCreator: member.is_creator });
70
99
 
71
100
  const accent = member.pending
72
101
  ? 'before:bg-dynamic-orange'
@@ -78,11 +107,11 @@ export function WorkspaceAccessMemberRow({
78
107
 
79
108
  return (
80
109
  <article
81
- className={`group relative overflow-hidden rounded-xl border bg-background pl-5 transition-all before:absolute before:inset-y-0 before:left-0 before:w-1 hover:border-foreground/20 hover:shadow-sm ${accent} ${member.pending ? 'border-dashed' : ''}`}
110
+ className={`group relative overflow-hidden rounded-xl border bg-background pl-4 transition-colors before:absolute before:inset-y-0 before:left-0 before:w-1 hover:border-foreground/20 sm:pl-5 ${accent} ${member.pending ? 'border-dashed' : ''}`}
82
111
  >
83
- <div className="p-4 pl-0">
84
- <div className="flex items-start gap-3">
85
- <Avatar className="h-11 w-11 border border-border">
112
+ <div className="p-3 pl-0 sm:p-4 sm:pl-0">
113
+ <div className="flex items-start gap-2.5 sm:gap-3">
114
+ <Avatar className="size-10 border border-border sm:size-11">
86
115
  <AvatarImage src={member.avatar_url ?? undefined} />
87
116
  <AvatarFallback className="font-semibold">
88
117
  {member.display_name ? (
@@ -95,7 +124,9 @@ export function WorkspaceAccessMemberRow({
95
124
 
96
125
  <div className="min-w-0 flex-1">
97
126
  <div className="flex flex-wrap items-center gap-1.5">
98
- <p className="truncate font-semibold text-base">{memberName}</p>
127
+ <p className="truncate font-semibold text-sm sm:text-base">
128
+ {memberName}
129
+ </p>
99
130
  {member.is_creator ? (
100
131
  <Badge className="h-5 gap-1 border-dynamic-yellow/50 bg-dynamic-yellow/10 px-1.5 text-dynamic-yellow text-xs">
101
132
  <Crown className="h-3 w-3" />
@@ -122,14 +153,134 @@ export function WorkspaceAccessMemberRow({
122
153
  </Badge>
123
154
  ) : null}
124
155
  </div>
125
- <p className="mt-0.5 truncate text-muted-foreground text-sm">
156
+ <p className="mt-0.5 truncate text-muted-foreground text-xs sm:text-sm">
126
157
  {member.email ||
127
158
  (member.handle ? `@${member.handle}` : t('common.unknown'))}
128
159
  </p>
129
160
  </div>
161
+
162
+ {hasActions ? (
163
+ <DropdownMenu>
164
+ <DropdownMenuTrigger asChild>
165
+ <Button
166
+ variant="ghost"
167
+ size="icon"
168
+ className="size-8 shrink-0"
169
+ disabled={isMutating}
170
+ aria-label={t('common.actions')}
171
+ >
172
+ <Ellipsis className="size-4" />
173
+ </Button>
174
+ </DropdownMenuTrigger>
175
+ <DropdownMenuContent align="end" className="w-56">
176
+ {canRemoveRoles ? (
177
+ <>
178
+ <DropdownMenuSub>
179
+ <DropdownMenuSubTrigger>
180
+ <Plus className="size-4" />
181
+ {labels.assignRolePlaceholder}
182
+ </DropdownMenuSubTrigger>
183
+ <DropdownMenuSubContent className="w-52">
184
+ {availableRoles.length === 0 ? (
185
+ <DropdownMenuItem disabled>
186
+ {labels.noAdditionalRoles}
187
+ </DropdownMenuItem>
188
+ ) : (
189
+ availableRoles.map((role) => (
190
+ <DropdownMenuItem
191
+ key={role.id}
192
+ onSelect={() =>
193
+ onAssignRole({
194
+ roleId: role.id,
195
+ userId: memberId,
196
+ })
197
+ }
198
+ >
199
+ <ShieldUser className="size-4" />
200
+ {role.name}
201
+ </DropdownMenuItem>
202
+ ))
203
+ )}
204
+ </DropdownMenuSubContent>
205
+ </DropdownMenuSub>
206
+
207
+ {member.roles.length > 0 ? (
208
+ <DropdownMenuSub>
209
+ <DropdownMenuSubTrigger>
210
+ <X className="size-4" />
211
+ {t('common.remove')}
212
+ </DropdownMenuSubTrigger>
213
+ <DropdownMenuSubContent className="w-52">
214
+ {member.roles.map((role) => (
215
+ <DropdownMenuItem
216
+ key={role.id}
217
+ variant="destructive"
218
+ onSelect={() =>
219
+ onRemoveRole({
220
+ roleId: role.id,
221
+ userId: memberId,
222
+ })
223
+ }
224
+ >
225
+ <X className="size-4" />
226
+ {role.name}
227
+ </DropdownMenuItem>
228
+ ))}
229
+ </DropdownMenuSubContent>
230
+ </DropdownMenuSub>
231
+ ) : null}
232
+ </>
233
+ ) : null}
234
+
235
+ {canEditProfile ? (
236
+ <>
237
+ {canRemoveRoles ? <DropdownMenuSeparator /> : null}
238
+ <DropdownMenuItem
239
+ onSelect={() => onEditMemberProfile(member)}
240
+ >
241
+ <Pencil className="size-4" />
242
+ {t('ws-members.profile_display_name')}
243
+ </DropdownMenuItem>
244
+ </>
245
+ ) : null}
246
+
247
+ {member.is_creator ? (
248
+ <>
249
+ {canRemoveRoles || canEditProfile ? (
250
+ <DropdownMenuSeparator />
251
+ ) : null}
252
+ <DropdownMenuLabel className="flex items-center gap-2 text-muted-foreground">
253
+ <ShieldUser className="size-4" />
254
+ {labels.protectedMemberLabel}
255
+ </DropdownMenuLabel>
256
+ </>
257
+ ) : null}
258
+
259
+ {canManageMembers && !member.is_creator ? (
260
+ <>
261
+ {canRemoveRoles || canEditProfile ? (
262
+ <DropdownMenuSeparator />
263
+ ) : null}
264
+ <DropdownMenuItem
265
+ variant="destructive"
266
+ onSelect={() =>
267
+ onRemoveMember({
268
+ email: member.email,
269
+ userId: member.id,
270
+ })
271
+ }
272
+ >
273
+ <UserMinus className="size-4" />
274
+ {labels.removeMemberAction}
275
+ </DropdownMenuItem>
276
+ </>
277
+ ) : null}
278
+ </DropdownMenuContent>
279
+ </DropdownMenu>
280
+ ) : null}
130
281
  </div>
131
282
 
132
- <div className="mt-3 flex flex-wrap gap-1.5">
283
+ <div className="mt-2.5 flex flex-wrap gap-1.5 sm:mt-3">
133
284
  {member.roles.length > 0 ? (
134
285
  member.roles.map((role) => (
135
286
  <Badge
@@ -137,20 +288,13 @@ export function WorkspaceAccessMemberRow({
137
288
  className="h-5 gap-1 border-dynamic-purple/40 bg-dynamic-purple/10 px-1.5 text-dynamic-purple text-xs"
138
289
  >
139
290
  <span>{role.name}</span>
140
- {canRemoveRoles ? (
141
- <button
142
- type="button"
143
- className="rounded-full p-0.5 transition-colors hover:bg-dynamic-purple/20"
144
- onClick={() =>
145
- onRemoveRole({ roleId: role.id, userId: memberId })
146
- }
147
- aria-label={t('common.delete')}
148
- >
149
- <X className="h-3 w-3" />
150
- </button>
151
- ) : null}
152
291
  </Badge>
153
292
  ))
293
+ ) : inheritsAdministrator ? (
294
+ <Badge className="h-5 gap-1 border-dynamic-green/40 bg-dynamic-green/10 px-1.5 text-dynamic-green text-xs">
295
+ <ShieldUser className="size-3" />
296
+ {t('ws-roles.admin')}
297
+ </Badge>
154
298
  ) : (
155
299
  <Badge variant="outline" className="h-5 px-1.5 text-xs">
156
300
  {labels.noRolesLabel}
@@ -159,7 +303,7 @@ export function WorkspaceAccessMemberRow({
159
303
  </div>
160
304
 
161
305
  {guest.direct_board_guest ? (
162
- <div className="mt-3 rounded-lg border border-dynamic-blue/20 bg-dynamic-blue/5 p-3 text-sm">
306
+ <div className="mt-3 rounded-lg border border-dynamic-blue/20 bg-dynamic-blue/5 p-2.5 text-sm sm:p-3">
163
307
  <div className="font-medium text-dynamic-blue">
164
308
  {t('ws-members.direct_board_guest_scope_title')}
165
309
  </div>
@@ -185,7 +329,7 @@ export function WorkspaceAccessMemberRow({
185
329
  </div>
186
330
  ) : null}
187
331
 
188
- <div className="mt-3 flex flex-col gap-3 border-border border-t pt-3 text-sm md:flex-row md:items-center md:justify-between">
332
+ <div className="mt-3 border-border border-t pt-3 text-sm">
189
333
  {member.created_at ? (
190
334
  <div className="text-muted-foreground text-xs">
191
335
  <span>
@@ -202,55 +346,6 @@ export function WorkspaceAccessMemberRow({
202
346
  ) : (
203
347
  <span />
204
348
  )}
205
-
206
- <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
207
- {canRemoveRoles ? (
208
- <Select
209
- onValueChange={(roleId) =>
210
- onAssignRole({ roleId, userId: memberId })
211
- }
212
- >
213
- <SelectTrigger className="h-9 min-w-[180px]">
214
- <SelectValue placeholder={labels.assignRolePlaceholder} />
215
- </SelectTrigger>
216
- <SelectContent>
217
- {availableRoles.length === 0 ? (
218
- <SelectItem value="__no_roles__" disabled>
219
- {labels.noAdditionalRoles}
220
- </SelectItem>
221
- ) : (
222
- availableRoles.map((role) => (
223
- <SelectItem key={role.id} value={role.id}>
224
- {role.name}
225
- </SelectItem>
226
- ))
227
- )}
228
- </SelectContent>
229
- </Select>
230
- ) : null}
231
-
232
- {canManageMembers && !member.is_creator ? (
233
- <Button
234
- variant="outline"
235
- size="sm"
236
- disabled={isMutating}
237
- onClick={() =>
238
- onRemoveMember({
239
- email: member.email,
240
- userId: member.pending ? null : member.id,
241
- })
242
- }
243
- >
244
- <UserMinus className="mr-2 h-4 w-4" />
245
- {labels.removeMemberAction}
246
- </Button>
247
- ) : (
248
- <div className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-muted-foreground text-xs">
249
- <ShieldUser className="h-3.5 w-3.5" />
250
- {labels.protectedMemberLabel}
251
- </div>
252
- )}
253
- </div>
254
349
  </div>
255
350
  </div>
256
351
  </article>
@@ -8,13 +8,16 @@ import type { WorkspaceAccessLabels, WorkspaceAccessRole } from './types';
8
8
  import { WorkspaceAccessMemberRow } from './workspace-access-member-row';
9
9
 
10
10
  type Props = {
11
+ canEditProfiles: boolean;
11
12
  canManageMembers: boolean;
12
13
  canManageRoles: boolean;
14
+ defaultAdminEnabled: boolean;
13
15
  isLoading: boolean;
14
16
  isMutating: boolean;
15
17
  labels: WorkspaceAccessLabels;
16
18
  members: InternalApiEnhancedWorkspaceMember[];
17
19
  onAssignRole: (payload: { roleId: string; userId: string }) => void;
20
+ onEditMemberProfile: (member: InternalApiEnhancedWorkspaceMember) => void;
18
21
  onRemoveMember: (payload: {
19
22
  email?: null | string;
20
23
  userId?: null | string;
@@ -26,13 +29,16 @@ type Props = {
26
29
  };
27
30
 
28
31
  export function WorkspaceAccessMembers({
32
+ canEditProfiles,
29
33
  canManageMembers,
30
34
  canManageRoles,
35
+ defaultAdminEnabled,
31
36
  isLoading,
32
37
  isMutating,
33
38
  labels,
34
39
  members,
35
40
  onAssignRole,
41
+ onEditMemberProfile,
36
42
  onRemoveMember,
37
43
  onRemoveRole,
38
44
  roles,
@@ -43,7 +49,7 @@ export function WorkspaceAccessMembers({
43
49
 
44
50
  if (isLoading) {
45
51
  return (
46
- <div className="grid gap-4 lg:grid-cols-2">
52
+ <div className="grid gap-3 lg:grid-cols-2">
47
53
  <Skeleton className="h-48 rounded-xl" />
48
54
  <Skeleton className="h-48 rounded-xl" />
49
55
  <Skeleton className="h-48 rounded-xl" />
@@ -70,16 +76,19 @@ export function WorkspaceAccessMembers({
70
76
  }
71
77
 
72
78
  return (
73
- <div className="grid gap-4 lg:grid-cols-2">
79
+ <div className="grid gap-3 lg:grid-cols-2">
74
80
  {members.map((member) => (
75
81
  <WorkspaceAccessMemberRow
76
82
  key={`${member.id ?? member.email ?? member.handle}`}
83
+ canEditProfiles={canEditProfiles}
77
84
  canManageMembers={canManageMembers}
78
85
  canManageRoles={canManageRoles}
86
+ defaultAdminEnabled={defaultAdminEnabled}
79
87
  isMutating={isMutating}
80
88
  labels={labels}
81
89
  member={member}
82
90
  onAssignRole={onAssignRole}
91
+ onEditMemberProfile={onEditMemberProfile}
83
92
  onRemoveMember={onRemoveMember}
84
93
  onRemoveRole={onRemoveRole}
85
94
  roles={roles}
@@ -51,25 +51,25 @@ export function WorkspaceAccessPageHeader({
51
51
  ];
52
52
 
53
53
  return (
54
- <section className="rounded-xl border border-border bg-background">
55
- <div className="flex flex-col gap-6 p-6 lg:flex-row lg:items-start lg:justify-between">
56
- <div className="flex min-w-0 gap-4">
57
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border border-dynamic-blue/30 bg-dynamic-blue/10 text-dynamic-blue">
54
+ <section className="overflow-hidden rounded-xl border border-border bg-background">
55
+ <div className="flex flex-col gap-4 p-4 sm:p-6 lg:flex-row lg:items-start lg:justify-between">
56
+ <div className="flex min-w-0 gap-3 sm:gap-4">
57
+ <div className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-dynamic-blue/30 bg-dynamic-blue/10 text-dynamic-blue sm:size-11">
58
58
  <Users className="h-5 w-5" />
59
59
  </div>
60
60
  <div className="min-w-0">
61
61
  <div className="flex items-center gap-2">
62
- <h1 className="truncate font-bold text-2xl tracking-tight">
62
+ <h1 className="truncate font-bold text-xl tracking-tight sm:text-2xl">
63
63
  {mode === 'cms'
64
64
  ? t('external-projects.settings.members_title')
65
65
  : t('workspace-settings-layout.members')}
66
66
  </h1>
67
- <span className="inline-flex items-center gap-1 rounded-full border border-dynamic-purple/30 bg-dynamic-purple/10 px-2 py-0.5 font-medium text-dynamic-purple text-xs">
67
+ <span className="hidden items-center gap-1 rounded-full border border-dynamic-purple/30 bg-dynamic-purple/10 px-2 py-0.5 font-medium text-dynamic-purple text-xs sm:inline-flex">
68
68
  <ShieldCheck className="h-3 w-3" />
69
69
  {t('ws-roles.plural')}
70
70
  </span>
71
71
  </div>
72
- <p className="mt-1.5 max-w-2xl text-muted-foreground text-sm leading-6">
72
+ <p className="mt-1 max-w-2xl text-muted-foreground text-sm leading-5 sm:mt-1.5 sm:leading-6">
73
73
  {mode === 'cms'
74
74
  ? t('external-projects.settings.members_description')
75
75
  : t('ws-members.description')}
@@ -85,21 +85,21 @@ export function WorkspaceAccessPageHeader({
85
85
  </div>
86
86
  </div>
87
87
 
88
- <div className="grid shrink-0 grid-cols-3 divide-x divide-border overflow-hidden rounded-lg border border-border bg-foreground/[0.02]">
88
+ <div className="grid w-full shrink-0 grid-cols-3 divide-x divide-border overflow-hidden rounded-lg border border-border bg-foreground/[0.02] lg:w-auto">
89
89
  {stats.map((stat) => (
90
90
  <div
91
91
  key={stat.label}
92
- className="flex flex-col gap-1 px-4 py-3 text-center"
92
+ className="flex min-w-0 flex-col gap-0.5 px-2 py-2.5 text-center sm:gap-1 sm:px-4 sm:py-3"
93
93
  >
94
94
  <span
95
95
  className={`inline-flex items-center justify-center gap-1.5 ${stat.accent}`}
96
96
  >
97
97
  {stat.icon}
98
- <span className="font-bold text-foreground text-xl tabular-nums">
98
+ <span className="font-bold text-foreground text-lg tabular-nums sm:text-xl">
99
99
  {stat.value}
100
100
  </span>
101
101
  </span>
102
- <span className="text-muted-foreground text-xs">
102
+ <span className="truncate text-[11px] text-muted-foreground sm:text-xs">
103
103
  {stat.label}
104
104
  </span>
105
105
  </div>