aplance-ui 1.1.0 → 1.2.0

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 (24) hide show
  1. package/package.json +1 -1
  2. package/registry/components/primitives/infinite-scroll/infinite-scroll-context.ts +32 -0
  3. package/registry/components/primitives/infinite-scroll/infinite-scroll.module.css +118 -0
  4. package/registry/components/primitives/infinite-scroll/infinite-scroll.tsx +386 -0
  5. package/registry/components/primitives/popover/popover.module.css +16 -21
  6. package/registry/components/primitives/slider/slider-context.ts +50 -0
  7. package/registry/components/primitives/slider/slider.module.css +300 -0
  8. package/registry/components/primitives/slider/slider.tsx +1174 -0
  9. package/registry/components/primitives/slider/types.ts +140 -0
  10. package/registry/components/primitives/slider/utils.ts +88 -0
  11. package/registry/components/ui/breadcrumbs/breadcrumbs.module.css +144 -0
  12. package/registry/components/ui/breadcrumbs/breadcrumbs.tsx +236 -0
  13. package/registry/components/ui/button/button.module.css +3 -9
  14. package/registry/components/ui/chart/chart.module.css +86 -70
  15. package/registry/components/ui/chart/chart.tsx +117 -31
  16. package/registry/components/ui/input-fields/common/input-field-common.module.css +39 -65
  17. package/registry/components/ui/input-fields/date-picker/calendar.module.css +2 -6
  18. package/registry/components/ui/input-fields/date-picker/date-picker.module.css +47 -3
  19. package/registry/components/ui/input-fields/date-picker/date-picker.tsx +12 -12
  20. package/registry/components/ui/input-fields/media-input/media-input.module.css +12 -0
  21. package/registry/components/ui/input-fields/media-input/media-input.tsx +10 -9
  22. package/registry/components/ui/input-fields/media-input/useMediaUpload.tsx +19 -5
  23. package/registry/components/ui/input-fields/media-input/utils.ts +13 -5
  24. package/registry/registry.json +64 -1
@@ -1,5 +1,4 @@
1
- /* calendar.module.css – standard absolute nav layout (shadcn pattern), clean spacing */
2
- .calendar {
1
+ .calendar {
3
2
  --calendar-cell-size: var(--spacing-11);
4
3
  --calendar-cell-radius: var(--radius-md);
5
4
  --calendar-row-gap: var(--spacing-1);
@@ -44,10 +43,7 @@
44
43
  pointer-events: auto;
45
44
  }
46
45
  }
47
-
48
-
49
-
50
- .chevron {
46
+ .chevron {
51
47
  width: var(--spacing-8);
52
48
  height: var(--spacing-8);
53
49
  }
@@ -1,4 +1,3 @@
1
- /* date-picker.module.css – clean, premium layout with no hacks */
2
1
  .popover_menu {
3
2
  max-width: calc(100vw - var(--spacing-11)) !important;
4
3
  }
@@ -6,6 +5,8 @@
6
5
  .menu_content {
7
6
  display: flex;
8
7
  flex-direction: column;
8
+ width: fit-content;
9
+ max-width: 100%;
9
10
  }
10
11
 
11
12
  .manual_panel {
@@ -17,23 +18,55 @@
17
18
  display: flex;
18
19
  flex-direction: row;
19
20
  gap: var(--spacing-4);
21
+ min-width: 0;
20
22
  }
21
23
 
22
24
  .calendar_wrap {
23
25
  display: flex;
24
26
  justify-content: center;
25
27
  padding: var(--spacing-4);
28
+ width: 100%;
29
+ min-width: 0;
30
+ max-width: 100%;
26
31
  }
27
32
 
28
33
  .range_calendars {
29
34
  display: flex;
30
35
  align-items: flex-start;
31
36
  gap: var(--spacing-4);
37
+ width: fit-content;
38
+ max-width: 100%;
32
39
  }
33
40
 
34
- @media (width <= 48rem) {
35
- .range_calendars {
41
+ @media (max-width: 48rem) {
42
+ .manual_panel,
43
+ .calendar_wrap,
44
+ .time_panel {
45
+ padding: var(--spacing-3);
46
+ }
47
+
48
+ .manual_grid {
36
49
  flex-direction: column;
50
+ gap: var(--spacing-3);
51
+ }
52
+
53
+ .calendar_wrap {
54
+ justify-content: flex-start;
55
+ overflow-x: auto;
56
+ overscroll-behavior-x: contain;
57
+ scrollbar-width: thin;
58
+ }
59
+
60
+ .range_calendars {
61
+ width: max-content;
62
+ max-width: none;
63
+ flex-shrink: 0;
64
+ gap: var(--spacing-3);
65
+ }
66
+
67
+ .range_time_grid {
68
+ grid-template-columns: minmax(0, 1fr);
69
+ gap: var(--spacing-3);
37
70
  }
38
71
  }
39
72
 
@@ -55,3 +88,14 @@
55
88
  gap: var(--spacing-4);
56
89
  }
57
90
 
91
+ @media (max-width: 36rem) {
92
+ .popover_menu {
93
+ max-width: calc(100vw - var(--spacing-4)) !important;
94
+ }
95
+ }
96
+
97
+ @media (max-width: 23rem) {
98
+ .calendar {
99
+ --calendar-cell-size: min(var(--spacing-11), calc((100vw - var(--spacing-10)) / 7));
100
+ }
101
+ }
@@ -56,10 +56,10 @@ export { Calendar } from "./calendar";
56
56
  export type { CalendarProps } from "./calendar";
57
57
  export * from "./types";
58
58
 
59
- const DEFAULT_START_PLACEHOLDER = "Start date";
60
- const DEFAULT_END_PLACEHOLDER = "End date";
61
-
62
- export const DatePicker = (props: DatePickerProps) => {
59
+ const DEFAULT_START_PLACEHOLDER = "Start date";
60
+ const DEFAULT_END_PLACEHOLDER = "End date";
61
+
62
+ export const DatePicker = (props: DatePickerProps) => {
63
63
  const {
64
64
  label,
65
65
  className,
@@ -629,9 +629,9 @@ export const DatePicker = (props: DatePickerProps) => {
629
629
  [timePickerPopover?.align, timePickerPopover?.position],
630
630
  );
631
631
 
632
- const isIndependentRangeMonths = isRange && resolvedRangeMonthCount > 1;
633
-
634
- return (
632
+ const isIndependentRangeMonths = isRange && resolvedRangeMonthCount > 1;
633
+
634
+ return (
635
635
  <div className={clsx(sCommon.container, className)} style={style}>
636
636
  {label && (
637
637
  <div
@@ -758,11 +758,11 @@ export const DatePicker = (props: DatePickerProps) => {
758
758
  </div>
759
759
  )}
760
760
 
761
- <div className={s.calendar_wrap}>
762
- {isRange ? (
763
- isIndependentRangeMonths ? (
764
- <div className={s.range_calendars}>
765
- {Array.from({ length: resolvedRangeMonthCount }, (_, index) => {
761
+ <div className={s.calendar_wrap}>
762
+ {isRange ? (
763
+ isIndependentRangeMonths ? (
764
+ <div className={s.range_calendars}>
765
+ {Array.from({ length: resolvedRangeMonthCount }, (_, index) => {
766
766
  const month =
767
767
  rangeCalendarMonths[index] ??
768
768
  addMonthOffset(
@@ -92,6 +92,7 @@
92
92
 
93
93
  .dropzone_inner {
94
94
  display: flex;
95
+ min-width: 0;
95
96
  flex-direction: column;
96
97
  align-items: center;
97
98
  justify-content: center;
@@ -107,24 +108,30 @@
107
108
 
108
109
  .dropzone_copy {
109
110
  display: flex;
111
+ max-width: 100%;
112
+ min-width: 0;
110
113
  flex-direction: column;
111
114
  gap: var(--spacing-2);
112
115
  align-items: center;
113
116
  }
114
117
 
115
118
  .dropzone_title {
119
+ max-width: 100%;
116
120
  font-family: var(--font-family-1);
117
121
  font-size: var(--font-size-sm);
118
122
  color: var(--field-content-color, var(--color-foreground));
119
123
  font-weight: 500;
120
124
  line-height: 1.3;
125
+ overflow-wrap: anywhere;
121
126
  }
122
127
 
123
128
  .dropzone_hint {
129
+ max-width: 100%;
124
130
  font-family: var(--font-family-1);
125
131
  font-size: var(--font-size-xs);
126
132
  color: var(--field-placeholder-color, var(--color-muted-foreground));
127
133
  line-height: 1.4;
134
+ overflow-wrap: anywhere;
128
135
  }
129
136
 
130
137
  /* ── Preview list ─────────────────────────────────────────────────────── */
@@ -272,18 +279,23 @@
272
279
 
273
280
  .preview_sub {
274
281
  display: inline-flex;
282
+ min-width: 0;
275
283
  align-items: center;
276
284
  gap: var(--spacing-1);
277
285
  font-family: var(--font-family-1);
278
286
  font-size: var(--font-size-xs);
279
287
  color: var(--color-muted-foreground);
280
288
  line-height: 1.3;
289
+ overflow: hidden;
290
+ text-overflow: ellipsis;
291
+ white-space: nowrap;
281
292
  }
282
293
 
283
294
  .preview_error {
284
295
  font-family: var(--font-family-1);
285
296
  font-size: var(--font-size-xs);
286
297
  color: var(--color-error-foreground);
298
+ overflow-wrap: anywhere;
287
299
  }
288
300
 
289
301
  /* Position only - visuals come from the Button component (variant + size + box layout). */
@@ -109,15 +109,12 @@ export const MediaInput = (props: MediaInputProps) => {
109
109
  const resolvedTitle =
110
110
  dropzoneTitle ??
111
111
  (multiple ? "Drop files here or browse" : "Drop a file here or browse");
112
- const resolvedHint =
113
- dropzoneHint ??
114
- [
115
- accept ? `Accepts ${accept}` : null,
116
- maxSize ? `Up to ${formatBytes(maxSize)} per file` : null,
117
- multiple && maxFiles ? `Max ${maxFiles} files` : null,
118
- ]
119
- .filter(Boolean)
120
- .join(" • ");
112
+ const hintParts = [
113
+ accept ? `Accepts ${accept}` : null,
114
+ maxSize ? `Up to ${formatBytes(maxSize)} per file` : null,
115
+ multiple && maxFiles ? `Max ${maxFiles} files` : null,
116
+ ].filter(Boolean);
117
+ const resolvedHint = dropzoneHint ?? hintParts.join(" - ");
121
118
 
122
119
  const inputProps = getInputProps({
123
120
  id: inputId,
@@ -210,6 +207,10 @@ export const MediaInput = (props: MediaInputProps) => {
210
207
  "aria-labelledby": label ? labelId : undefined,
211
208
  "aria-describedby": resolvedHint ? hintId : undefined,
212
209
  onClick: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
210
+ onDragEnter: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
211
+ onDragLeave: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
212
+ onDragOver: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
213
+ onDrop: dropzoneSelectable ? undefined : (e) => e.preventDefault(),
213
214
  });
214
215
 
215
216
  return (
@@ -96,6 +96,12 @@ export function useMediaUpload({
96
96
  filesRef.current = isControlled ? value! : internal;
97
97
  }, [isControlled, value, internal]);
98
98
 
99
+ useEffect(() => {
100
+ if (!disabled && !readOnly) return;
101
+ dragCounter.current = 0;
102
+ setIsDragging(false);
103
+ }, [disabled, readOnly]);
104
+
99
105
  // Revoke any preview URLs the hook created on unmount so blob memory is
100
106
  // freed even if the consumer forgets to clear state.
101
107
  useEffect(
@@ -130,8 +136,11 @@ export function useMediaUpload({
130
136
  existing: baseExisting,
131
137
  });
132
138
 
133
- // Single-mode replacement frees the previous preview blob immediately.
134
- if (!multiple && existing.length > 0) existing.forEach(revokePreviewUrl);
139
+ // Single-mode replacement frees the previous preview blob immediately,
140
+ // but only after a valid replacement exists.
141
+ if (!multiple && accepted.length > 0 && existing.length > 0) {
142
+ existing.forEach(revokePreviewUrl);
143
+ }
135
144
 
136
145
  const next = multiple ? [...existing, ...accepted] : accepted;
137
146
  if (accepted.length > 0) {
@@ -162,6 +171,7 @@ export function useMediaUpload({
162
171
  revokePreviewUrl(target);
163
172
  const next = filesRef.current.filter((f) => f.id !== id);
164
173
  commit(next);
174
+ setRejections([]);
165
175
  onRemove?.(target);
166
176
  },
167
177
  [commit, disabled, onRemove, readOnly],
@@ -190,9 +200,9 @@ export function useMediaUpload({
190
200
 
191
201
  const handleDragEnter = useCallback(
192
202
  (e: DragEvent) => {
193
- if (disabled || readOnly) return;
194
203
  e.preventDefault();
195
204
  e.stopPropagation();
205
+ if (disabled || readOnly) return;
196
206
  dragCounter.current += 1;
197
207
  if (e.dataTransfer?.types?.includes("Files")) setIsDragging(true);
198
208
  },
@@ -208,9 +218,9 @@ export function useMediaUpload({
208
218
 
209
219
  const handleDragOver = useCallback(
210
220
  (e: DragEvent) => {
211
- if (disabled || readOnly) return;
212
221
  e.preventDefault();
213
222
  e.stopPropagation();
223
+ if (disabled || readOnly) return;
214
224
  if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
215
225
  },
216
226
  [disabled, readOnly],
@@ -218,11 +228,11 @@ export function useMediaUpload({
218
228
 
219
229
  const handleDrop = useCallback(
220
230
  (e: DragEvent) => {
221
- if (disabled || readOnly) return;
222
231
  e.preventDefault();
223
232
  e.stopPropagation();
224
233
  dragCounter.current = 0;
225
234
  setIsDragging(false);
235
+ if (disabled || readOnly) return;
226
236
  add(e.dataTransfer?.files);
227
237
  },
228
238
  [add, disabled, readOnly],
@@ -247,18 +257,22 @@ export function useMediaUpload({
247
257
  },
248
258
  onDragEnter: (e) => {
249
259
  overrides.onDragEnter?.(e);
260
+ if (e.defaultPrevented) return;
250
261
  handleDragEnter(e);
251
262
  },
252
263
  onDragLeave: (e) => {
253
264
  overrides.onDragLeave?.(e);
265
+ if (e.defaultPrevented) return;
254
266
  handleDragLeave(e);
255
267
  },
256
268
  onDragOver: (e) => {
257
269
  overrides.onDragOver?.(e);
270
+ if (e.defaultPrevented) return;
258
271
  handleDragOver(e);
259
272
  },
260
273
  onDrop: (e) => {
261
274
  overrides.onDrop?.(e);
275
+ if (e.defaultPrevented) return;
262
276
  handleDrop(e);
263
277
  },
264
278
  }),
@@ -138,9 +138,17 @@ export const validateIncomingFiles = (
138
138
  const rejected: MediaRejection[] = [];
139
139
 
140
140
  const seen = new Set(options.existing.map((f) => fileFingerprint(f.file)));
141
+ const maxFiles =
142
+ typeof options.maxFiles === "number" && Number.isFinite(options.maxFiles)
143
+ ? Math.max(0, Math.floor(options.maxFiles))
144
+ : undefined;
145
+ const maxSize =
146
+ typeof options.maxSize === "number" && Number.isFinite(options.maxSize)
147
+ ? Math.max(0, options.maxSize)
148
+ : undefined;
141
149
  const remainingCapacity = options.multiple
142
- ? options.maxFiles
143
- ? Math.max(0, options.maxFiles - options.existing.length)
150
+ ? maxFiles !== undefined
151
+ ? Math.max(0, maxFiles - options.existing.length)
144
152
  : Number.POSITIVE_INFINITY
145
153
  : 1;
146
154
 
@@ -153,11 +161,11 @@ export const validateIncomingFiles = (
153
161
  });
154
162
  continue;
155
163
  }
156
- if (options.maxSize && file.size > options.maxSize) {
164
+ if (maxSize !== undefined && file.size > maxSize) {
157
165
  rejected.push({
158
166
  file,
159
167
  code: "file-too-large",
160
- message: rejectionMessage("file-too-large", { maxSize: options.maxSize }),
168
+ message: rejectionMessage("file-too-large", { maxSize }),
161
169
  });
162
170
  continue;
163
171
  }
@@ -174,7 +182,7 @@ export const validateIncomingFiles = (
174
182
  rejected.push({
175
183
  file,
176
184
  code: "too-many-files",
177
- message: rejectionMessage("too-many-files", { maxFiles: options.maxFiles }),
185
+ message: rejectionMessage("too-many-files", { maxFiles }),
178
186
  });
179
187
  continue;
180
188
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://ui.aplance.com/registry.schema.json",
3
3
  "version": 4,
4
- "generated": "2026-05-11T11:35:17.188Z",
4
+ "generated": "2026-05-15T11:50:32.790Z",
5
5
  "units": [
6
6
  {
7
7
  "name": "accordion",
@@ -53,6 +53,24 @@
53
53
  "clsx"
54
54
  ]
55
55
  },
56
+ {
57
+ "name": "breadcrumbs",
58
+ "kind": "component",
59
+ "group": "ui",
60
+ "files": [
61
+ "components/ui/breadcrumbs/breadcrumbs.module.css",
62
+ "components/ui/breadcrumbs/breadcrumbs.tsx"
63
+ ],
64
+ "clientFiles": [
65
+ "components/ui/breadcrumbs/breadcrumbs.tsx"
66
+ ],
67
+ "deps": [],
68
+ "externalDeps": [
69
+ "@tabler/icons-react",
70
+ "clsx",
71
+ "react"
72
+ ]
73
+ },
56
74
  {
57
75
  "name": "button",
58
76
  "kind": "component",
@@ -353,6 +371,27 @@
353
371
  "react"
354
372
  ]
355
373
  },
374
+ {
375
+ "name": "infinite-scroll",
376
+ "kind": "component",
377
+ "group": "primitives",
378
+ "files": [
379
+ "components/primitives/infinite-scroll/infinite-scroll-context.ts",
380
+ "components/primitives/infinite-scroll/infinite-scroll.module.css",
381
+ "components/primitives/infinite-scroll/infinite-scroll.tsx"
382
+ ],
383
+ "clientFiles": [
384
+ "components/primitives/infinite-scroll/infinite-scroll-context.ts",
385
+ "components/primitives/infinite-scroll/infinite-scroll.tsx"
386
+ ],
387
+ "deps": [
388
+ "useResizeObserver"
389
+ ],
390
+ "externalDeps": [
391
+ "clsx",
392
+ "react"
393
+ ]
394
+ },
356
395
  {
357
396
  "name": "input",
358
397
  "kind": "component",
@@ -565,6 +604,30 @@
565
604
  "react"
566
605
  ]
567
606
  },
607
+ {
608
+ "name": "slider",
609
+ "kind": "component",
610
+ "group": "primitives",
611
+ "files": [
612
+ "components/primitives/slider/slider-context.ts",
613
+ "components/primitives/slider/slider.module.css",
614
+ "components/primitives/slider/slider.tsx",
615
+ "components/primitives/slider/types.ts",
616
+ "components/primitives/slider/utils.ts"
617
+ ],
618
+ "clientFiles": [
619
+ "components/primitives/slider/slider-context.ts",
620
+ "components/primitives/slider/slider.tsx"
621
+ ],
622
+ "deps": [
623
+ "useResizeObserver"
624
+ ],
625
+ "externalDeps": [
626
+ "@tabler/icons-react",
627
+ "clsx",
628
+ "react"
629
+ ]
630
+ },
568
631
  {
569
632
  "name": "stepper",
570
633
  "kind": "component",