@greatapps/common 1.1.230 → 1.1.232

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 (237) hide show
  1. package/dist/components/account/ConfigurationsMyAccountModal.mjs +1 -1
  2. package/dist/components/account/ConfigurationsMyAccountModal.mjs.map +1 -1
  3. package/dist/components/account/ConfirmDeleteAccountModal.mjs.map +1 -1
  4. package/dist/components/account/ConfirmGlobalPreferencesModal.mjs.map +1 -1
  5. package/dist/components/account/DeleteAccountModal.mjs.map +1 -1
  6. package/dist/components/account/DisableTwoFactorAuthModal.mjs.map +1 -1
  7. package/dist/components/account/IsntPossibleDeleteAccountModal.mjs.map +1 -1
  8. package/dist/components/account/TwoFactorAuthModal.mjs.map +1 -1
  9. package/dist/components/account/hooks/useChangePhoneForm.mjs.map +1 -1
  10. package/dist/components/account/hooks/useTwoFactorAuthForm.mjs.map +1 -1
  11. package/dist/components/account/sections/ChangeEmailModal.mjs.map +1 -1
  12. package/dist/components/account/sections/ChangePasswordSection.mjs.map +1 -1
  13. package/dist/components/account/sections/ChangePhoneModal.mjs.map +1 -1
  14. package/dist/components/account/sections/MyProfileSection.mjs.map +1 -1
  15. package/dist/components/account/sections/PreferencesSection.mjs.map +1 -1
  16. package/dist/components/account/sections/SecuritySection.mjs.map +1 -1
  17. package/dist/components/embeds/CrispEmbed.mjs.map +1 -1
  18. package/dist/components/embeds/FrillEmbed.mjs.map +1 -1
  19. package/dist/components/layouts/AppMobileNavBar.mjs.map +1 -1
  20. package/dist/components/layouts/AppNavBar.mjs.map +1 -1
  21. package/dist/components/layouts/MdSideBarNavigation.mjs.map +1 -1
  22. package/dist/components/layouts/NavBar.mjs +1 -1
  23. package/dist/components/layouts/NavBar.mjs.map +1 -1
  24. package/dist/components/layouts/NotificationsPopover.mjs.map +1 -1
  25. package/dist/components/layouts/ProfilePopover.mjs.map +1 -1
  26. package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
  27. package/dist/components/navigation/CancelledSubscriptionBanner.mjs.map +1 -1
  28. package/dist/components/navigation/NavItemsContext.mjs.map +1 -1
  29. package/dist/components/navigation/ProjectSelectorPopover.mjs +4 -1
  30. package/dist/components/navigation/ProjectSelectorPopover.mjs.map +1 -1
  31. package/dist/components/navigation/shells/TabsMobileShell.mjs.map +1 -1
  32. package/dist/components/navigation/subcomponents/ProjectSelector.mjs +5 -1
  33. package/dist/components/navigation/subcomponents/ProjectSelector.mjs.map +1 -1
  34. package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
  35. package/dist/components/ui/buttons/Button.mjs.map +1 -1
  36. package/dist/components/ui/buttons/CancelButton.mjs.map +1 -1
  37. package/dist/components/ui/buttons/CopyButton.mjs.map +1 -1
  38. package/dist/components/ui/data-display/Accordion.mjs.map +1 -1
  39. package/dist/components/ui/data-display/Badge.mjs.map +1 -1
  40. package/dist/components/ui/data-display/Card.mjs.map +1 -1
  41. package/dist/components/ui/data-display/Pagination.mjs.map +1 -1
  42. package/dist/components/ui/data-display/ScrollArea.mjs.map +1 -1
  43. package/dist/components/ui/data-display/Separator.mjs.map +1 -1
  44. package/dist/components/ui/data-display/Table.mjs.map +1 -1
  45. package/dist/components/ui/data-display/Tabs.mjs.map +1 -1
  46. package/dist/components/ui/feedback/CircularProgress.mjs.map +1 -1
  47. package/dist/components/ui/feedback/DefaultCircularProgress.mjs.map +1 -1
  48. package/dist/components/ui/feedback/LoadingOverlay.mjs.map +1 -1
  49. package/dist/components/ui/feedback/Progress.mjs.map +1 -1
  50. package/dist/components/ui/feedback/Toast.mjs.map +1 -1
  51. package/dist/components/ui/form/Calendar.mjs.map +1 -1
  52. package/dist/components/ui/form/Checkbox.mjs.map +1 -1
  53. package/dist/components/ui/form/ComboboxField.mjs.map +1 -1
  54. package/dist/components/ui/form/DatePicker.mjs.map +1 -1
  55. package/dist/components/ui/form/DateRangePicker.mjs.map +1 -1
  56. package/dist/components/ui/form/FormField.mjs.map +1 -1
  57. package/dist/components/ui/form/Input.mjs.map +1 -1
  58. package/dist/components/ui/form/InputOtp.mjs.map +1 -1
  59. package/dist/components/ui/form/PhoneInput.mjs.map +1 -1
  60. package/dist/components/ui/form/RadioGroup.mjs.map +1 -1
  61. package/dist/components/ui/form/Select.mjs +1 -1
  62. package/dist/components/ui/form/Select.mjs.map +1 -1
  63. package/dist/components/ui/form/Switch.mjs.map +1 -1
  64. package/dist/components/ui/form/Textarea.mjs.map +1 -1
  65. package/dist/components/ui/overlay/Command.mjs.map +1 -1
  66. package/dist/components/ui/overlay/Dialog.mjs.map +1 -1
  67. package/dist/components/ui/overlay/Popover.mjs.map +1 -1
  68. package/dist/components/ui/overlay/Sheet.mjs.map +1 -1
  69. package/dist/components/ui/overlay/Tooltip.mjs.map +1 -1
  70. package/dist/hooks/copy-to-clipboard.hook.mjs.map +1 -1
  71. package/dist/index.mjs +7 -0
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/middleware.mjs.map +1 -1
  74. package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
  75. package/dist/modules/accounts/actions/find-current-account.action.mjs.map +1 -1
  76. package/dist/modules/accounts/actions/list-currencies.action.mjs.map +1 -1
  77. package/dist/modules/accounts/actions/list-timezones.action.mjs.map +1 -1
  78. package/dist/modules/accounts/hooks/current-account.hook.mjs.map +1 -1
  79. package/dist/modules/accounts/hooks/useAccountManagement.mjs.map +1 -1
  80. package/dist/modules/accounts/hooks/useCurrencies.mjs.map +1 -1
  81. package/dist/modules/accounts/hooks/useTimezones.mjs.map +1 -1
  82. package/dist/modules/accounts/services/account.service.mjs.map +1 -1
  83. package/dist/modules/auth/hooks/login.hook.mjs.map +1 -1
  84. package/dist/modules/auth/hooks/logout.hook.mjs.map +1 -1
  85. package/dist/modules/auth/hooks/register.hook.mjs.map +1 -1
  86. package/dist/modules/ia-credits/actions/list-ia-credits.action.mjs.map +1 -1
  87. package/dist/modules/projects/actions/add-project-user.action.mjs.map +1 -1
  88. package/dist/modules/projects/actions/list-all-account-users.action.mjs.map +1 -1
  89. package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -1
  90. package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -1
  91. package/dist/modules/projects/actions/remove-project-user.action.mjs.map +1 -1
  92. package/dist/modules/projects/hooks/add-project-user.hook.mjs.map +1 -1
  93. package/dist/modules/projects/hooks/list-all-account-users.hook.mjs.map +1 -1
  94. package/dist/modules/projects/hooks/list-available-users.hook.mjs.map +1 -1
  95. package/dist/modules/projects/hooks/list-project-users.hook.mjs.map +1 -1
  96. package/dist/modules/projects/hooks/remove-project-user.hook.mjs.map +1 -1
  97. package/dist/modules/subscriptions/actions/find-plan-by-id.action.mjs +10 -0
  98. package/dist/modules/subscriptions/actions/find-plan-by-id.action.mjs.map +1 -0
  99. package/dist/modules/subscriptions/actions/list-subscriptions.action.mjs.map +1 -1
  100. package/dist/modules/subscriptions/hooks/find-active-subscription.hook.mjs.map +1 -1
  101. package/dist/modules/subscriptions/hooks/list-subscriptions.hook.mjs.map +1 -1
  102. package/dist/modules/subscriptions/hooks/use-has-plan-addon.hook.mjs +15 -0
  103. package/dist/modules/subscriptions/hooks/use-has-plan-addon.hook.mjs.map +1 -0
  104. package/dist/modules/subscriptions/hooks/use-plan-by-id.hook.mjs +15 -0
  105. package/dist/modules/subscriptions/hooks/use-plan-by-id.hook.mjs.map +1 -0
  106. package/dist/modules/subscriptions/services/plans.service.mjs +34 -0
  107. package/dist/modules/subscriptions/services/plans.service.mjs.map +1 -0
  108. package/dist/modules/subscriptions/types/plan.type.mjs +20 -0
  109. package/dist/modules/subscriptions/types/plan.type.mjs.map +1 -0
  110. package/dist/modules/users/action/find-user-by-id.action.mjs.map +1 -1
  111. package/dist/modules/users/action/list-messages.action.mjs.map +1 -1
  112. package/dist/modules/users/action/mark-all-messages-as-read.action.mjs.map +1 -1
  113. package/dist/modules/users/action/mark-message-as-read.action.mjs.map +1 -1
  114. package/dist/modules/users/hooks/messages.hook.mjs.map +1 -1
  115. package/dist/modules/whitelabel/actions/find-whitelabel-styles.action.mjs.map +1 -1
  116. package/dist/modules/whitelabel/actions/find-whitelabel.action.mjs.map +1 -1
  117. package/dist/modules/whitelabel/services/whitelabel-styles.service.mjs.map +1 -1
  118. package/dist/providers/whitelabel.provider.mjs.map +1 -1
  119. package/dist/server.mjs +4 -0
  120. package/dist/server.mjs.map +1 -1
  121. package/dist/utils/countries.mjs.map +1 -1
  122. package/dist/utils/safeServerAction.mjs.map +1 -1
  123. package/dist/utils/withAction.mjs.map +1 -1
  124. package/package.json +89 -89
  125. package/src/components/account/ConfigurationsMyAccountModal.tsx +150 -150
  126. package/src/components/account/ConfirmDeleteAccountModal.tsx +55 -55
  127. package/src/components/account/ConfirmGlobalPreferencesModal.tsx +66 -66
  128. package/src/components/account/DeleteAccountModal.tsx +130 -130
  129. package/src/components/account/DisableTwoFactorAuthModal.tsx +86 -86
  130. package/src/components/account/IsntPossibleDeleteAccountModal.tsx +49 -49
  131. package/src/components/account/TwoFactorAuthModal.tsx +223 -223
  132. package/src/components/account/hooks/useChangePhoneForm.tsx +81 -81
  133. package/src/components/account/hooks/useTwoFactorAuthForm.tsx +101 -101
  134. package/src/components/account/sections/ChangeEmailModal.tsx +225 -225
  135. package/src/components/account/sections/ChangePasswordSection.tsx +64 -64
  136. package/src/components/account/sections/ChangePhoneModal.tsx +231 -231
  137. package/src/components/account/sections/MyProfileSection.tsx +327 -327
  138. package/src/components/account/sections/PreferencesSection.tsx +273 -273
  139. package/src/components/account/sections/SecuritySection.tsx +160 -160
  140. package/src/components/embeds/CrispEmbed.tsx +101 -101
  141. package/src/components/embeds/FrillEmbed.tsx +119 -119
  142. package/src/components/layouts/AppMobileNavBar.tsx +110 -110
  143. package/src/components/layouts/AppNavBar.tsx +57 -57
  144. package/src/components/layouts/MdSideBarNavigation.tsx +56 -56
  145. package/src/components/layouts/NavBar.tsx +120 -120
  146. package/src/components/layouts/NotificationsPopover.tsx +115 -115
  147. package/src/components/layouts/ProfilePopover.tsx +167 -167
  148. package/src/components/layouts/UsersSelectorPopover.tsx +238 -238
  149. package/src/components/navigation/CancelledSubscriptionBanner.tsx +69 -69
  150. package/src/components/navigation/NavItemsContext.ts +20 -20
  151. package/src/components/navigation/ProjectSelectorPopover.tsx +157 -151
  152. package/src/components/navigation/shells/TabsMobileShell.tsx +72 -72
  153. package/src/components/navigation/subcomponents/ProjectSelector.tsx +155 -149
  154. package/src/components/pages/notifications/Notifications.tsx +160 -160
  155. package/src/components/ui/buttons/Button.tsx +57 -57
  156. package/src/components/ui/buttons/CancelButton.tsx +14 -14
  157. package/src/components/ui/buttons/CopyButton.tsx +54 -54
  158. package/src/components/ui/data-display/Accordion.tsx +81 -81
  159. package/src/components/ui/data-display/Badge.tsx +47 -47
  160. package/src/components/ui/data-display/Card.tsx +93 -93
  161. package/src/components/ui/data-display/Pagination.tsx +126 -126
  162. package/src/components/ui/data-display/ScrollArea.tsx +59 -59
  163. package/src/components/ui/data-display/Separator.tsx +29 -29
  164. package/src/components/ui/data-display/Table.tsx +117 -117
  165. package/src/components/ui/data-display/Tabs.tsx +67 -67
  166. package/src/components/ui/feedback/CircularProgress.tsx +75 -75
  167. package/src/components/ui/feedback/DefaultCircularProgress.tsx +61 -61
  168. package/src/components/ui/feedback/LoadingOverlay.tsx +31 -31
  169. package/src/components/ui/feedback/Progress.tsx +40 -40
  170. package/src/components/ui/feedback/Toast.tsx +101 -101
  171. package/src/components/ui/form/Calendar.tsx +228 -228
  172. package/src/components/ui/form/Checkbox.tsx +31 -31
  173. package/src/components/ui/form/ComboboxField.tsx +154 -154
  174. package/src/components/ui/form/DatePicker.tsx +97 -97
  175. package/src/components/ui/form/DateRangePicker.tsx +108 -108
  176. package/src/components/ui/form/FormField.tsx +131 -131
  177. package/src/components/ui/form/Input.tsx +20 -20
  178. package/src/components/ui/form/InputOtp.tsx +80 -80
  179. package/src/components/ui/form/PhoneInput.tsx +205 -205
  180. package/src/components/ui/form/RadioGroup.tsx +45 -45
  181. package/src/components/ui/form/Select.tsx +200 -200
  182. package/src/components/ui/form/Switch.tsx +32 -32
  183. package/src/components/ui/form/Textarea.tsx +19 -19
  184. package/src/components/ui/overlay/Command.tsx +185 -185
  185. package/src/components/ui/overlay/Dialog.tsx +151 -151
  186. package/src/components/ui/overlay/Popover.tsx +49 -49
  187. package/src/components/ui/overlay/Sheet.tsx +166 -166
  188. package/src/components/ui/overlay/Tooltip.tsx +62 -62
  189. package/src/hooks/copy-to-clipboard.hook.tsx +67 -67
  190. package/src/index.ts +359 -355
  191. package/src/middleware.ts +4 -4
  192. package/src/modules/accounts/actions/account-management.action.ts +90 -90
  193. package/src/modules/accounts/actions/find-current-account.action.ts +9 -9
  194. package/src/modules/accounts/actions/list-currencies.action.ts +8 -8
  195. package/src/modules/accounts/actions/list-timezones.action.ts +8 -8
  196. package/src/modules/accounts/hooks/current-account.hook.tsx +22 -22
  197. package/src/modules/accounts/hooks/useAccountManagement.ts +85 -85
  198. package/src/modules/accounts/hooks/useCurrencies.ts +15 -15
  199. package/src/modules/accounts/hooks/useTimezones.ts +15 -15
  200. package/src/modules/accounts/services/account.service.ts +236 -236
  201. package/src/modules/accounts/types.ts +214 -214
  202. package/src/modules/auth/hooks/login.hook.tsx +13 -13
  203. package/src/modules/auth/hooks/logout.hook.tsx +11 -11
  204. package/src/modules/auth/hooks/register.hook.tsx +11 -11
  205. package/src/modules/ia-credits/actions/list-ia-credits.action.ts +8 -8
  206. package/src/modules/projects/actions/add-project-user.action.ts +9 -9
  207. package/src/modules/projects/actions/list-all-account-users.action.ts +9 -9
  208. package/src/modules/projects/actions/list-available-users.action.ts +9 -9
  209. package/src/modules/projects/actions/list-project-users.action.ts +9 -9
  210. package/src/modules/projects/actions/remove-project-user.action.ts +9 -9
  211. package/src/modules/projects/hooks/add-project-user.hook.ts +69 -69
  212. package/src/modules/projects/hooks/list-all-account-users.hook.ts +34 -34
  213. package/src/modules/projects/hooks/list-available-users.hook.ts +33 -33
  214. package/src/modules/projects/hooks/list-project-users.hook.ts +33 -33
  215. package/src/modules/projects/hooks/remove-project-user.hook.ts +69 -69
  216. package/src/modules/subscriptions/actions/find-plan-by-id.action.ts +8 -0
  217. package/src/modules/subscriptions/actions/list-subscriptions.action.ts +9 -9
  218. package/src/modules/subscriptions/hooks/find-active-subscription.hook.ts +19 -19
  219. package/src/modules/subscriptions/hooks/list-subscriptions.hook.ts +17 -17
  220. package/src/modules/subscriptions/hooks/use-has-plan-addon.hook.ts +17 -0
  221. package/src/modules/subscriptions/hooks/use-plan-by-id.hook.ts +13 -0
  222. package/src/modules/subscriptions/services/plans.service.ts +36 -0
  223. package/src/modules/subscriptions/types/plan.type.ts +21 -0
  224. package/src/modules/users/action/find-user-by-id.action.ts +8 -8
  225. package/src/modules/users/action/list-messages.action.ts +12 -12
  226. package/src/modules/users/action/mark-all-messages-as-read.action.ts +8 -8
  227. package/src/modules/users/action/mark-message-as-read.action.ts +8 -8
  228. package/src/modules/users/hooks/messages.hook.ts +60 -60
  229. package/src/modules/whitelabel/actions/find-whitelabel-styles.action.ts +14 -14
  230. package/src/modules/whitelabel/actions/find-whitelabel.action.ts +23 -23
  231. package/src/modules/whitelabel/services/whitelabel-styles.service.ts +98 -98
  232. package/src/modules/whitelabel/styles-schema.ts +37 -37
  233. package/src/providers/whitelabel.provider.tsx +46 -46
  234. package/src/server.ts +2 -0
  235. package/src/utils/countries.ts +224 -224
  236. package/src/utils/safeServerAction.ts +21 -21
  237. package/src/utils/withAction.ts +13 -13
@@ -1,154 +1,154 @@
1
- 'use client';
2
-
3
- import { useState, useMemo } from 'react';
4
- import { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';
5
- import { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';
6
- import { cn } from '../../../infra/utils/clsx';
7
-
8
- export type ComboboxOption = {
9
- value: string;
10
- label: string;
11
- displayValue?: string;
12
- };
13
-
14
- interface ComboboxFieldProps {
15
- options: ComboboxOption[];
16
- label?: string;
17
- placeholder?: string;
18
- searchPlaceholder?: string;
19
- emptyMessage?: string;
20
- value?: string;
21
- onChange?: (value: string) => void;
22
- disabled?: boolean;
23
- icon?: React.ReactNode;
24
- className?: string;
25
- containerClassName?: string;
26
- }
27
-
28
- export function ComboboxField({
29
- options,
30
- label,
31
- placeholder = 'Selecione uma opção',
32
- searchPlaceholder = 'Buscar...',
33
- emptyMessage = 'Nenhuma opção encontrada.',
34
- value,
35
- onChange,
36
- disabled = false,
37
- icon,
38
- className,
39
- containerClassName,
40
- }: ComboboxFieldProps) {
41
- const [open, setOpen] = useState(false);
42
- const [search, setSearch] = useState('');
43
-
44
- const selectedOption = useMemo(
45
- () => options.find((o) => o.value === value),
46
- [options, value]
47
- );
48
-
49
- const filteredOptions = useMemo(() => {
50
- const q = search.trim().toLowerCase();
51
- if (!q) return options;
52
- return options.filter((o) => o.label.toLowerCase().includes(q));
53
- }, [options, search]);
54
-
55
- const handleSelect = (optionValue: string) => {
56
- onChange?.(optionValue);
57
- setOpen(false);
58
- setSearch('');
59
- };
60
-
61
- return (
62
- <div className={cn('flex flex-col gap-1.5', containerClassName)}>
63
- {label && (
64
- <div className="flex items-center gap-1 paragraph-xsmall-semibold">
65
- <span className="text-gray-600">{label}</span>
66
- </div>
67
- )}
68
-
69
- <Popover
70
- open={open}
71
- onOpenChange={(v) => {
72
- if (!disabled) {
73
- setOpen(v);
74
- if (!v) setSearch('');
75
- }
76
- }}
77
- >
78
- <PopoverTrigger asChild>
79
- <button
80
- type="button"
81
- disabled={disabled}
82
- className={cn(
83
- 'paragraph-small-medium w-full h-10 text-gray-950 px-4 transition-colors',
84
- 'flex items-center justify-between gap-2 rounded-md border bg-transparent',
85
- 'border-gray-200 hover:border-gray-400 focus:border-gray-950 focus:outline-none',
86
- disabled && 'cursor-not-allowed opacity-50',
87
- className
88
- )}
89
- >
90
- <span className={cn('truncate text-left', !selectedOption && 'text-gray-400')}>
91
- {selectedOption
92
- ? (selectedOption.displayValue ?? selectedOption.label)
93
- : placeholder}
94
- </span>
95
- {icon ?? (
96
- <IconChevronDown
97
- className={cn(
98
- 'size-4 opacity-50 shrink-0 transition-all duration-200',
99
- open && 'rotate-180 opacity-100 text-gray-950'
100
- )}
101
- />
102
- )}
103
- </button>
104
- </PopoverTrigger>
105
-
106
- <PopoverContent
107
- className="p-0 overflow-hidden rounded-md border shadow-md"
108
- align="start"
109
- sideOffset={4}
110
- style={{ width: 'var(--radix-popover-trigger-width)' }}
111
- onWheel={(e) => e.stopPropagation()}
112
- >
113
- <div className="flex items-center gap-2 border-b border-gray-100 px-3 h-9">
114
- <IconSearch className="size-4 shrink-0 opacity-50 text-gray-500" />
115
- <input
116
- autoFocus
117
- placeholder={searchPlaceholder}
118
- value={search}
119
- onChange={(e) => setSearch(e.target.value)}
120
- className="flex h-full w-full bg-transparent text-sm outline-none placeholder:text-gray-400 paragraph-small-medium text-gray-950"
121
- />
122
- </div>
123
-
124
- <div className="overflow-y-auto max-h-[280px] p-1">
125
- {filteredOptions.length === 0 ? (
126
- <p className="py-6 text-center paragraph-small-medium text-gray-500">{emptyMessage}</p>
127
- ) : (
128
- filteredOptions.map((option) => {
129
- const isSelected = value === option.value;
130
- return (
131
- <div
132
- key={option.value}
133
- className={cn(
134
- 'flex items-center gap-2 rounded-sm px-2 h-9 cursor-pointer select-none',
135
- 'paragraph-small-medium text-gray-950',
136
- 'hover:bg-gray-100',
137
- isSelected && 'bg-gray-50'
138
- )}
139
- onClick={() => handleSelect(option.value)}
140
- >
141
- <span className="flex-1 truncate">{option.label}</span>
142
- {isSelected && <IconCheck className="size-4 shrink-0 text-pages-600" />}
143
- </div>
144
- );
145
- })
146
- )}
147
- </div>
148
- </PopoverContent>
149
- </Popover>
150
- </div>
151
- );
152
- }
153
-
154
- export default ComboboxField;
1
+ 'use client';
2
+
3
+ import { useState, useMemo } from 'react';
4
+ import { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';
5
+ import { Popover, PopoverContent, PopoverTrigger } from '../overlay/Popover';
6
+ import { cn } from '../../../infra/utils/clsx';
7
+
8
+ export type ComboboxOption = {
9
+ value: string;
10
+ label: string;
11
+ displayValue?: string;
12
+ };
13
+
14
+ interface ComboboxFieldProps {
15
+ options: ComboboxOption[];
16
+ label?: string;
17
+ placeholder?: string;
18
+ searchPlaceholder?: string;
19
+ emptyMessage?: string;
20
+ value?: string;
21
+ onChange?: (value: string) => void;
22
+ disabled?: boolean;
23
+ icon?: React.ReactNode;
24
+ className?: string;
25
+ containerClassName?: string;
26
+ }
27
+
28
+ export function ComboboxField({
29
+ options,
30
+ label,
31
+ placeholder = 'Selecione uma opção',
32
+ searchPlaceholder = 'Buscar...',
33
+ emptyMessage = 'Nenhuma opção encontrada.',
34
+ value,
35
+ onChange,
36
+ disabled = false,
37
+ icon,
38
+ className,
39
+ containerClassName,
40
+ }: ComboboxFieldProps) {
41
+ const [open, setOpen] = useState(false);
42
+ const [search, setSearch] = useState('');
43
+
44
+ const selectedOption = useMemo(
45
+ () => options.find((o) => o.value === value),
46
+ [options, value]
47
+ );
48
+
49
+ const filteredOptions = useMemo(() => {
50
+ const q = search.trim().toLowerCase();
51
+ if (!q) return options;
52
+ return options.filter((o) => o.label.toLowerCase().includes(q));
53
+ }, [options, search]);
54
+
55
+ const handleSelect = (optionValue: string) => {
56
+ onChange?.(optionValue);
57
+ setOpen(false);
58
+ setSearch('');
59
+ };
60
+
61
+ return (
62
+ <div className={cn('flex flex-col gap-1.5', containerClassName)}>
63
+ {label && (
64
+ <div className="flex items-center gap-1 paragraph-xsmall-semibold">
65
+ <span className="text-gray-600">{label}</span>
66
+ </div>
67
+ )}
68
+
69
+ <Popover
70
+ open={open}
71
+ onOpenChange={(v) => {
72
+ if (!disabled) {
73
+ setOpen(v);
74
+ if (!v) setSearch('');
75
+ }
76
+ }}
77
+ >
78
+ <PopoverTrigger asChild>
79
+ <button
80
+ type="button"
81
+ disabled={disabled}
82
+ className={cn(
83
+ 'paragraph-small-medium w-full h-10 text-gray-950 px-4 transition-colors',
84
+ 'flex items-center justify-between gap-2 rounded-md border bg-transparent',
85
+ 'border-gray-200 hover:border-gray-400 focus:border-gray-950 focus:outline-none',
86
+ disabled && 'cursor-not-allowed opacity-50',
87
+ className
88
+ )}
89
+ >
90
+ <span className={cn('truncate text-left', !selectedOption && 'text-gray-400')}>
91
+ {selectedOption
92
+ ? (selectedOption.displayValue ?? selectedOption.label)
93
+ : placeholder}
94
+ </span>
95
+ {icon ?? (
96
+ <IconChevronDown
97
+ className={cn(
98
+ 'size-4 opacity-50 shrink-0 transition-all duration-200',
99
+ open && 'rotate-180 opacity-100 text-gray-950'
100
+ )}
101
+ />
102
+ )}
103
+ </button>
104
+ </PopoverTrigger>
105
+
106
+ <PopoverContent
107
+ className="p-0 overflow-hidden rounded-md border shadow-md"
108
+ align="start"
109
+ sideOffset={4}
110
+ style={{ width: 'var(--radix-popover-trigger-width)' }}
111
+ onWheel={(e) => e.stopPropagation()}
112
+ >
113
+ <div className="flex items-center gap-2 border-b border-gray-100 px-3 h-9">
114
+ <IconSearch className="size-4 shrink-0 opacity-50 text-gray-500" />
115
+ <input
116
+ autoFocus
117
+ placeholder={searchPlaceholder}
118
+ value={search}
119
+ onChange={(e) => setSearch(e.target.value)}
120
+ className="flex h-full w-full bg-transparent text-sm outline-none placeholder:text-gray-400 paragraph-small-medium text-gray-950"
121
+ />
122
+ </div>
123
+
124
+ <div className="overflow-y-auto max-h-[280px] p-1">
125
+ {filteredOptions.length === 0 ? (
126
+ <p className="py-6 text-center paragraph-small-medium text-gray-500">{emptyMessage}</p>
127
+ ) : (
128
+ filteredOptions.map((option) => {
129
+ const isSelected = value === option.value;
130
+ return (
131
+ <div
132
+ key={option.value}
133
+ className={cn(
134
+ 'flex items-center gap-2 rounded-sm px-2 h-9 cursor-pointer select-none',
135
+ 'paragraph-small-medium text-gray-950',
136
+ 'hover:bg-gray-100',
137
+ isSelected && 'bg-gray-50'
138
+ )}
139
+ onClick={() => handleSelect(option.value)}
140
+ >
141
+ <span className="flex-1 truncate">{option.label}</span>
142
+ {isSelected && <IconCheck className="size-4 shrink-0 text-pages-600" />}
143
+ </div>
144
+ );
145
+ })
146
+ )}
147
+ </div>
148
+ </PopoverContent>
149
+ </Popover>
150
+ </div>
151
+ );
152
+ }
153
+
154
+ export default ComboboxField;
@@ -1,97 +1,97 @@
1
- "use client";
2
-
3
- import { useState, forwardRef, useEffect } from "react";
4
- import { format } from "date-fns";
5
- import { Calendar } from "./Calendar";
6
- import { Popover, PopoverContent, PopoverTrigger } from "../overlay/Popover";
7
- import { cn } from "../../../infra/utils/clsx";
8
-
9
- // Converte string ISO (yyyy-MM-dd) para Date no fuso horário local
10
- // Evita bug de timezone onde new Date("2025-08-02") interpreta como UTC
11
- const isoParaDataLocal = (iso: string): Date => {
12
- const [ano, mes, dia] = iso.split("-").map(Number);
13
- return new Date(ano, mes - 1, dia);
14
- };
15
-
16
- interface DatePickerProps {
17
- value?: string;
18
- onChange?: (date: string | undefined) => void;
19
- placeholder?: string;
20
- className?: string;
21
- disabled?: boolean;
22
- }
23
-
24
- export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
25
- (
26
- { value, onChange, placeholder = "Selecione a data", className, disabled },
27
- ref,
28
- ) => {
29
- const [open, setOpen] = useState(false);
30
- const [date, setDate] = useState<Date | undefined>(() => {
31
- return value ? isoParaDataLocal(value) : undefined;
32
- });
33
-
34
- // Sincroniza estado interno quando value mudar externamente
35
- useEffect(() => {
36
- if (value) {
37
- setDate(isoParaDataLocal(value));
38
- } else {
39
- setDate(undefined);
40
- }
41
- }, [value]);
42
-
43
- const handleDateSelect = (selectedDate: Date | undefined) => {
44
- setDate(selectedDate);
45
-
46
- if (selectedDate) {
47
- onChange?.(format(selectedDate, "yyyy-MM-dd"));
48
- setOpen(false); // Fecha o popover após seleção
49
- } else {
50
- onChange?.(undefined);
51
- }
52
- };
53
-
54
- const displayText = date ? format(date, "dd/MM/yyyy") : placeholder;
55
-
56
- return (
57
- <Popover open={open} onOpenChange={setOpen}>
58
- <PopoverTrigger asChild>
59
- <button
60
- ref={ref}
61
- disabled={disabled}
62
- className={cn(
63
- "flex items-center gap-2 px-4 h-10 border rounded-lg transition-colors w-full justify-start",
64
- "border-gray-200 hover:border-gray-400 focus-within:border-gray-950",
65
- "disabled:opacity-50 disabled:cursor-not-allowed",
66
- className,
67
- )}
68
- >
69
- <span
70
- className={cn(
71
- "paragraph-small-medium",
72
- date ? "text-gray-950" : "text-gray-500",
73
- )}
74
- >
75
- {displayText}
76
- </span>
77
- </button>
78
- </PopoverTrigger>
79
- <PopoverContent
80
- className="w-auto p-0 bg-white rounded-2xl shadow-md border border-gray-200"
81
- align="start"
82
- sideOffset={8}
83
- >
84
- <div className="p-3">
85
- <Calendar
86
- mode="single"
87
- selected={date}
88
- onSelect={handleDateSelect}
89
- />
90
- </div>
91
- </PopoverContent>
92
- </Popover>
93
- );
94
- },
95
- );
96
-
97
- DatePicker.displayName = "DatePicker";
1
+ "use client";
2
+
3
+ import { useState, forwardRef, useEffect } from "react";
4
+ import { format } from "date-fns";
5
+ import { Calendar } from "./Calendar";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "../overlay/Popover";
7
+ import { cn } from "../../../infra/utils/clsx";
8
+
9
+ // Converte string ISO (yyyy-MM-dd) para Date no fuso horário local
10
+ // Evita bug de timezone onde new Date("2025-08-02") interpreta como UTC
11
+ const isoParaDataLocal = (iso: string): Date => {
12
+ const [ano, mes, dia] = iso.split("-").map(Number);
13
+ return new Date(ano, mes - 1, dia);
14
+ };
15
+
16
+ interface DatePickerProps {
17
+ value?: string;
18
+ onChange?: (date: string | undefined) => void;
19
+ placeholder?: string;
20
+ className?: string;
21
+ disabled?: boolean;
22
+ }
23
+
24
+ export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
25
+ (
26
+ { value, onChange, placeholder = "Selecione a data", className, disabled },
27
+ ref,
28
+ ) => {
29
+ const [open, setOpen] = useState(false);
30
+ const [date, setDate] = useState<Date | undefined>(() => {
31
+ return value ? isoParaDataLocal(value) : undefined;
32
+ });
33
+
34
+ // Sincroniza estado interno quando value mudar externamente
35
+ useEffect(() => {
36
+ if (value) {
37
+ setDate(isoParaDataLocal(value));
38
+ } else {
39
+ setDate(undefined);
40
+ }
41
+ }, [value]);
42
+
43
+ const handleDateSelect = (selectedDate: Date | undefined) => {
44
+ setDate(selectedDate);
45
+
46
+ if (selectedDate) {
47
+ onChange?.(format(selectedDate, "yyyy-MM-dd"));
48
+ setOpen(false); // Fecha o popover após seleção
49
+ } else {
50
+ onChange?.(undefined);
51
+ }
52
+ };
53
+
54
+ const displayText = date ? format(date, "dd/MM/yyyy") : placeholder;
55
+
56
+ return (
57
+ <Popover open={open} onOpenChange={setOpen}>
58
+ <PopoverTrigger asChild>
59
+ <button
60
+ ref={ref}
61
+ disabled={disabled}
62
+ className={cn(
63
+ "flex items-center gap-2 px-4 h-10 border rounded-lg transition-colors w-full justify-start",
64
+ "border-gray-200 hover:border-gray-400 focus-within:border-gray-950",
65
+ "disabled:opacity-50 disabled:cursor-not-allowed",
66
+ className,
67
+ )}
68
+ >
69
+ <span
70
+ className={cn(
71
+ "paragraph-small-medium",
72
+ date ? "text-gray-950" : "text-gray-500",
73
+ )}
74
+ >
75
+ {displayText}
76
+ </span>
77
+ </button>
78
+ </PopoverTrigger>
79
+ <PopoverContent
80
+ className="w-auto p-0 bg-white rounded-2xl shadow-md border border-gray-200"
81
+ align="start"
82
+ sideOffset={8}
83
+ >
84
+ <div className="p-3">
85
+ <Calendar
86
+ mode="single"
87
+ selected={date}
88
+ onSelect={handleDateSelect}
89
+ />
90
+ </div>
91
+ </PopoverContent>
92
+ </Popover>
93
+ );
94
+ },
95
+ );
96
+
97
+ DatePicker.displayName = "DatePicker";