@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
@@ -1,16 +1,24 @@
1
1
  'use client';
2
2
 
3
- import { MailPlus, UserPlus } from '@tuturuuu/icons';
4
- import type { WorkspaceDefaultPermissionMemberType } from '@tuturuuu/types';
3
+ import {
4
+ CreditCard,
5
+ MailPlus,
6
+ ShieldCheck,
7
+ TriangleAlert,
8
+ UserPlus,
9
+ } from '@tuturuuu/icons';
5
10
  import { Badge } from '@tuturuuu/ui/badge';
6
11
  import { Button } from '@tuturuuu/ui/button';
12
+ import { Checkbox } from '@tuturuuu/ui/checkbox';
7
13
  import {
8
14
  Dialog,
9
15
  DialogContent,
10
16
  DialogDescription,
17
+ DialogFooter,
11
18
  DialogHeader,
12
19
  DialogTitle,
13
20
  } from '@tuturuuu/ui/dialog';
21
+ import { Label } from '@tuturuuu/ui/label';
14
22
  import {
15
23
  Select,
16
24
  SelectContent,
@@ -23,22 +31,32 @@ import { useTranslations } from 'next-intl';
23
31
  import { parseInviteEmails } from './member-filter-utils';
24
32
 
25
33
  type Props = {
34
+ accessPreset: 'guest' | 'member' | 'pos_operator';
35
+ canManageRoles: boolean;
36
+ confirmDefaultAdminMigration: boolean;
37
+ defaultAdminEnabled: boolean;
26
38
  emails: string;
27
39
  isSubmitting: boolean;
28
- memberType: WorkspaceDefaultPermissionMemberType;
40
+ joinedMemberCount: number;
41
+ onAccessPresetChange: (value: 'guest' | 'member' | 'pos_operator') => void;
42
+ onConfirmDefaultAdminMigrationChange: (value: boolean) => void;
29
43
  onEmailsChange: (value: string) => void;
30
- onMemberTypeChange: (value: WorkspaceDefaultPermissionMemberType) => void;
31
44
  onOpenChange: (open: boolean) => void;
32
45
  onSubmit: () => void;
33
46
  open: boolean;
34
47
  };
35
48
 
36
49
  export function WorkspaceAccessInviteDialog({
50
+ accessPreset,
51
+ canManageRoles,
52
+ confirmDefaultAdminMigration,
53
+ defaultAdminEnabled,
37
54
  emails,
38
55
  isSubmitting,
39
- memberType,
56
+ joinedMemberCount,
57
+ onAccessPresetChange,
58
+ onConfirmDefaultAdminMigrationChange,
40
59
  onEmailsChange,
41
- onMemberTypeChange,
42
60
  onOpenChange,
43
61
  onSubmit,
44
62
  open,
@@ -48,54 +66,143 @@ export function WorkspaceAccessInviteDialog({
48
66
 
49
67
  return (
50
68
  <Dialog open={open} onOpenChange={onOpenChange}>
51
- <DialogContent className="sm:max-w-xl">
52
- <DialogHeader className="space-y-3">
53
- <div className="flex h-10 w-10 items-center justify-center rounded-lg border bg-muted/50">
54
- <UserPlus className="h-4 w-4" />
69
+ <DialogContent className="flex max-h-[calc(100dvh-1rem)] w-[calc(100%-1rem)] max-w-none flex-col gap-0 overflow-hidden rounded-b-none p-0 max-sm:top-auto max-sm:bottom-0 max-sm:left-0 max-sm:translate-x-0 max-sm:translate-y-0 sm:max-h-[min(88dvh,52rem)] sm:max-w-xl sm:rounded-lg">
70
+ <DialogHeader className="shrink-0 gap-0 border-b p-4 pr-12 text-left sm:p-6 sm:pr-12">
71
+ <div className="flex items-start gap-3">
72
+ <div className="flex size-10 shrink-0 items-center justify-center rounded-xl border border-dynamic-blue/25 bg-dynamic-blue/10 text-dynamic-blue">
73
+ <UserPlus className="size-4" />
74
+ </div>
75
+ <div className="min-w-0 space-y-1">
76
+ <DialogTitle>{t('ws-members.invite_member')}</DialogTitle>
77
+ <DialogDescription className="leading-5">
78
+ {t('ws-members.invite_dialog_description')}
79
+ </DialogDescription>
80
+ </div>
55
81
  </div>
56
- <DialogTitle>{t('ws-members.invite_member')}</DialogTitle>
57
- <DialogDescription>
58
- {t('ws-members.invite_dialog_description')}
59
- </DialogDescription>
60
82
  </DialogHeader>
61
83
 
62
- <div className="space-y-4">
63
- <Textarea
64
- rows={7}
65
- value={emails}
66
- onChange={(event) => onEmailsChange(event.target.value)}
67
- placeholder={'one@example.com\ntwo@example.com'}
68
- />
69
-
70
- <Select
71
- value={memberType}
72
- onValueChange={(value) =>
73
- onMemberTypeChange(value as WorkspaceDefaultPermissionMemberType)
74
- }
75
- >
76
- <SelectTrigger className="w-full">
77
- <SelectValue />
78
- </SelectTrigger>
79
- <SelectContent>
80
- <SelectItem value="MEMBER">
81
- {t('ws-members.invite_membership_member')}
82
- </SelectItem>
83
- <SelectItem value="GUEST">
84
- {t('ws-members.invite_membership_guest')}
85
- </SelectItem>
86
- </SelectContent>
87
- </Select>
84
+ <div className="min-h-0 flex-1 space-y-4 overflow-y-auto overscroll-contain p-4 sm:p-6">
85
+ <div className="grid gap-2">
86
+ <Label htmlFor="workspace-access-invite-emails">
87
+ {t('ws-members.invite_member')}
88
+ </Label>
89
+ <Textarea
90
+ id="workspace-access-invite-emails"
91
+ rows={5}
92
+ value={emails}
93
+ onChange={(event) => onEmailsChange(event.target.value)}
94
+ placeholder={'one@example.com\ntwo@example.com'}
95
+ className="min-h-28 resize-y"
96
+ />
97
+ </div>
88
98
 
89
- <div className="flex flex-wrap items-center justify-between gap-3">
90
- <Badge variant="secondary" className="rounded-full">
91
- {count} {t('ws-members.pending_invitations').toLowerCase()}
92
- </Badge>
93
- <Button disabled={isSubmitting || count === 0} onClick={onSubmit}>
94
- <MailPlus className="mr-2 h-4 w-4" />
95
- {t('ws-members.invite_submit')}
96
- </Button>
99
+ <div className="grid gap-2">
100
+ <Label htmlFor="workspace-access-invite-membership">
101
+ {t('ws-members.invite_membership_label')}
102
+ </Label>
103
+ <Select
104
+ value={accessPreset}
105
+ onValueChange={(value) =>
106
+ onAccessPresetChange(
107
+ value as 'guest' | 'member' | 'pos_operator'
108
+ )
109
+ }
110
+ >
111
+ <SelectTrigger
112
+ id="workspace-access-invite-membership"
113
+ className="w-full"
114
+ >
115
+ <SelectValue />
116
+ </SelectTrigger>
117
+ <SelectContent>
118
+ <SelectItem value="member">
119
+ {t('ws-members.invite_membership_member')}
120
+ </SelectItem>
121
+ <SelectItem value="guest">
122
+ {t('ws-members.invite_membership_guest')}
123
+ </SelectItem>
124
+ <SelectItem value="pos_operator" disabled={!canManageRoles}>
125
+ {t('ws-members.invite_membership_pos_operator')}
126
+ </SelectItem>
127
+ </SelectContent>
128
+ </Select>
97
129
  </div>
130
+
131
+ {accessPreset === 'pos_operator' ? (
132
+ <div className="space-y-3 rounded-xl border border-dynamic-blue/25 bg-dynamic-blue/5 p-3.5 sm:p-4">
133
+ <div className="flex items-start gap-3">
134
+ <div className="mt-0.5 rounded-lg border border-dynamic-blue/20 bg-background p-2 text-dynamic-blue">
135
+ <CreditCard className="h-4 w-4" />
136
+ </div>
137
+ <div className="min-w-0 space-y-1">
138
+ <p className="font-medium text-sm">
139
+ {t('ws-members.pos_operator_title')}
140
+ </p>
141
+ <p className="text-muted-foreground text-sm leading-5">
142
+ {t('ws-members.pos_operator_description')}
143
+ </p>
144
+ </div>
145
+ </div>
146
+
147
+ <div className="grid gap-2 sm:grid-cols-2">
148
+ {defaultAdminEnabled ? (
149
+ <div className="flex items-center gap-2 rounded-lg border bg-background/70 p-3 text-sm">
150
+ <ShieldCheck className="h-4 w-4 text-dynamic-green" />
151
+ <span>
152
+ {joinedMemberCount}{' '}
153
+ {t('ws-members.pos_operator_existing_members')}
154
+ </span>
155
+ </div>
156
+ ) : null}
157
+ <div className="flex items-center gap-2 rounded-lg border bg-background/70 p-3 text-sm">
158
+ <CreditCard className="h-4 w-4 text-dynamic-blue" />
159
+ <span>{t('ws-members.pos_operator_only_permission')}</span>
160
+ </div>
161
+ </div>
162
+
163
+ {defaultAdminEnabled ? (
164
+ <div className="flex items-start gap-2 rounded-lg border border-dynamic-orange/25 bg-dynamic-orange/5 p-3 text-sm">
165
+ <TriangleAlert className="mt-0.5 h-4 w-4 shrink-0 text-dynamic-orange" />
166
+ <p className="text-muted-foreground leading-5">
167
+ {t('ws-members.pos_operator_admin_migration_note')}
168
+ </p>
169
+ </div>
170
+ ) : null}
171
+
172
+ <label className="flex cursor-pointer items-start gap-3 rounded-lg border bg-background p-3">
173
+ <Checkbox
174
+ checked={confirmDefaultAdminMigration}
175
+ className="mt-0.5"
176
+ onCheckedChange={(checked) =>
177
+ onConfirmDefaultAdminMigrationChange(checked === true)
178
+ }
179
+ />
180
+ <span className="text-sm leading-5">
181
+ {t('ws-members.pos_operator_confirmation')}
182
+ </span>
183
+ </label>
184
+ </div>
185
+ ) : null}
98
186
  </div>
187
+
188
+ <DialogFooter className="grid shrink-0 grid-cols-[auto_1fr] items-center gap-3 border-t bg-muted/20 p-3 sm:flex sm:p-4">
189
+ <Badge variant="secondary" className="rounded-full">
190
+ {count} {t('ws-members.pending_invitations').toLowerCase()}
191
+ </Badge>
192
+ <Button
193
+ className="min-w-0"
194
+ disabled={
195
+ isSubmitting ||
196
+ count === 0 ||
197
+ (accessPreset === 'pos_operator' &&
198
+ (!canManageRoles || !confirmDefaultAdminMigration))
199
+ }
200
+ onClick={onSubmit}
201
+ >
202
+ <MailPlus className="mr-2 size-4" />
203
+ <span className="truncate">{t('ws-members.invite_submit')}</span>
204
+ </Button>
205
+ </DialogFooter>
99
206
  </DialogContent>
100
207
  </Dialog>
101
208
  );
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ import { Loader2 } from '@tuturuuu/icons';
4
+ import type { InternalApiEnhancedWorkspaceMember } from '@tuturuuu/types';
5
+ import { Button } from '@tuturuuu/ui/button';
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from '@tuturuuu/ui/dialog';
14
+ import { Input } from '@tuturuuu/ui/input';
15
+ import { Label } from '@tuturuuu/ui/label';
16
+ import { MAX_NAME_LENGTH } from '@tuturuuu/utils/constants';
17
+ import { useTranslations } from 'next-intl';
18
+ import { type FormEvent, useState } from 'react';
19
+
20
+ type Props = {
21
+ isSubmitting: boolean;
22
+ member: InternalApiEnhancedWorkspaceMember;
23
+ onOpenChange: (open: boolean) => void;
24
+ onSubmit: (displayName: string | null) => void;
25
+ open: boolean;
26
+ };
27
+
28
+ export function WorkspaceAccessMemberProfileDialog({
29
+ isSubmitting,
30
+ member,
31
+ onOpenChange,
32
+ onSubmit,
33
+ open,
34
+ }: Props) {
35
+ const t = useTranslations();
36
+ const [displayName, setDisplayName] = useState(
37
+ member.workspace_profile_display_name ?? member.display_name ?? ''
38
+ );
39
+
40
+ const submit = (event: FormEvent<HTMLFormElement>) => {
41
+ event.preventDefault();
42
+ onSubmit(displayName.trim() || null);
43
+ };
44
+
45
+ return (
46
+ <Dialog open={open} onOpenChange={onOpenChange}>
47
+ <DialogContent className="sm:max-w-md">
48
+ <DialogHeader>
49
+ <DialogTitle>{t('ws-members.profile_display_name')}</DialogTitle>
50
+ <DialogDescription>
51
+ {t('ws-members.profile_display_name_description')}
52
+ </DialogDescription>
53
+ </DialogHeader>
54
+
55
+ <form className="space-y-4" onSubmit={submit}>
56
+ <div className="space-y-2">
57
+ <Label htmlFor="workspace-member-profile-display-name">
58
+ {t('ws-members.profile_display_name')}
59
+ </Label>
60
+ <Input
61
+ id="workspace-member-profile-display-name"
62
+ autoComplete="off"
63
+ autoFocus
64
+ disabled={isSubmitting}
65
+ maxLength={MAX_NAME_LENGTH}
66
+ onChange={(event) => setDisplayName(event.target.value)}
67
+ placeholder={t('ws-members.profile_display_name_placeholder')}
68
+ value={displayName}
69
+ />
70
+ </div>
71
+
72
+ <DialogFooter>
73
+ <Button
74
+ disabled={isSubmitting}
75
+ onClick={() => onOpenChange(false)}
76
+ type="button"
77
+ variant="outline"
78
+ >
79
+ {t('common.cancel')}
80
+ </Button>
81
+ <Button disabled={isSubmitting} type="submit">
82
+ {isSubmitting ? (
83
+ <Loader2 className="size-4 animate-spin" />
84
+ ) : null}
85
+ {t('ws-members.save_profile_display_name')}
86
+ </Button>
87
+ </DialogFooter>
88
+ </form>
89
+ </DialogContent>
90
+ </Dialog>
91
+ );
92
+ }