@apliteni/apliteni-ui 0.77.0 → 0.79.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.
@@ -0,0 +1,87 @@
1
+ # File drop
2
+
3
+ ## Spend one row at rest
4
+
5
+ <!-- rule: one-row -->
6
+
7
+ **Rule:** At rest, a file drop is one row: the button that opens the picker and the accepted types beside it.
8
+
9
+ **Why:** A tall box holds the page's best space open for a file that arrives once a month.
10
+
11
+ **Do:** Put an Upload button and “PDF or CSV, up to 10 MB” on one line above the list they feed.
12
+
13
+ **Don't:** Draw a tall dashed box with a glyph, an invitation and a button stacked inside it.
14
+
15
+ **Except:** A page whose whole purpose is receiving one file may use the field-sized picker, which carries a label of its own.
16
+
17
+ ## Paint the target only while a file is over it
18
+
19
+ <!-- rule: on-drag -->
20
+
21
+ **Rule:** Show the drop target while a file is over the region that accepts it, and nowhere else.
22
+
23
+ **Why:** A target drawn before there is a file to catch covers content the reader still needs.
24
+
25
+ **Do:** Cover the panel that accepts the file the moment the file crosses it.
26
+
27
+ **Don't:** Cover the whole window when one panel accepts the file.
28
+
29
+ ## Keep the file in the row it arrived in
30
+
31
+ <!-- rule: in-the-row -->
32
+
33
+ **Rule:** After the drop, the file's name, size and progress take the place the button was in, with remove on the name's line.
34
+
35
+ **Why:** A row that changes in place keeps the page still while the file uploads.
36
+
37
+ **Do:** Give the name a line with remove at its end, put the size under it, and run the progress track the full width below both.
38
+
39
+ **Don't:** Push the list down with a progress card, or open a dialog over the page.
40
+
41
+ ## Fail in place
42
+
43
+ <!-- rule: failure -->
44
+
45
+ **Rule:** A refused file keeps its place, says what it did wrong under its name, and offers Retry beside Remove.
46
+
47
+ **Why:** A row that drops the file sends the reader back to choosing one. The reason is the one string here that has to be read in full, so it gets a line rather than a share of one.
48
+
49
+ **Do:** Keep the name on its line and say “Larger than 10 MB” under it, with Retry.
50
+
51
+ **Don't:** Replace the row with “Upload failed”, losing both the file and the retry.
52
+
53
+ ## Offer a button, not only a drag
54
+
55
+ <!-- rule: button-path -->
56
+
57
+ **Rule:** Give the reader a button that opens the picker, and let dragging be the shortcut.
58
+
59
+ **Why:** A keyboard cannot drag a file.
60
+
61
+ **Do:** Make the Upload button the control, reachable by keyboard and showing the focus ring.
62
+
63
+ **Don't:** Write “Drag a file here” with nothing to press.
64
+
65
+ ## State the limits once
66
+
67
+ <!-- rule: limits-once -->
68
+
69
+ **Rule:** Name the accepted types and the size limit once, beside the button, in the product's own words.
70
+
71
+ **Why:** Repeated limits teach the reader to skip them.
72
+
73
+ **Do:** Write “PDF or CSV, up to 10 MB” beside the button, and let a failure say what this file did wrong.
74
+
75
+ **Don't:** Repeat the limits under the button, in a tooltip and again in the error.
76
+
77
+ ## Choose the row, the region or the dialog
78
+
79
+ <!-- rule: which-shape -->
80
+
81
+ **Rule:** Use a row where the file joins a list on the page, a region when one panel owns the file, and a dialog only when the upload needs answers of its own.
82
+
83
+ **Why:** A dialog that asks nothing is one more thing to dismiss. The rule picks between three containers, so a drawing would show one of them and not the choice.
84
+
85
+ **Do:** Take a statement into the row under the statements it joins, and a replacement logo into the panel that shows it.
86
+
87
+ **Don't:** Open a dialog for one file that the row behind it could have taken.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.77.0",
3
+ "version": "0.79.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -493,6 +493,34 @@ places a decorative kit glyph with `icon` — the same glyph names `Button` and
493
493
  `onFileChange`. Use `accept` and `hint` to describe allowed file types and size.
494
494
  Your application must validate files before upload.
495
495
 
496
+ ## File drop
497
+
498
+ `FileDrop` is the compact drop: one row with the button that opens the picker and
499
+ the accepted types beside it, and a drop target painted only while a file is over
500
+ the region. Use it where a file joins a list on the page; use `FileField` where a
501
+ labelled, field-sized picker belongs. The guideline page states when to use a row,
502
+ a region or a dialog.
503
+
504
+ ```tsx
505
+ <FileDrop note="PDF or CSV, up to 10 MB" accept=".pdf,.csv"
506
+ file={upload} onFile={send} onRemove={clear} onRetry={send} />
507
+ ```
508
+
509
+ Your application owns the upload and supplies the `file` it is holding: the
510
+ name, the `size` already written for a reader, `progress` while it uploads, and
511
+ the `error` when it failed. A file with no `status` is uploading, so a file
512
+ handed over before the request starts never reads as uploaded. A status the row
513
+ draws shows a mark and a word; where you give `progress` the track says it
514
+ instead, and the word goes to the track's accessible name. The file reads as a
515
+ stack: the name on top with the actions at its end, the size — or the failure
516
+ message — under it, and the track the full width below. No tier takes a second
517
+ line. The name truncates its stem and keeps its extension, with the whole name in
518
+ a `title`, and nothing is hidden or moved at any width. `Remove` is the icon-only
519
+ `x`, named by `removeLabel`; `Retry` keeps its word. Both appear only when you
520
+ handle them. Children render above the row, inside the region the target covers;
521
+ pass `dragging` to drive that target from a parent. `accept` filters the system
522
+ picker only — validate type and size yourself.
523
+
496
524
  Use `Field` when an existing labelled control needs the kit frame. Spread its
497
525
  render-prop attributes onto one labelable control, and apply the existing control
498
526
  class and invalid styling as appropriate:
@@ -672,3 +700,89 @@ the accent from a checked box.
672
700
  <Checkbox label="Full access" type="radio" name="scope" value="full" />
673
701
  <Switch label="Email notifications" checked={email} onChange={event => setEmail(event.currentTarget.checked)} />
674
702
  ```
703
+
704
+ ## DatePicker
705
+
706
+ `DatePicker` picks one month (`mode="month"`, the default), a range of months
707
+ (`mode="range"`), one date (`mode="day"`) or a range of dates (`mode="day-range"`). It
708
+ uses the dropdown's own trigger and panel, so it sits beside a `Dropdown` as the same
709
+ control. Periods are ISO strings in the mode's grain: `'2026-08'` for months,
710
+ `'2026-08-14'` for dates. Pass `value`/`onChange` for the single-period modes and
711
+ `range`/`onRangeChange` for the two range modes, or `defaultValue`/`defaultRange` to
712
+ leave the state to the component. Import both the kit CSS and the React CSS.
713
+
714
+ ```tsx
715
+ <DatePicker value={month} onChange={setMonth}
716
+ min="2025-01" max="2026-12" disabledPeriods={['2026-07']} />
717
+
718
+ <DatePicker mode="range" range={span} onRangeChange={setSpan}
719
+ presets={[{ label: 'This year', range: { start: '2026-01', end: '2026-12' } }]} />
720
+
721
+ <DatePicker mode="day-range" range={span} onRangeChange={setSpan}
722
+ presets={[{ label: 'This week', range: { start: '2026-09-14', end: '2026-09-20' } }]} />
723
+ ```
724
+
725
+ **The trigger shows the value and nothing else.** There is no field name in front of it:
726
+ "Period: Apr 2026 – Aug 2026" says "period" twice, and the picker has no word to add that
727
+ the value does not already carry. Where the surrounding screen leaves the control
728
+ unexplained, name it with `ariaLabel`, which names the trigger and the panel without
729
+ drawing anything.
730
+
731
+ **The grid has one tab stop.** The arrows move one period and one row, Home and End go to
732
+ the ends of the row — they never leave the month, so the blank slots a day grid pads its
733
+ first and last weeks with are not somewhere they can take you — Page Up and Page Down
734
+ move a year in the month modes and a month in day mode, Enter and Space pick, and Escape
735
+ closes and returns focus to the trigger. A move past the edge of the shown year or month
736
+ turns the page and keeps the reader on the period they moved to. Each cell's accessible
737
+ name says what the cell is: `selected` for the pick in every mode, plus `range start`,
738
+ `range end` and `in range` in range mode, and `this month` or `today`.
739
+
740
+ **Bounds hold against your code as well as the reader's.** `min`, `max` and
741
+ `disabledPeriods` are `string`, so a period in the other grain is converted rather than
742
+ dropped: in day mode `min="2026-09"` is 1 September and `max="2026-09"` is the 30th, and
743
+ `disabledPeriods={['2026-09']}` blocks the whole month; in month mode a date means the
744
+ month it falls in. Blocked cells get `aria-disabled` and refuse the press while staying
745
+ focusable. A page step with nowhere to go is disabled.
746
+
747
+ **A range mode takes a start, then an end**, staying open in between; a second pick above
748
+ or below the first always reads as the same range, so the ends swap rather than
749
+ restarting. `range` and `day-range` differ only in grain — a range may run past the page
750
+ it started on, and the span is painted on both pages.
751
+ `onRangeChange` fires on each end, so `{ start, end: null }` reaches you too. `presets`
752
+ set both ends at once and are held to the same bounds the grid is: a preset that overruns
753
+ them is clamped, one with no overlap at all is disabled, and one whose end lands on a
754
+ blocked period is disabled too rather than quietly moved inwards. A blocked period between
755
+ the two ends is still inside the range you are handed — `{ start, end }` is a pair and
756
+ spans it — and says so in its accessible name; only the tint is withheld, because the
757
+ disabled ink over it cannot be read. To exclude a period, split the range and show two
758
+ pickers.
759
+
760
+ **Blocked beats every other state in the paint, and the label is struck through.** Block
761
+ the period your own `value` names and the cell goes bare, keeping its place by weight
762
+ rather than wearing the accent fill under disabled ink; the strike is what says
763
+ unavailable before the pointer is anywhere near it. A cell under the pointer takes the
764
+ kit's neutral row hover, so the accent stays on the pick and the span. Its name still
765
+ says `selected` and the gridcell still carries `aria-selected`: it is still your value,
766
+ it just cannot be pressed.
767
+
768
+ **The period you are in now wears a ring** — hollow for the period you are in, filled for
769
+ the one you chose — and the cell's own name says "this month" or "today". Nothing is drawn
770
+ under the grid to explain it: a key is a second place to read, and these marks are the
771
+ ones a calendar has always drawn. There is no slot for a consumer's own note on a period
772
+ either; a 5px dot can only be read against a key, and a note about a period belongs on the
773
+ surface that shows that period's numbers. The month grid draws its twelve names at one
774
+ length — "Sep", not en-GB's "Sept" — so no cell reads as emphasised; a locale that numbers
775
+ its months keeps its own names.
776
+
777
+ **Below 560px the panel is a bottom `Drawer`**, so the sheet has the kit's scrim, close
778
+ control and focus trap and the page behind it is inert. Pass `sheet` to force that at any
779
+ width — a host that already knows it is on a phone, or one rendering where no viewport
780
+ can be read, should, because the picker reads the viewport with `matchMedia` and starts
781
+ as the popover until it has.
782
+
783
+ Pass `today` (a `'YYYY-MM-DD'` date) to fix what the grid calls today — stories and tests
784
+ use it to stay the same whenever they run. `locale` names the months and weekdays,
785
+ `weekStartsOn` sets the first column in day mode, and `align="end"` hangs the panel off
786
+ the trigger's trailing edge. Without `ariaLabel` the trigger's own text is its accessible
787
+ name, so a control reading "17 September 2026" is heard as that rather than as its
788
+ placeholder. The popover stays mounted while closed, and is `inert` while it is.
@@ -31,14 +31,15 @@
31
31
  --tap-min: 44px;
32
32
  --tap-aa: 24px;
33
33
  --tap-gap: var(--space-5, 20px);
34
+ --tap-gap-bordered: calc(var(--tap-min) - var(--tap-aa) + 2px);
34
35
  --tap-clear-x: 0px;
35
36
  --tap-clear-y: 0px;
36
37
  }
37
38
  @media (max-width: 560px) and (pointer: coarse) {
38
- :where(.ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social) {
39
+ :where(.ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-datepicker__opt) {
39
40
  position: relative;
40
41
  }
41
- :where(.ui-btn, .ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-switch)::after {
42
+ :where(.ui-btn, .ui-seg button, .ui-tabs__tab, .ui-dropdown__trigger, .ui-toast__action, .ui-drawer__close, .ui-snippet__copy, .ui-footer__social, .ui-switch, .ui-datepicker__opt)::after {
42
43
  content: "";
43
44
  position: absolute;
44
45
  left: 50%;
@@ -82,6 +83,14 @@
82
83
  .ui-table__act .ui-btn + .ui-btn {
83
84
  margin-inline-start: var(--tap-gap) !important;
84
85
  }
86
+ .ui-drop__row {
87
+ --tap-clear-y: var(--tap-gap);
88
+ }
89
+ .ui-drop__actions {
90
+ gap: var(--tap-gap) !important;
91
+ --tap-clear-x: var(--tap-gap);
92
+ --tap-clear-y: var(--tap-gap);
93
+ }
85
94
  .ui-table__act {
86
95
  --tap-clear-x: var(--tap-gap);
87
96
  --tap-clear-y: 0px;
@@ -101,6 +110,19 @@
101
110
  row-gap: var(--tap-gap) !important;
102
111
  --tap-clear-y: var(--tap-gap);
103
112
  }
113
+ .ui-datepicker__grid {
114
+ row-gap: var(--tap-gap) !important;
115
+ --tap-clear-x: var(--space-1, 4px);
116
+ --tap-clear-y: var(--tap-gap);
117
+ }
118
+ .ui-datepicker__head {
119
+ --tap-clear-x: var(--tap-gap-bordered);
120
+ --tap-clear-y: var(--tap-gap-bordered);
121
+ }
122
+ .ui-datepicker__presets {
123
+ --tap-clear-x: var(--space-1, 4px);
124
+ --tap-clear-y: var(--tap-gap-bordered);
125
+ }
104
126
  .ui-footer__socials,
105
127
  .ui-drawer__header {
106
128
  gap: var(--tap-gap) !important;
@@ -276,6 +298,168 @@
276
298
  }
277
299
  }
278
300
 
301
+ /* src/DatePicker.css */
302
+ .ui-datepicker__panel {
303
+ --ui-dropdown-pad: var(--space-3);
304
+ width: max-content;
305
+ min-width: 0;
306
+ max-width: 92vw;
307
+ }
308
+ .ui-datepicker__body {
309
+ display: flex;
310
+ align-items: flex-start;
311
+ gap: var(--space-4);
312
+ }
313
+ .ui-datepicker__calendar {
314
+ display: grid;
315
+ gap: var(--space-3);
316
+ }
317
+ .ui-datepicker__head {
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: space-between;
321
+ gap: var(--space-2);
322
+ }
323
+ .ui-datepicker__caption {
324
+ font-size: var(--text-sm);
325
+ font-weight: var(--weight-semibold);
326
+ color: var(--strong);
327
+ }
328
+ .ui-datepicker__grid {
329
+ display: grid;
330
+ gap: var(--space-1);
331
+ }
332
+ .ui-datepicker__row {
333
+ display: grid;
334
+ grid-template-columns: repeat(3, minmax(0, 1fr));
335
+ gap: var(--space-1);
336
+ }
337
+ .ui-datepicker__grid.is-days .ui-datepicker__row {
338
+ grid-template-columns: repeat(7, minmax(0, 1fr));
339
+ }
340
+ .ui-datepicker__weekday {
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ font-size: var(--text-xs);
345
+ font-weight: var(--weight-semibold);
346
+ color: var(--text);
347
+ }
348
+ .ui-datepicker__cell {
349
+ display: flex;
350
+ }
351
+ .ui-datepicker__opt {
352
+ position: relative;
353
+ flex: 1;
354
+ display: inline-flex;
355
+ align-items: center;
356
+ justify-content: center;
357
+ min-width: 34px;
358
+ min-height: 34px;
359
+ padding: var(--space-2);
360
+ font-size: var(--text-sm);
361
+ font-weight: var(--weight-medium);
362
+ font-variant-numeric: tabular-nums;
363
+ color: var(--text);
364
+ background: transparent;
365
+ border: 1px solid transparent;
366
+ border-radius: var(--radius-xs);
367
+ cursor: pointer;
368
+ transition:
369
+ background var(--dur-fast) var(--ease),
370
+ border-color var(--dur-fast) var(--ease),
371
+ color var(--dur-fast) var(--ease);
372
+ }
373
+ .ui-datepicker__grid.is-days .ui-datepicker__opt {
374
+ min-width: 30px;
375
+ }
376
+ .ui-datepicker__opt:hover {
377
+ outline: 1px solid var(--control-edge);
378
+ outline-offset: -1px;
379
+ background: var(--surface);
380
+ color: var(--strong);
381
+ }
382
+ .ui-datepicker__opt:focus-visible {
383
+ outline: 2px solid transparent;
384
+ }
385
+ .ui-datepicker__opt.is-inside {
386
+ background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated));
387
+ }
388
+ .ui-datepicker__opt.is-selected {
389
+ background: var(--accent-strong);
390
+ border-color: var(--accent-strong);
391
+ color: var(--accent-contrast);
392
+ }
393
+ .ui-datepicker__opt.is-selected:hover {
394
+ outline-color: transparent;
395
+ background: var(--accent-strong);
396
+ color: var(--accent-contrast);
397
+ }
398
+ .ui-datepicker__opt.is-today {
399
+ border-color: var(--accent);
400
+ }
401
+ .ui-datepicker__opt.is-today:hover {
402
+ outline-color: transparent;
403
+ }
404
+ .ui-datepicker__opt.is-selected.is-today {
405
+ border-color: var(--accent-contrast);
406
+ }
407
+ .ui-datepicker__opt.is-disabled {
408
+ color: var(--disabled-ink-bare);
409
+ cursor: not-allowed;
410
+ }
411
+ .ui-datepicker__opt.is-disabled .ui-datepicker__num {
412
+ text-decoration: line-through;
413
+ }
414
+ .ui-datepicker__opt.is-disabled:hover {
415
+ outline-color: transparent;
416
+ background: transparent;
417
+ border-color: transparent;
418
+ color: var(--disabled-ink-bare);
419
+ }
420
+ .ui-datepicker__opt.is-disabled.is-selected,
421
+ .ui-datepicker__opt.is-disabled.is-selected:hover,
422
+ .ui-datepicker__opt.is-disabled.is-inside,
423
+ .ui-datepicker__opt.is-disabled.is-inside:hover {
424
+ background: transparent;
425
+ border-color: transparent;
426
+ color: var(--disabled-ink-bare);
427
+ }
428
+ .ui-datepicker__opt.is-disabled.is-selected {
429
+ font-weight: var(--weight-bold);
430
+ }
431
+ .ui-datepicker__opt.is-disabled.is-today,
432
+ .ui-datepicker__opt.is-disabled.is-today:hover {
433
+ border-color: var(--accent);
434
+ }
435
+ .ui-datepicker__presets {
436
+ display: grid;
437
+ align-content: start;
438
+ gap: var(--space-1);
439
+ padding: 0 var(--space-3) 0 0;
440
+ border-inline-end: 1px solid var(--border);
441
+ }
442
+ .ui-datepicker__preset {
443
+ justify-content: flex-start;
444
+ }
445
+ .ui-datepicker__body.is-sheet {
446
+ flex-direction: column;
447
+ align-items: stretch;
448
+ }
449
+ .ui-datepicker__body.is-sheet .ui-datepicker__presets {
450
+ grid-auto-flow: column;
451
+ grid-auto-columns: max-content;
452
+ overflow-x: auto;
453
+ scroll-padding-inline: var(--space-1);
454
+ padding: var(--space-3) var(--space-1);
455
+ border-inline-end: 0;
456
+ border-block-end: 1px solid var(--border);
457
+ }
458
+ .ui-datepicker__body.is-sheet .ui-datepicker__presets:focus-visible {
459
+ outline: var(--ring-scroll);
460
+ outline-offset: var(--ring-scroll-offset);
461
+ }
462
+
279
463
  /* src/KeyValueList.css */
280
464
  .ui-kv {
281
465
  display: grid;
@@ -498,6 +682,129 @@
498
682
  cursor: not-allowed;
499
683
  }
500
684
 
685
+ /* ../src/styles/file-drop.css */
686
+ .ui-drop {
687
+ position: relative;
688
+ }
689
+ .ui-drop > * + .ui-drop__row {
690
+ margin-top: var(--space-3);
691
+ }
692
+ .ui-drop__row {
693
+ display: flex;
694
+ flex-wrap: wrap;
695
+ align-items: center;
696
+ min-height: var(--space-8);
697
+ gap: var(--space-2) var(--space-3);
698
+ font-size: var(--text-sm);
699
+ color: var(--text);
700
+ overflow-wrap: anywhere;
701
+ }
702
+ .ui-drop__note {
703
+ margin: 0;
704
+ }
705
+ .ui-drop__file {
706
+ display: grid;
707
+ flex: 1 1 auto;
708
+ gap: var(--space-2);
709
+ min-width: 0;
710
+ }
711
+ .ui-drop__line {
712
+ display: flex;
713
+ flex-wrap: nowrap;
714
+ align-items: center;
715
+ gap: var(--space-3);
716
+ min-width: 0;
717
+ }
718
+ .ui-drop__name {
719
+ display: flex;
720
+ flex: 1 1 auto;
721
+ min-width: var(--space-10);
722
+ color: var(--strong);
723
+ font-weight: var(--weight-medium);
724
+ }
725
+ .ui-drop__stem {
726
+ overflow: hidden;
727
+ text-overflow: ellipsis;
728
+ white-space: nowrap;
729
+ }
730
+ .ui-drop__ext {
731
+ flex: none;
732
+ }
733
+ .ui-drop__facts {
734
+ display: flex;
735
+ flex-wrap: nowrap;
736
+ align-items: center;
737
+ gap: var(--space-3);
738
+ min-width: 0;
739
+ }
740
+ .ui-drop__size {
741
+ flex: none;
742
+ white-space: nowrap;
743
+ }
744
+ .ui-drop__word {
745
+ overflow: hidden;
746
+ text-overflow: ellipsis;
747
+ white-space: nowrap;
748
+ flex: 0 1 auto;
749
+ min-width: 0;
750
+ }
751
+ .ui-drop__state {
752
+ display: inline-flex;
753
+ flex: 0 1 auto;
754
+ align-items: center;
755
+ gap: var(--space-2);
756
+ min-width: 0;
757
+ }
758
+ .ui-drop__state svg {
759
+ width: 16px;
760
+ height: 16px;
761
+ flex: none;
762
+ stroke-width: 2.4;
763
+ }
764
+ .ui-drop__error {
765
+ color: var(--pink);
766
+ }
767
+ .ui-drop__actions {
768
+ display: flex;
769
+ flex: none;
770
+ align-items: center;
771
+ gap: var(--space-2);
772
+ margin-left: auto;
773
+ }
774
+ .ui-drop__bar {
775
+ display: block;
776
+ width: 100%;
777
+ height: 6px;
778
+ border-radius: var(--radius-pill);
779
+ background: var(--surface-2);
780
+ overflow: hidden;
781
+ }
782
+ .ui-drop__bar span {
783
+ display: block;
784
+ height: 100%;
785
+ background: var(--accent);
786
+ }
787
+ .ui-drop__target {
788
+ position: absolute;
789
+ inset: 0;
790
+ display: flex;
791
+ align-items: center;
792
+ justify-content: center;
793
+ padding: var(--space-3);
794
+ border: 1px dashed var(--accent);
795
+ border-radius: var(--radius-md);
796
+ background: var(--surface);
797
+ color: var(--strong);
798
+ font-size: var(--text-sm);
799
+ text-align: center;
800
+ }
801
+ .ui-drop.is-disabled .ui-drop__row {
802
+ color: var(--disabled-ink);
803
+ }
804
+ .ui-drop.is-disabled .ui-drop__name {
805
+ color: var(--disabled-ink);
806
+ }
807
+
501
808
  /* src/Toast.css */
502
809
  .rx-toast-stack {
503
810
  position: fixed;