aplance-ui 1.0.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 (132) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/api.d.ts +175 -0
  4. package/dist/api.js +1954 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/frameworks/astro/astro.config.snippet.md +23 -0
  7. package/dist/frameworks/astro/postcss.config.mjs.template +20 -0
  8. package/dist/frameworks/astro/vite-inline.snippet.md +22 -0
  9. package/dist/frameworks/electron-vite/vite.config.snippet.md +44 -0
  10. package/dist/frameworks/next/layout-import.snippet.md +19 -0
  11. package/dist/frameworks/next/next.config.snippet.md +15 -0
  12. package/dist/frameworks/next/postcss.config.js.template +16 -0
  13. package/dist/frameworks/tanstack-start/postcss.config.ts.template +17 -0
  14. package/dist/frameworks/tanstack-start/vite-inline.snippet.md +22 -0
  15. package/dist/frameworks/vite-react/postcss.config.ts.template +16 -0
  16. package/dist/frameworks/vite-react/vite-inline.snippet.md +20 -0
  17. package/dist/frameworks/vite-react/vite.config.ts.template +48 -0
  18. package/dist/index.js +2047 -0
  19. package/dist/index.js.map +1 -0
  20. package/package.json +63 -0
  21. package/registry/components/primitives/icon/icon.module.css +63 -0
  22. package/registry/components/primitives/icon/icon.tsx +53 -0
  23. package/registry/components/primitives/modal/modal-context.ts +19 -0
  24. package/registry/components/primitives/modal/modal.module.css +415 -0
  25. package/registry/components/primitives/modal/modal.tsx +550 -0
  26. package/registry/components/primitives/popover/popover-context.ts +28 -0
  27. package/registry/components/primitives/popover/popover.module.css +28 -0
  28. package/registry/components/primitives/popover/popover.tsx +465 -0
  29. package/registry/components/ui/accordion/accordion.module.css +208 -0
  30. package/registry/components/ui/accordion/accordion.tsx +327 -0
  31. package/registry/components/ui/avatar/avatar.module.css +32 -0
  32. package/registry/components/ui/avatar/avatar.tsx +37 -0
  33. package/registry/components/ui/badge/badge.module.css +103 -0
  34. package/registry/components/ui/badge/badge.tsx +29 -0
  35. package/registry/components/ui/button/button.module.css +216 -0
  36. package/registry/components/ui/button/button.tsx +133 -0
  37. package/registry/components/ui/chart/chart.module.css +168 -0
  38. package/registry/components/ui/chart/chart.tsx +1293 -0
  39. package/registry/components/ui/chart/types.ts +210 -0
  40. package/registry/components/ui/chart/utils.ts +59 -0
  41. package/registry/components/ui/copy-button/copy-button.module.css +21 -0
  42. package/registry/components/ui/copy-button/copy-button.tsx +123 -0
  43. package/registry/components/ui/countdown/countdown.module.css +5 -0
  44. package/registry/components/ui/countdown/countdown.tsx +152 -0
  45. package/registry/components/ui/data-table/checkbox-column.tsx +59 -0
  46. package/registry/components/ui/data-table/data-table.module.css +594 -0
  47. package/registry/components/ui/data-table/data-table.tsx +1109 -0
  48. package/registry/components/ui/data-table/filter-bar.tsx +33 -0
  49. package/registry/components/ui/data-table/pagination-bar.tsx +125 -0
  50. package/registry/components/ui/data-table/types.ts +594 -0
  51. package/registry/components/ui/data-table/useDataTable.tsx +573 -0
  52. package/registry/components/ui/data-table/utils.ts +445 -0
  53. package/registry/components/ui/empty-state/empty-state.module.css +26 -0
  54. package/registry/components/ui/empty-state/empty-state.tsx +32 -0
  55. package/registry/components/ui/input-fields/color-picker/color-palette.module.css +102 -0
  56. package/registry/components/ui/input-fields/color-picker/color-palette.tsx +92 -0
  57. package/registry/components/ui/input-fields/color-picker/color-picker.module.css +29 -0
  58. package/registry/components/ui/input-fields/color-picker/color-picker.tsx +168 -0
  59. package/registry/components/ui/input-fields/common/field-message/field-message.module.css +28 -0
  60. package/registry/components/ui/input-fields/common/field-message/field-message.tsx +39 -0
  61. package/registry/components/ui/input-fields/common/input-field-common.module.css +318 -0
  62. package/registry/components/ui/input-fields/common/input-field-native.module.css +267 -0
  63. package/registry/components/ui/input-fields/common/picker-popover-common.module.css +40 -0
  64. package/registry/components/ui/input-fields/common/select-button/select-button.module.css +9 -0
  65. package/registry/components/ui/input-fields/common/select-button/select-button.tsx +171 -0
  66. package/registry/components/ui/input-fields/common/types.ts +38 -0
  67. package/registry/components/ui/input-fields/custom-select/custom-select.module.css +56 -0
  68. package/registry/components/ui/input-fields/custom-select/custom-select.tsx +403 -0
  69. package/registry/components/ui/input-fields/date-picker/calendar.module.css +311 -0
  70. package/registry/components/ui/input-fields/date-picker/calendar.tsx +186 -0
  71. package/registry/components/ui/input-fields/date-picker/date-picker.module.css +57 -0
  72. package/registry/components/ui/input-fields/date-picker/date-picker.tsx +945 -0
  73. package/registry/components/ui/input-fields/date-picker/types.ts +102 -0
  74. package/registry/components/ui/input-fields/date-picker/utils.ts +236 -0
  75. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.module.css +90 -0
  76. package/registry/components/ui/input-fields/emoji-picker/emoji-palette.tsx +168 -0
  77. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.module.css +8 -0
  78. package/registry/components/ui/input-fields/emoji-picker/emoji-picker.tsx +102 -0
  79. package/registry/components/ui/input-fields/emoji-picker/utils.ts +505 -0
  80. package/registry/components/ui/input-fields/field-group/field-group.module.css +86 -0
  81. package/registry/components/ui/input-fields/field-group/field-group.tsx +86 -0
  82. package/registry/components/ui/input-fields/input/input.tsx +319 -0
  83. package/registry/components/ui/input-fields/media-input/kind-icons.tsx +62 -0
  84. package/registry/components/ui/input-fields/media-input/media-input.module.css +294 -0
  85. package/registry/components/ui/input-fields/media-input/media-input.tsx +327 -0
  86. package/registry/components/ui/input-fields/media-input/media-preview.tsx +101 -0
  87. package/registry/components/ui/input-fields/media-input/types.ts +129 -0
  88. package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +302 -0
  89. package/registry/components/ui/input-fields/media-input/utils.ts +325 -0
  90. package/registry/components/ui/input-fields/otp-input/otp-input.module.css +24 -0
  91. package/registry/components/ui/input-fields/otp-input/otp-input.tsx +291 -0
  92. package/registry/components/ui/input-fields/select/select.tsx +118 -0
  93. package/registry/components/ui/input-fields/switch/switch.module.css +70 -0
  94. package/registry/components/ui/input-fields/switch/switch.tsx +59 -0
  95. package/registry/components/ui/input-fields/textarea/textarea.tsx +133 -0
  96. package/registry/components/ui/input-fields/time-picker/time-picker.module.css +57 -0
  97. package/registry/components/ui/input-fields/time-picker/time-picker.tsx +437 -0
  98. package/registry/components/ui/loader/loader.module.css +156 -0
  99. package/registry/components/ui/loader/loader.tsx +102 -0
  100. package/registry/components/ui/stepper/stepper.module.css +294 -0
  101. package/registry/components/ui/stepper/stepper.tsx +197 -0
  102. package/registry/components/ui/tabs/tabs.module.css +150 -0
  103. package/registry/components/ui/tabs/tabs.tsx +134 -0
  104. package/registry/components/ui/toast/toast.module.css +159 -0
  105. package/registry/components/ui/toast/toast.tsx +104 -0
  106. package/registry/components/ui/tooltip/tooltip.module.css +82 -0
  107. package/registry/components/ui/tooltip/tooltip.tsx +72 -0
  108. package/registry/global.d.ts +4 -0
  109. package/registry/hooks/useFocusOutside.tsx +33 -0
  110. package/registry/hooks/useResizeObserver.tsx +32 -0
  111. package/registry/lib/dom.ts +25 -0
  112. package/registry/lib/string.ts +23 -0
  113. package/registry/registry.json +719 -0
  114. package/registry/styles/app.css +240 -0
  115. package/registry/styles/components.css +78 -0
  116. package/registry/styles/fonts/geist-regular.woff2 +0 -0
  117. package/registry/styles/globals.css +7 -0
  118. package/registry/styles/media.css +25 -0
  119. package/registry/styles/utilities.css +90 -0
  120. package/registry/themes/colors/blue.css +37 -0
  121. package/registry/themes/colors/green.css +37 -0
  122. package/registry/themes/colors/rose.css +37 -0
  123. package/registry/themes/colors/slate.css +37 -0
  124. package/registry/themes/colors/stone.css +37 -0
  125. package/registry/themes/colors/violet.css +37 -0
  126. package/registry/themes/colors/zinc.css +37 -0
  127. package/registry/themes/radius/pill.css +8 -0
  128. package/registry/themes/radius/rounded.css +8 -0
  129. package/registry/themes/radius/sharp.css +8 -0
  130. package/registry/themes/spacing/compact.css +17 -0
  131. package/registry/themes/spacing/spacious.css +17 -0
  132. package/registry/tsconfig.json +25 -0
@@ -0,0 +1,403 @@
1
+ import type { Align, Position } from "../../../primitives/popover/popover";
2
+ import {
3
+ type CSSProperties,
4
+ type ReactNode,
5
+ useCallback,
6
+ useEffect,
7
+ useId,
8
+ useMemo,
9
+ useState,
10
+ } from "react";
11
+ import clsx from "clsx";
12
+ import { Input } from "../input/input";
13
+ import {
14
+ Popover,
15
+ PopoverTrigger,
16
+ PopoverMenu,
17
+ } from "../../../primitives/popover/popover";
18
+ import type { ComponentProps } from "react";
19
+ import sCommon from "../common/input-field-common.module.css";
20
+ import sPickerCommon from "../common/picker-popover-common.module.css";
21
+ import { Button, type ButtonVariant } from "../../button/button";
22
+ import s from "./custom-select.module.css";
23
+ import { FieldMessage } from "../common/field-message/field-message";
24
+ import { SelectButton, type SelectButtonProps } from "../common/select-button/select-button";
25
+ import { Icon } from "../../../primitives/icon/icon";
26
+ import type { BaseInputFieldProps } from "../common/types";
27
+
28
+ export type CustomSelectOption = {
29
+ value: string;
30
+ label: string;
31
+ icon?: ReactNode;
32
+ optionStyle?: CSSProperties;
33
+ optionClassName?: string;
34
+ optionButtonVariant?: ButtonVariant;
35
+ applyToSelector?: boolean;
36
+ disabled?: boolean;
37
+ };
38
+
39
+ export type CustomSelectProps = Omit<
40
+ BaseInputFieldProps,
41
+ // its used in selectButton so no need it here
42
+ "buttonStyle"
43
+ > & {
44
+ // Select specific props
45
+ placeholder?: string;
46
+ searchPlaceholder?: string;
47
+ options: CustomSelectOption[];
48
+ value?: string | string[];
49
+ defaultValue?: string | string[];
50
+ onChange?: (value: string | string[]) => void;
51
+ multiple?: boolean;
52
+ searchable?: boolean;
53
+ showDisplayText?: boolean;
54
+ showDisplayIcon?: boolean;
55
+ onClear?: () => void;
56
+
57
+ // Popover behavior defaults
58
+ closeOnPathChange?: boolean;
59
+ closeOnClickOutside?: boolean;
60
+ animationDuration?: number;
61
+ position?: Position;
62
+ align?: Align;
63
+
64
+ // Nested popover props
65
+ popover?: Omit<ComponentProps<typeof Popover>, "children">;
66
+ popoverTrigger?: ComponentProps<typeof PopoverTrigger>;
67
+ popoverMenu?: ComponentProps<typeof PopoverMenu>;
68
+
69
+ // Options inside menu
70
+ optionsContainerClassName?: string;
71
+ optionsContainerStyle?: CSSProperties;
72
+
73
+ // Select button props
74
+ selectButton?: SelectButtonProps;
75
+ };
76
+
77
+ export const CustomSelect = ({
78
+ // Base props
79
+ label,
80
+ className,
81
+ style,
82
+ id,
83
+ info,
84
+ errors,
85
+ layout = "default",
86
+
87
+ // Select options and behavior
88
+ options,
89
+ value,
90
+ defaultValue,
91
+ onChange,
92
+ multiple = false,
93
+ searchable = false,
94
+ showDisplayIcon = true,
95
+ showDisplayText = true,
96
+ disabled = false,
97
+ readOnly = false,
98
+ required = false,
99
+ onClear,
100
+ placeholder = "Select...",
101
+ searchPlaceholder = "Search options...",
102
+
103
+ // Popover behavior
104
+ closeOnPathChange = true,
105
+ closeOnClickOutside = true,
106
+ animationDuration = 200,
107
+ position = "bottom",
108
+ align = "start",
109
+
110
+ // Menu options props
111
+ optionsContainerClassName,
112
+ optionsContainerStyle,
113
+
114
+ // Nested popover props
115
+ popover,
116
+ popoverTrigger,
117
+ popoverMenu,
118
+ selectButton,
119
+ }: CustomSelectProps) => {
120
+ const [searchTerm, setSearchTerm] = useState("");
121
+ const [isOpen, setIsOpen] = useState(false);
122
+
123
+ // Internal state for uncontrolled mode
124
+ const [internalValue, setInternalValue] = useState<string | string[]>(() => {
125
+ if (defaultValue !== undefined) {
126
+ return Array.isArray(defaultValue)
127
+ ? defaultValue
128
+ : defaultValue
129
+ ? [defaultValue]
130
+ : [];
131
+ }
132
+ return multiple ? [] : "";
133
+ });
134
+
135
+ const generatedId = useId();
136
+ const inputId = id ?? generatedId;
137
+ const isControlled = value !== undefined;
138
+ const actualValue = isControlled ? value : internalValue;
139
+ const interactive = !disabled && !readOnly;
140
+
141
+ // Normalize to array for consistent handling
142
+ const selectedValues = useMemo(
143
+ () =>
144
+ Array.isArray(actualValue)
145
+ ? actualValue
146
+ : actualValue
147
+ ? [actualValue]
148
+ : [],
149
+ [actualValue],
150
+ );
151
+
152
+ // Filter options based on search
153
+ const filteredOptions = useMemo(
154
+ () =>
155
+ searchable && searchTerm
156
+ ? options.filter((option) =>
157
+ option.label.toLowerCase().includes(searchTerm.toLowerCase()),
158
+ )
159
+ : options,
160
+ [options, searchable, searchTerm],
161
+ );
162
+
163
+ // Get all selected option data in one place
164
+ const selectedOptionData = useMemo(() => {
165
+ const hasValue = selectedValues.length > 0;
166
+ const selectedOption = options.find(
167
+ (opt) => opt.value === selectedValues[0],
168
+ );
169
+ const isMultipleSelection = multiple && selectedValues.length > 1;
170
+
171
+ return {
172
+ hasValue,
173
+ option: selectedOption,
174
+ label: selectedOption?.label,
175
+ icon: selectedOption?.icon,
176
+ displayText: !hasValue
177
+ ? placeholder
178
+ : isMultipleSelection
179
+ ? `${selectedValues.length} selected`
180
+ : selectedOption?.label,
181
+ shouldShowIcon:
182
+ showDisplayIcon && selectedOption?.icon && selectedValues.length === 1,
183
+ appliedClassName: selectedOption?.applyToSelector
184
+ ? selectedOption.optionClassName
185
+ : undefined,
186
+ appliedStyle: selectedOption?.applyToSelector
187
+ ? selectedOption.optionStyle
188
+ : undefined,
189
+ };
190
+ }, [multiple, options, placeholder, selectedValues, showDisplayIcon]);
191
+
192
+ const popoverOnClose = popover?.onClose;
193
+ const handlePopoverClose = useCallback(() => {
194
+ setSearchTerm("");
195
+ popoverOnClose?.();
196
+ }, [popoverOnClose]);
197
+
198
+ const {
199
+ className: popoverClassName,
200
+ offset: popoverOffset = 4,
201
+ ...popoverRest
202
+ } = popover ?? {};
203
+
204
+ const {
205
+ className: popoverTriggerClassName,
206
+ style: popoverTriggerStyle,
207
+ ...popoverTriggerRest
208
+ } = popoverTrigger ?? {};
209
+
210
+ const {
211
+ className: popoverMenuClassName,
212
+ style: popoverMenuStyle,
213
+ ...popoverMenuRest
214
+ } = popoverMenu ?? {};
215
+
216
+ // Handle external value changes for controlled mode
217
+ useEffect(() => {
218
+ if (isControlled) {
219
+ const normalized = Array.isArray(value) ? value : value ? [value] : [];
220
+ const isDifferent =
221
+ normalized.length !== selectedValues.length ||
222
+ normalized.some((val, i) => val !== selectedValues[i]);
223
+
224
+ if (isDifferent && !normalized.length) {
225
+ onClear?.();
226
+ }
227
+ }
228
+ }, [value, onClear, selectedValues, isControlled]);
229
+
230
+ const handleSelect = (option: CustomSelectOption) => {
231
+ if (!interactive || option.disabled) return;
232
+
233
+ const optionValue = option.value;
234
+
235
+ let newValues: string | string[];
236
+
237
+ if (multiple) {
238
+ newValues = selectedValues.includes(optionValue)
239
+ ? selectedValues.filter((val) => val !== optionValue)
240
+ : [...selectedValues, optionValue];
241
+ } else {
242
+ newValues = optionValue;
243
+ setIsOpen(false);
244
+ }
245
+
246
+ if (!isControlled) {
247
+ setInternalValue(newValues);
248
+ }
249
+
250
+ onChange?.(newValues);
251
+ };
252
+
253
+ const handleClear = (e: React.MouseEvent) => {
254
+ e.stopPropagation();
255
+ if (!interactive) return;
256
+
257
+ const newValue = multiple ? [] : "";
258
+
259
+ if (!isControlled) {
260
+ setInternalValue(newValue);
261
+ }
262
+
263
+ onChange?.(newValue);
264
+ onClear?.();
265
+ };
266
+
267
+ return (
268
+ <div className={clsx(sCommon.container, className)} style={style}>
269
+ {label && (
270
+ <div
271
+ className={clsx(
272
+ sCommon.field_container,
273
+ layout !== "default" && sCommon[layout],
274
+ )}
275
+ >
276
+ <label className={sCommon.label} htmlFor={inputId}>
277
+ {label}
278
+ </label>
279
+ </div>
280
+ )}
281
+
282
+ <Popover
283
+ closeOnPathChange={closeOnPathChange}
284
+ closeOnClickOutside={closeOnClickOutside}
285
+ animationDuration={animationDuration}
286
+ isOpen={isOpen}
287
+ setIsOpen={setIsOpen}
288
+ position={position}
289
+ align={align}
290
+ className={clsx(sPickerCommon.picker_popover, popoverClassName)}
291
+ offset={popoverOffset}
292
+ {...popoverRest}
293
+ onClose={handlePopoverClose}
294
+ >
295
+ <PopoverTrigger
296
+ asChild
297
+ className={clsx(
298
+ sPickerCommon.picker_popover_trigger,
299
+ popoverTriggerClassName,
300
+ )}
301
+ style={popoverTriggerStyle}
302
+ disabled={disabled || readOnly}
303
+ {...popoverTriggerRest}
304
+ >
305
+ <SelectButton
306
+ disabled={disabled || readOnly}
307
+ required={required}
308
+ showClear={selectedOptionData.hasValue && interactive}
309
+ onClear={handleClear}
310
+ id={inputId}
311
+ className={clsx(
312
+ !selectedOptionData.hasValue &&
313
+ sPickerCommon.picker_value_placeholder,
314
+ selectButton?.className,
315
+ selectedOptionData.appliedClassName,
316
+ )}
317
+ style={{
318
+ ...selectButton?.style,
319
+ ...selectedOptionData.appliedStyle,
320
+ }}
321
+ iconStart={
322
+ selectedOptionData.shouldShowIcon ? (
323
+ <Icon>{selectedOptionData.icon}</Icon>
324
+ ) : undefined
325
+ }
326
+ {...selectButton}
327
+ >
328
+ {showDisplayText && (
329
+ <span className={clsx(s.option_label, sCommon.truncate_text)}>
330
+ {selectedOptionData.displayText}
331
+ </span>
332
+ )}
333
+ </SelectButton>
334
+ </PopoverTrigger>
335
+
336
+ {!disabled && !readOnly && (
337
+ <PopoverMenu
338
+ className={clsx(
339
+ sPickerCommon.picker_popover_menu_base,
340
+ s.popover_menu,
341
+ popoverMenuClassName,
342
+ )}
343
+ style={popoverMenuStyle}
344
+ {...popoverMenuRest}
345
+ >
346
+ {searchable && (
347
+ <div className={s.search_container}>
348
+ <Input
349
+ type="search"
350
+ placeholder={searchPlaceholder}
351
+ value={searchTerm}
352
+ onChange={(e) => setSearchTerm(e.target.value)}
353
+ onClick={(e) => e.stopPropagation()}
354
+ showClear={true}
355
+ autoFocus
356
+ />
357
+ </div>
358
+ )}
359
+
360
+ <div
361
+ className={clsx(s.options_container, optionsContainerClassName)}
362
+ style={optionsContainerStyle}
363
+ >
364
+ {filteredOptions.length === 0 ? (
365
+ <p className={s.no_options}>No options found</p>
366
+ ) : (
367
+ filteredOptions.map((option) => (
368
+ <Button
369
+ key={option.value}
370
+ as="div"
371
+ variant={option.optionButtonVariant || "ghost"}
372
+ className={clsx(s.option, option.optionClassName)}
373
+ style={option.optionStyle}
374
+ isActive={selectedValues.includes(option.value)}
375
+ disabled={disabled || readOnly || option.disabled}
376
+ onClick={() => handleSelect(option)}
377
+ >
378
+ {multiple && (
379
+ <Input
380
+ type="checkbox"
381
+ className={sCommon.option_checkbox}
382
+ checked={selectedValues.includes(option.value)}
383
+ disabled={disabled || readOnly || option.disabled}
384
+ />
385
+ )}
386
+ {option.icon && option.icon}
387
+ <span
388
+ className={clsx(s.option_label, sCommon.truncate_text)}
389
+ >
390
+ {option.label}
391
+ </span>
392
+ </Button>
393
+ ))
394
+ )}
395
+ </div>
396
+ </PopoverMenu>
397
+ )}
398
+ </Popover>
399
+
400
+ <FieldMessage errors={errors} info={info} />
401
+ </div>
402
+ );
403
+ };
@@ -0,0 +1,311 @@
1
+ /* calendar.module.css – standard absolute nav layout (shadcn pattern), clean spacing */
2
+ .calendar {
3
+ --calendar-cell-size: var(--spacing-11);
4
+ --calendar-cell-radius: var(--radius-md);
5
+ --calendar-row-gap: var(--spacing-1);
6
+
7
+ width: fit-content;
8
+ font-family: var(--font-family-1);
9
+ color: var(--color-foreground);
10
+ background: transparent;
11
+ user-select: none;
12
+ }
13
+
14
+ .root {
15
+ width: fit-content;
16
+ }
17
+
18
+ .months {
19
+ display: flex;
20
+ flex-direction: row;
21
+ gap: var(--spacing-4);
22
+ position: relative;
23
+ }
24
+
25
+ .month {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--spacing-3);
29
+ }
30
+
31
+ /* Nav floats at left/right edges over the caption row */
32
+ .nav {
33
+ position: absolute;
34
+ inset-inline: 0;
35
+ top: 0;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ pointer-events: none;
40
+ z-index: 1;
41
+ height: var(--calendar-cell-size);
42
+
43
+ & > * {
44
+ pointer-events: auto;
45
+ }
46
+ }
47
+
48
+
49
+
50
+ .chevron {
51
+ width: var(--spacing-8);
52
+ height: var(--spacing-8);
53
+ }
54
+
55
+ /* Caption: centered text/dropdowns with padding to avoid arrow overlap */
56
+ .month_caption {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ min-height: var(--calendar-cell-size);
61
+ padding-inline: var(--calendar-cell-size);
62
+ }
63
+
64
+ .caption_label {
65
+ font-size: var(--font-size-sm);
66
+ font-weight: var(--font-weight-medium);
67
+ color: var(--color-foreground);
68
+ display: inline-flex;
69
+ align-items: center;
70
+ gap: var(--spacing-2);
71
+ }
72
+
73
+ .dropdowns {
74
+ display: grid;
75
+ grid-auto-flow: column;
76
+ grid-auto-columns: minmax(0, 1fr);
77
+ align-items: center;
78
+ gap: var(--spacing-4);
79
+ width: 100%;
80
+ min-width: 0;
81
+ }
82
+
83
+ .dropdown_root {
84
+ position: relative;
85
+ display: flex;
86
+ align-items: center;
87
+ width: 100%;
88
+ min-width: 0;
89
+ }
90
+
91
+ .dropdown {
92
+ position: absolute;
93
+ inset: 0;
94
+ opacity: 0;
95
+ pointer-events: none;
96
+ }
97
+
98
+ .dropdown_field {
99
+ width: 100%;
100
+ min-width: 0;
101
+ }
102
+
103
+ .dropdown_select_button {
104
+ width: 100%;
105
+ min-width: 0;
106
+ overflow: hidden;
107
+ background: var(--color-muted);
108
+ border: var(--border-1) solid transparent;
109
+ border-radius: var(--calendar-cell-radius);
110
+ transition:
111
+ border-color var(--transition-fast),
112
+ background-color var(--transition-fast),
113
+ box-shadow var(--transition-fast);
114
+
115
+ &:hover {
116
+ border-color: var(--color-border);
117
+ }
118
+
119
+ &:focus,
120
+ &[data-active="true"] {
121
+ border-color: var(--color-ring);
122
+ box-shadow: var(--shadow-ring);
123
+ }
124
+
125
+ > span,
126
+ > span > span {
127
+ display: block;
128
+ min-width: 0;
129
+ overflow: hidden;
130
+ text-overflow: ellipsis;
131
+ white-space: nowrap;
132
+ }
133
+ }
134
+
135
+ .dropdown_menu {
136
+ min-width: 100%;
137
+ background-color: var(--color-background);
138
+ border: var(--border-1) solid var(--color-border);
139
+ border-radius: var(--radius-md);
140
+ box-shadow: var(--shadow-lg);
141
+ }
142
+
143
+ .dropdown_options {
144
+ max-height: 13rem;
145
+ }
146
+
147
+ .dropdown_option {
148
+ padding-block: var(--spacing-3);
149
+ }
150
+
151
+ .month_grid {
152
+ width: 100%;
153
+ border-collapse: collapse;
154
+ border-spacing: 0;
155
+ }
156
+
157
+ .weekdays {
158
+ display: flex;
159
+ margin-bottom: var(--spacing-1);
160
+ }
161
+
162
+ .weekday {
163
+ flex: 1;
164
+ min-width: var(--calendar-cell-size);
165
+ height: var(--calendar-cell-size);
166
+ display: inline-flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ font-size: var(--font-size-xs);
170
+ font-weight: var(--font-weight-medium);
171
+ color: var(--color-muted-foreground);
172
+ text-transform: capitalize;
173
+ letter-spacing: var(--letter-spacing-wide);
174
+ }
175
+
176
+ .weeks {
177
+ display: flex;
178
+ flex-direction: column;
179
+ gap: var(--calendar-row-gap);
180
+ }
181
+
182
+ .week {
183
+ display: flex;
184
+ }
185
+
186
+ .week_number,
187
+ .week_number_header {
188
+ width: var(--calendar-cell-size);
189
+ height: var(--calendar-cell-size);
190
+ display: inline-flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ font-size: var(--font-size-xs);
194
+ color: var(--color-muted-foreground);
195
+ }
196
+
197
+ .day {
198
+ flex: 1;
199
+ min-width: var(--calendar-cell-size);
200
+ height: var(--calendar-cell-size);
201
+ padding: 0;
202
+ position: relative;
203
+ text-align: center;
204
+ display: inline-flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+
208
+ &.outside {
209
+ color: var(--color-muted-foreground);
210
+ opacity: 0.55;
211
+ }
212
+
213
+ &.today .day_button {
214
+ font-weight: var(--font-weight-medium);
215
+ color: var(--color-foreground);
216
+ border-color: var(--color-border);
217
+ background: var(--color-muted);
218
+ }
219
+
220
+ /* Selected states stay solid primary – hover no longer overrides */
221
+ &.selected .day_button,
222
+ &.range_start .day_button,
223
+ &.range_end .day_button {
224
+ background: var(--color-primary);
225
+ color: var(--color-primary-foreground);
226
+ border-color: var(--color-primary);
227
+ font-weight: var(--font-weight-medium);
228
+ }
229
+
230
+ /* Explicit hover override for selected states */
231
+ &.selected .day_button:hover:not(:disabled),
232
+ &.range_start .day_button:hover:not(:disabled),
233
+ &.range_end .day_button:hover:not(:disabled) {
234
+ background: var(--color-primary);
235
+ color: var(--color-primary-foreground);
236
+ border-color: var(--color-primary);
237
+ }
238
+
239
+ &.range_middle {
240
+ background: var(--color-muted);
241
+ border-radius: 0;
242
+
243
+ .day_button {
244
+ background: transparent;
245
+ color: var(--color-foreground);
246
+ border-color: transparent;
247
+ }
248
+ }
249
+
250
+ &.range_start {
251
+ border-top-left-radius: var(--calendar-cell-radius);
252
+ border-bottom-left-radius: var(--calendar-cell-radius);
253
+ background: var(--color-muted);
254
+ }
255
+
256
+ &.range_end {
257
+ border-top-right-radius: var(--calendar-cell-radius);
258
+ border-bottom-right-radius: var(--calendar-cell-radius);
259
+ background: var(--color-muted);
260
+ }
261
+
262
+ &.day_disabled .day_button {
263
+ opacity: 0.35;
264
+ cursor: not-allowed;
265
+ text-decoration: line-through;
266
+ }
267
+ }
268
+
269
+ .day_button {
270
+ position: relative;
271
+ width: calc(var(--calendar-cell-size) - var(--spacing-2));
272
+ height: calc(var(--calendar-cell-size) - var(--spacing-2));
273
+ margin: auto;
274
+ display: inline-flex;
275
+ align-items: center;
276
+ justify-content: center;
277
+ background: transparent;
278
+ color: inherit;
279
+ font-family: inherit;
280
+ font-size: var(--font-size-sm);
281
+ font-weight: var(--font-weight-normal);
282
+ border: var(--border-1) solid transparent;
283
+ border-radius: var(--calendar-cell-radius);
284
+ cursor: pointer;
285
+ transition:
286
+ background-color var(--transition-fast),
287
+ color var(--transition-fast),
288
+ border-color var(--transition-fast),
289
+ box-shadow var(--transition-fast);
290
+
291
+ &:hover:not(:disabled) {
292
+ background: var(--color-muted);
293
+ color: var(--color-foreground);
294
+ }
295
+
296
+ &:focus-visible {
297
+ outline: none;
298
+ border-color: var(--color-ring);
299
+ box-shadow: var(--shadow-ring);
300
+ z-index: 1;
301
+ }
302
+
303
+ &:disabled {
304
+ cursor: not-allowed;
305
+ }
306
+ }
307
+
308
+ .hidden {
309
+ visibility: hidden;
310
+ }
311
+