@digiko-npm/designsystem 0.4.0 → 0.5.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.
@@ -1620,6 +1620,24 @@ hr {
1620
1620
  .ds-modal--md .ds-modal__content { max-width: var(--ds-container-md); }
1621
1621
  .ds-modal--lg .ds-modal__content { max-width: var(--ds-container-lg); }
1622
1622
 
1623
+ /* Fullscreen on mobile */
1624
+ @media (max-width: 1023px) {
1625
+ .ds-modal--fullscreen-mobile .ds-modal__content {
1626
+ max-width: none;
1627
+ max-height: none;
1628
+ width: 100%;
1629
+ height: 100dvh;
1630
+ margin: 0;
1631
+ border-radius: 0;
1632
+ border: none;
1633
+ box-shadow: none;
1634
+ }
1635
+
1636
+ .ds-modal--fullscreen-mobile {
1637
+ padding: 0;
1638
+ }
1639
+ }
1640
+
1623
1641
  /* ==========================================================================
1624
1642
  Component: Toast
1625
1643
  ========================================================================== */
@@ -5328,173 +5346,1011 @@ tr:hover .ds-sortable__handle,
5328
5346
  color: var(--ds-color-text-tertiary);
5329
5347
  }
5330
5348
 
5331
-
5332
5349
  /* ==========================================================================
5333
- Utilities: Layout
5334
- Container with clamp padding, generous responsive grid.
5350
+ Component: Search
5351
+ Inline search bar with dropdown results, keyboard navigation, and
5352
+ responsive mobile expansion. Distinct from ds-command (modal overlay).
5353
+
5354
+ Usage:
5355
+ <div class="ds-search">
5356
+ <span class="ds-search__icon">...</span>
5357
+ <input class="ds-search__input" placeholder="Search..." />
5358
+ <kbd class="ds-search__shortcut">Ctrl+K</kbd>
5359
+ <button class="ds-search__clear">...</button>
5360
+ </div>
5361
+ <div class="ds-search__dropdown">
5362
+ <div class="ds-search__group">
5363
+ <span class="ds-search__group-label">Pages</span>
5364
+ <button class="ds-search__result ds-search__result--active">
5365
+ <span class="ds-search__result-icon">...</span>
5366
+ <div class="ds-search__result-content">
5367
+ <span class="ds-search__result-title">Page name</span>
5368
+ <span class="ds-search__result-meta">Database</span>
5369
+ </div>
5370
+ </button>
5371
+ </div>
5372
+ <div class="ds-search__empty">No results found.</div>
5373
+ </div>
5374
+
5375
+ Modifiers:
5376
+ .ds-search--mobile-expanded — Fullscreen bar on mobile
5377
+ .ds-search__dropdown--mobile — Fullscreen dropdown on mobile
5378
+
5379
+ Mobile:
5380
+ .ds-search-mobile-trigger — Icon button shown on mobile (hidden desktop)
5381
+ .ds-search__close — Close button inside expanded mobile bar
5335
5382
  ========================================================================== */
5336
5383
 
5337
- /* --- Container --- */
5338
- .ds-container {
5384
+ .ds-search {
5385
+ position: relative;
5386
+ display: flex;
5387
+ align-items: center;
5388
+ gap: var(--ds-space-1);
5339
5389
  width: 100%;
5340
- max-width: var(--ds-container-max);
5341
- margin-inline: auto;
5342
- padding-inline: var(--ds-container-padding);
5390
+ padding: var(--ds-space-2) var(--ds-space-2-5);
5391
+ background: var(--ds-color-surface);
5392
+ border: 1px solid var(--ds-color-border);
5393
+ border-radius: var(--ds-radius-lg);
5394
+ transition:
5395
+ border-color var(--ds-duration-fast) var(--ds-ease-default),
5396
+ box-shadow var(--ds-duration-fast) var(--ds-ease-default);
5343
5397
  }
5344
5398
 
5345
- /* --- Section (generous vertical rhythm) --- */
5346
- .ds-section {
5347
- padding-block: var(--ds-section-padding);
5399
+ .ds-search:focus-within {
5400
+ border-color: var(--ds-color-border-active);
5401
+ box-shadow: 0 0 0 var(--ds-ring-width) var(--ds-ring-color);
5348
5402
  }
5349
5403
 
5350
- /* --- Flex --- */
5351
- .ds-flex { display: flex; }
5352
- .ds-inline-flex { display: inline-flex; }
5353
- .ds-flex-col { flex-direction: column; }
5354
- .ds-flex-row { flex-direction: row; }
5355
- .ds-flex-wrap { flex-wrap: wrap; }
5356
- .ds-flex-1 { flex: 1 1 0%; }
5357
- .ds-flex-auto { flex: 1 1 auto; }
5358
- .ds-flex-none { flex: none; }
5359
- .ds-shrink-0 { flex-shrink: 0; }
5404
+ /* Icon */
5405
+ .ds-search__icon {
5406
+ color: var(--ds-color-text-tertiary);
5407
+ flex-shrink: 0;
5408
+ }
5360
5409
 
5361
- /* --- Alignment --- */
5362
- .ds-items-start { align-items: flex-start; }
5363
- .ds-items-center { align-items: center; }
5364
- .ds-items-end { align-items: flex-end; }
5365
- .ds-items-stretch { align-items: stretch; }
5366
- .ds-items-baseline { align-items: baseline; }
5410
+ /* Input */
5411
+ .ds-search__input {
5412
+ flex: 1;
5413
+ min-width: 0;
5414
+ border: none;
5415
+ outline: none;
5416
+ background: transparent;
5417
+ color: var(--ds-color-text);
5418
+ font-size: var(--ds-text-base);
5419
+ font-family: inherit;
5420
+ line-height: 1.25;
5421
+ }
5367
5422
 
5368
- .ds-justify-start { justify-content: flex-start; }
5369
- .ds-justify-center { justify-content: center; }
5370
- .ds-justify-end { justify-content: flex-end; }
5371
- .ds-justify-between { justify-content: space-between; }
5372
- .ds-justify-around { justify-content: space-around; }
5373
- .ds-justify-evenly { justify-content: space-evenly; }
5423
+ .ds-search__input::placeholder {
5424
+ color: var(--ds-color-text-tertiary);
5425
+ }
5374
5426
 
5375
- .ds-self-start { align-self: flex-start; }
5376
- .ds-self-center { align-self: center; }
5377
- .ds-self-end { align-self: flex-end; }
5427
+ /* Keyboard shortcut hint */
5428
+ .ds-search__shortcut {
5429
+ flex-shrink: 0;
5430
+ padding: 0.125rem 0.375rem;
5431
+ font-size: var(--ds-text-2xs);
5432
+ font-family: inherit;
5433
+ line-height: 1;
5434
+ color: var(--ds-color-text-tertiary);
5435
+ background: var(--ds-color-bg-elevated);
5436
+ border: 1px solid var(--ds-color-border);
5437
+ border-radius: var(--ds-radius-sm);
5438
+ pointer-events: none;
5439
+ user-select: none;
5440
+ }
5378
5441
 
5379
- /* --- Grid --- */
5380
- .ds-grid {
5381
- display: grid;
5382
- gap: var(--ds-space-6);
5442
+ /* Clear button */
5443
+ .ds-search__clear {
5444
+ display: flex;
5445
+ align-items: center;
5446
+ justify-content: center;
5447
+ flex-shrink: 0;
5448
+ width: 1.25rem;
5449
+ height: 1.25rem;
5450
+ border: none;
5451
+ border-radius: var(--ds-radius-sm);
5452
+ background: transparent;
5453
+ color: var(--ds-color-text-tertiary);
5454
+ cursor: pointer;
5455
+ transition:
5456
+ color var(--ds-duration-fast) var(--ds-ease-default),
5457
+ background-color var(--ds-duration-fast) var(--ds-ease-default);
5383
5458
  }
5384
5459
 
5385
- .ds-grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
5386
- .ds-grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
5387
- .ds-grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
5388
- .ds-grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
5460
+ .ds-search__clear:hover {
5461
+ background: var(--ds-color-surface-hover);
5462
+ color: var(--ds-color-text-secondary);
5463
+ }
5389
5464
 
5390
- /* --- Column Span --- */
5391
- .ds-col-span-1 { grid-column: span 1 / span 1; }
5392
- .ds-col-span-2 { grid-column: span 2 / span 2; }
5393
- .ds-col-span-3 { grid-column: span 3 / span 3; }
5394
- .ds-col-span-4 { grid-column: span 4 / span 4; }
5395
- .ds-col-span-full { grid-column: 1 / -1; }
5465
+ /* Dropdown results panel */
5466
+ .ds-search__dropdown {
5467
+ position: fixed;
5468
+ max-height: min(22rem, calc(100dvh - 5rem));
5469
+ overflow-y: auto;
5470
+ background: var(--ds-color-bg-elevated);
5471
+ border: 1px solid var(--ds-color-border);
5472
+ border-radius: var(--ds-radius-lg);
5473
+ box-shadow: var(--ds-shadow-lg);
5474
+ z-index: var(--ds-z-tooltip);
5475
+ padding: var(--ds-space-1) 0;
5476
+ }
5396
5477
 
5397
- @media (min-width: 640px) {
5398
- .ds-sm\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
5399
- .ds-sm\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
5478
+ /* Result groups */
5479
+ .ds-search__group {
5480
+ padding: var(--ds-space-1) 0;
5400
5481
  }
5401
5482
 
5402
- @media (min-width: 768px) {
5403
- .ds-md\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
5404
- .ds-md\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
5405
- .ds-md\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
5406
- .ds-md\:col-span-1 { grid-column: span 1 / span 1; }
5407
- .ds-md\:col-span-2 { grid-column: span 2 / span 2; }
5408
- .ds-md\:col-span-3 { grid-column: span 3 / span 3; }
5409
- .ds-md\:flex-row { flex-direction: row; }
5483
+ .ds-search__group + .ds-search__group {
5484
+ border-top: 1px solid var(--ds-color-border);
5410
5485
  }
5411
5486
 
5412
- @media (min-width: 1024px) {
5413
- .ds-lg\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
5414
- .ds-lg\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
5415
- .ds-lg\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
5416
- .ds-lg\:col-span-1 { grid-column: span 1 / span 1; }
5417
- .ds-lg\:col-span-2 { grid-column: span 2 / span 2; }
5418
- .ds-lg\:col-span-3 { grid-column: span 3 / span 3; }
5487
+ .ds-search__group-label {
5488
+ display: block;
5489
+ padding: var(--ds-space-1) var(--ds-space-3);
5490
+ font-size: var(--ds-text-2xs);
5491
+ font-weight: var(--ds-weight-medium);
5492
+ color: var(--ds-color-text-tertiary);
5493
+ text-transform: uppercase;
5494
+ letter-spacing: 0.04em;
5419
5495
  }
5420
5496
 
5421
- /* --- Gap --- */
5422
- .ds-gap-0 { gap: var(--ds-space-0); }
5423
- .ds-gap-1 { gap: var(--ds-space-1); }
5424
- .ds-gap-2 { gap: var(--ds-space-2); }
5425
- .ds-gap-3 { gap: var(--ds-space-3); }
5426
- .ds-gap-4 { gap: var(--ds-space-4); }
5427
- .ds-gap-5 { gap: var(--ds-space-5); }
5428
- .ds-gap-6 { gap: var(--ds-space-6); }
5429
- .ds-gap-8 { gap: var(--ds-space-8); }
5430
- .ds-gap-10 { gap: var(--ds-space-10); }
5431
- .ds-gap-12 { gap: var(--ds-space-12); }
5432
- .ds-gap-16 { gap: var(--ds-space-16); }
5433
- .ds-gap-20 { gap: var(--ds-space-20); }
5497
+ /* Individual result */
5498
+ .ds-search__result {
5499
+ display: flex;
5500
+ align-items: center;
5501
+ gap: var(--ds-space-2);
5502
+ width: 100%;
5503
+ padding: var(--ds-space-2-5) var(--ds-space-3);
5504
+ border: none;
5505
+ background: transparent;
5506
+ color: var(--ds-color-text);
5507
+ font-size: var(--ds-text-sm);
5508
+ font-family: inherit;
5509
+ text-align: left;
5510
+ cursor: pointer;
5511
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
5512
+ }
5434
5513
 
5435
- /* --- Display --- */
5436
- .ds-block { display: block; }
5437
- .ds-inline-block { display: inline-block; }
5438
- .ds-inline { display: inline; }
5439
- .ds-hidden { display: none; }
5514
+ .ds-search__result:hover,
5515
+ .ds-search__result--active {
5516
+ background: var(--ds-color-surface-hover);
5517
+ }
5440
5518
 
5441
- @media (min-width: 768px) {
5442
- .ds-md\:hidden { display: none; }
5443
- .ds-md\:block { display: block; }
5444
- .ds-md\:flex { display: flex; }
5519
+ .ds-search__result:focus-visible {
5520
+ outline: none;
5521
+ box-shadow: inset 0 0 0 var(--ds-ring-width) var(--ds-ring-color);
5445
5522
  }
5446
5523
 
5447
- /* --- Position --- */
5448
- .ds-relative { position: relative; }
5449
- .ds-absolute { position: absolute; }
5450
- .ds-fixed { position: fixed; }
5451
- .ds-sticky { position: sticky; top: 0; }
5524
+ .ds-search__result-icon {
5525
+ color: var(--ds-color-text-tertiary);
5526
+ flex-shrink: 0;
5527
+ }
5452
5528
 
5453
- /* --- Overflow --- */
5454
- .ds-overflow-hidden { overflow: hidden; }
5455
- .ds-overflow-auto { overflow: auto; }
5456
- .ds-overflow-x-auto { overflow-x: auto; }
5529
+ .ds-search__result-db-icon {
5530
+ display: flex;
5531
+ align-items: center;
5532
+ justify-content: center;
5533
+ width: 1.5rem;
5534
+ height: 1.5rem;
5535
+ border-radius: var(--ds-radius-md);
5536
+ flex-shrink: 0;
5537
+ }
5457
5538
 
5458
- /* --- Width/Height --- */
5459
- .ds-w-full { width: 100%; }
5460
- .ds-h-full { height: 100%; }
5461
- .ds-h-screen { height: 100dvh; }
5462
- .ds-min-h-screen { min-height: 100dvh; }
5539
+ .ds-search__result-content {
5540
+ display: flex;
5541
+ flex-direction: column;
5542
+ min-width: 0;
5543
+ }
5463
5544
 
5464
- /* --- Stack (vertical spacing) ---
5465
- :where() lowers specificity so ds-mt-* can override individual children. */
5466
- :where(.ds-stack) > * + * {
5467
- margin-top: var(--ds-space-4);
5545
+ .ds-search__result-title {
5546
+ white-space: nowrap;
5547
+ overflow: hidden;
5548
+ text-overflow: ellipsis;
5549
+ min-width: 0;
5550
+ line-height: var(--ds-leading-snug);
5468
5551
  }
5469
5552
 
5470
- :where(.ds-stack--sm) > * + * { margin-top: var(--ds-space-2); }
5471
- :where(.ds-stack--lg) > * + * { margin-top: var(--ds-space-8); }
5472
- :where(.ds-stack--xl) > * + * { margin-top: var(--ds-space-12); }
5473
- :where(.ds-stack--2xl) > * + * { margin-top: var(--ds-space-16); }
5474
- :where(.ds-stack--3xl) > * + * { margin-top: var(--ds-space-24); }
5553
+ .ds-search__result-meta {
5554
+ font-size: var(--ds-text-2xs);
5555
+ line-height: var(--ds-leading-snug);
5556
+ color: var(--ds-color-text-tertiary);
5557
+ white-space: nowrap;
5558
+ overflow: hidden;
5559
+ text-overflow: ellipsis;
5560
+ }
5475
5561
 
5476
- /* --- Center content --- */
5477
- .ds-center {
5562
+ /* Empty state */
5563
+ .ds-search__empty {
5478
5564
  display: flex;
5479
5565
  align-items: center;
5480
5566
  justify-content: center;
5567
+ padding: var(--ds-space-4) var(--ds-space-2);
5568
+ color: var(--ds-color-text-tertiary);
5569
+ font-size: var(--ds-text-sm);
5481
5570
  }
5482
5571
 
5483
- /* --- Fixed Sizing --- */
5484
- .ds-w-0\.5 { width: var(--ds-space-0-5); }
5485
- .ds-w-2\.5 { width: var(--ds-space-2-5); }
5486
- .ds-w-3 { width: var(--ds-space-3); }
5487
- .ds-w-4 { width: var(--ds-space-4); }
5488
- .ds-w-5 { width: var(--ds-space-5); }
5489
- .ds-w-8 { width: var(--ds-space-8); }
5490
- .ds-w-9 { width: 2.25rem; }
5491
- .ds-w-10 { width: var(--ds-space-10); }
5492
-
5493
- .ds-h-2\.5 { height: var(--ds-space-2-5); }
5494
- .ds-h-3 { height: var(--ds-space-3); }
5495
- .ds-h-4 { height: var(--ds-space-4); }
5496
- .ds-h-5 { height: var(--ds-space-5); }
5497
- .ds-h-8 { height: var(--ds-space-8); }
5572
+ /* Mobile trigger (hidden on desktop) */
5573
+ .ds-search-mobile-trigger {
5574
+ display: none;
5575
+ align-items: center;
5576
+ justify-content: center;
5577
+ width: 2.25rem;
5578
+ height: 2.25rem;
5579
+ border: none;
5580
+ border-radius: var(--ds-radius-md);
5581
+ background: transparent;
5582
+ color: var(--ds-color-text);
5583
+ cursor: pointer;
5584
+ flex-shrink: 0;
5585
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
5586
+ }
5587
+
5588
+ @media (hover: hover) {
5589
+ .ds-search-mobile-trigger:hover {
5590
+ background-color: var(--ds-color-surface-hover);
5591
+ }
5592
+ }
5593
+
5594
+ /* Close button (mobile expanded) */
5595
+ .ds-search__close {
5596
+ display: flex;
5597
+ align-items: center;
5598
+ justify-content: center;
5599
+ width: 2.25rem;
5600
+ height: 2.25rem;
5601
+ border: none;
5602
+ border-radius: var(--ds-radius-md);
5603
+ background: transparent;
5604
+ color: var(--ds-color-text);
5605
+ cursor: pointer;
5606
+ flex-shrink: 0;
5607
+ }
5608
+
5609
+ /* Responsive: mobile */
5610
+ @media (max-width: 1023px) {
5611
+ .ds-search__shortcut { display: none; }
5612
+ .ds-search-mobile-trigger { display: flex; }
5613
+ .ds-search { display: none; }
5614
+
5615
+ .ds-search--mobile-expanded {
5616
+ display: flex;
5617
+ position: fixed;
5618
+ top: 0;
5619
+ left: 0;
5620
+ right: 0;
5621
+ height: var(--ds-search-bar-height, 3.5rem);
5622
+ z-index: var(--ds-z-dropdown);
5623
+ border: none;
5624
+ border-bottom: 1px solid var(--ds-color-border);
5625
+ border-radius: 0;
5626
+ padding: 0 var(--ds-space-3);
5627
+ background: var(--ds-color-nav-bg);
5628
+ backdrop-filter: blur(20px) saturate(1.5);
5629
+ -webkit-backdrop-filter: blur(20px) saturate(1.5);
5630
+ }
5631
+
5632
+ .ds-search__dropdown--mobile {
5633
+ left: 0 !important;
5634
+ right: 0;
5635
+ width: auto !important;
5636
+ border-radius: 0;
5637
+ border-left: none;
5638
+ border-right: none;
5639
+ max-height: calc(100dvh - var(--ds-search-bar-height, 3.5rem));
5640
+ background: var(--ds-color-surface);
5641
+ backdrop-filter: blur(20px) saturate(1.5);
5642
+ -webkit-backdrop-filter: blur(20px) saturate(1.5);
5643
+ }
5644
+ }
5645
+
5646
+ /* ==========================================================================
5647
+ Component: Toolbar
5648
+ Fixed bar with rows of actions: toolbar buttons, filter chips, segmented
5649
+ controls, and scrollable groups. Common in data-heavy UIs below a header.
5650
+
5651
+ Usage:
5652
+ <div class="ds-toolbar">
5653
+ <div class="ds-toolbar__row">
5654
+ <div class="ds-toolbar__group">...</div>
5655
+ <div class="ds-toolbar__spacer"></div>
5656
+ <div class="ds-toolbar__group">
5657
+ <button class="ds-toolbar__btn ds-toolbar__btn--active">
5658
+ <span>Filter</span>
5659
+ <span class="ds-toolbar__badge">3</span>
5660
+ </button>
5661
+ <button class="ds-toolbar__btn">Sort</button>
5662
+ </div>
5663
+ </div>
5664
+ </div>
5665
+
5666
+ Modifiers:
5667
+ .ds-toolbar__row--scroll — Horizontally scrollable row
5668
+ .ds-toolbar__btn--active — Active/toggled button
5669
+ .ds-toolbar__group--scroll — Scrollable group within a row
5670
+
5671
+ Segmented control:
5672
+ <div class="ds-toolbar__segmented">
5673
+ <button class="ds-toolbar__segmented-btn ds-toolbar__segmented-btn--active">Past</button>
5674
+ <button class="ds-toolbar__segmented-btn">Future</button>
5675
+ </div>
5676
+ ========================================================================== */
5677
+
5678
+ /* Row container */
5679
+ .ds-toolbar__row {
5680
+ display: flex;
5681
+ align-items: center;
5682
+ height: var(--ds-toolbar-row-height, 2.5rem);
5683
+ gap: var(--ds-space-3);
5684
+ padding-inline: var(--ds-space-4);
5685
+ }
5686
+
5687
+ .ds-toolbar__row--scroll {
5688
+ overflow-x: auto;
5689
+ scrollbar-width: none;
5690
+ }
5691
+
5692
+ .ds-toolbar__row--scroll::-webkit-scrollbar { display: none; }
5693
+
5694
+ /* Group (flex row of items) */
5695
+ .ds-toolbar__group {
5696
+ display: flex;
5697
+ align-items: center;
5698
+ flex-shrink: 0;
5699
+ }
5700
+
5701
+ .ds-toolbar__group--scroll {
5702
+ flex-shrink: 1;
5703
+ min-width: 0;
5704
+ overflow-x: auto;
5705
+ scrollbar-width: none;
5706
+ }
5707
+
5708
+ .ds-toolbar__group--scroll::-webkit-scrollbar { display: none; }
5709
+
5710
+ /* Spacer */
5711
+ .ds-toolbar__spacer {
5712
+ flex: 1 1 0;
5713
+ }
5714
+
5715
+ /* Toolbar button */
5716
+ .ds-toolbar__btn {
5717
+ display: inline-flex;
5718
+ align-items: center;
5719
+ gap: var(--ds-space-1-5);
5720
+ padding: var(--ds-space-1) var(--ds-space-2-5);
5721
+ font-size: var(--ds-text-xs);
5722
+ font-weight: var(--ds-weight-medium);
5723
+ font-family: inherit;
5724
+ color: var(--ds-color-text-tertiary);
5725
+ background: transparent;
5726
+ border: 1px solid transparent;
5727
+ border-radius: var(--ds-radius-md);
5728
+ cursor: pointer;
5729
+ white-space: nowrap;
5730
+ transition:
5731
+ color var(--ds-duration-fast) var(--ds-ease-default),
5732
+ background-color var(--ds-duration-fast) var(--ds-ease-default),
5733
+ border-color var(--ds-duration-fast) var(--ds-ease-default);
5734
+ }
5735
+
5736
+ .ds-toolbar__btn:hover {
5737
+ color: var(--ds-color-text-secondary);
5738
+ background: var(--ds-color-surface-hover);
5739
+ }
5740
+
5741
+ .ds-toolbar__btn:focus-visible {
5742
+ outline: none;
5743
+ box-shadow: 0 0 0 var(--ds-ring-width) var(--ds-ring-color);
5744
+ }
5745
+
5746
+ .ds-toolbar__btn--active {
5747
+ color: var(--ds-color-text);
5748
+ background: var(--ds-color-surface);
5749
+ border-color: var(--ds-color-border);
5750
+ }
5751
+
5752
+ /* Badge (count indicator inside button) */
5753
+ .ds-toolbar__badge {
5754
+ display: inline-flex;
5755
+ align-items: center;
5756
+ justify-content: center;
5757
+ min-width: 1rem;
5758
+ height: 1rem;
5759
+ padding: 0 0.25rem;
5760
+ font-size: 0.625rem;
5761
+ font-weight: 600;
5762
+ line-height: 1;
5763
+ color: var(--ds-color-on-inverted);
5764
+ background: var(--ds-color-interactive);
5765
+ border-radius: var(--ds-radius-full);
5766
+ }
5767
+
5768
+ /* Eye toggle (visibility) */
5769
+ .ds-toolbar__eye {
5770
+ display: inline-flex;
5771
+ align-items: center;
5772
+ cursor: pointer;
5773
+ opacity: 0.7;
5774
+ transition: opacity var(--ds-duration-fast) var(--ds-ease-default);
5775
+ }
5776
+
5777
+ .ds-toolbar__eye:hover {
5778
+ opacity: 1;
5779
+ }
5780
+
5781
+ /* Segmented control */
5782
+ .ds-toolbar__segmented {
5783
+ flex-shrink: 0;
5784
+ display: flex;
5785
+ background: var(--ds-color-surface);
5786
+ border-radius: var(--ds-radius-md);
5787
+ padding: 2px;
5788
+ }
5789
+
5790
+ .ds-toolbar__segmented-btn {
5791
+ padding: var(--ds-space-1) var(--ds-space-3);
5792
+ font-size: var(--ds-text-xs);
5793
+ font-weight: var(--ds-weight-medium);
5794
+ color: var(--ds-color-text-tertiary);
5795
+ border: none;
5796
+ border-radius: var(--ds-radius-sm);
5797
+ background: transparent;
5798
+ cursor: pointer;
5799
+ transition:
5800
+ color var(--ds-duration-fast) var(--ds-ease-default),
5801
+ background-color var(--ds-duration-fast) var(--ds-ease-default);
5802
+ }
5803
+
5804
+ .ds-toolbar__segmented-btn:hover {
5805
+ color: var(--ds-color-text-secondary);
5806
+ }
5807
+
5808
+ .ds-toolbar__segmented-btn--active {
5809
+ background: var(--ds-color-bg-elevated);
5810
+ color: var(--ds-color-text);
5811
+ }
5812
+
5813
+ /* Responsive: mobile */
5814
+ @media (max-width: 1023px) {
5815
+ .ds-toolbar__row {
5816
+ padding-inline: var(--ds-space-3);
5817
+ gap: var(--ds-space-2);
5818
+ }
5819
+
5820
+ .ds-toolbar__btn-label { display: none; }
5821
+
5822
+ .ds-toolbar__btn {
5823
+ padding: var(--ds-space-2);
5824
+ min-width: var(--ds-size-2);
5825
+ min-height: var(--ds-size-2);
5826
+ justify-content: center;
5827
+ }
5828
+
5829
+ .ds-toolbar__badge {
5830
+ font-size: 0.5rem;
5831
+ min-width: 0.875rem;
5832
+ height: 0.875rem;
5833
+ }
5834
+ }
5835
+
5836
+ /* ==========================================================================
5837
+ Component: Chip
5838
+ Interactive filter/sort chips with optional remove button. Distinct from
5839
+ ds-tag (which is a static label). Chips represent active filter state.
5840
+
5841
+ Usage:
5842
+ <span class="ds-chip">
5843
+ Status: Active
5844
+ <button class="ds-chip__remove">×</button>
5845
+ </span>
5846
+ <button class="ds-chip ds-chip--logic">AND</button>
5847
+ <span class="ds-chip ds-chip--sort">Date ↑</span>
5848
+
5849
+ Modifiers:
5850
+ .ds-chip--logic — AND/OR toggle chip (clickable, no bg)
5851
+ .ds-chip--sort — Sort indicator (dashed border)
5852
+ ========================================================================== */
5853
+
5854
+ .ds-chip {
5855
+ display: inline-flex;
5856
+ align-items: center;
5857
+ gap: var(--ds-space-1);
5858
+ padding: var(--ds-space-0-5) var(--ds-space-2);
5859
+ font-size: var(--ds-text-xs);
5860
+ font-family: inherit;
5861
+ color: var(--ds-color-text-secondary);
5862
+ background: var(--ds-color-bg-elevated);
5863
+ border: 1px solid var(--ds-color-border);
5864
+ border-radius: var(--ds-radius-full);
5865
+ white-space: nowrap;
5866
+ }
5867
+
5868
+ /* Logic chip (AND/OR toggle) */
5869
+ .ds-chip--logic {
5870
+ cursor: pointer;
5871
+ font-weight: 600;
5872
+ color: var(--ds-color-text-tertiary);
5873
+ background: transparent;
5874
+ border-color: var(--ds-color-border);
5875
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
5876
+ }
5877
+
5878
+ .ds-chip--logic:hover {
5879
+ background: var(--ds-color-surface-hover);
5880
+ }
5881
+
5882
+ /* Sort chip (dashed border) */
5883
+ .ds-chip--sort {
5884
+ border-style: dashed;
5885
+ }
5886
+
5887
+ /* Remove button */
5888
+ .ds-chip__remove {
5889
+ display: inline-flex;
5890
+ align-items: center;
5891
+ justify-content: center;
5892
+ width: 14px;
5893
+ height: 14px;
5894
+ border: none;
5895
+ border-radius: var(--ds-radius-full);
5896
+ background: transparent;
5897
+ color: var(--ds-color-text-tertiary);
5898
+ cursor: pointer;
5899
+ transition:
5900
+ color var(--ds-duration-fast) var(--ds-ease-default),
5901
+ background-color var(--ds-duration-fast) var(--ds-ease-default);
5902
+ }
5903
+
5904
+ .ds-chip__remove:hover {
5905
+ color: var(--ds-color-text);
5906
+ background: var(--ds-color-surface-hover);
5907
+ }
5908
+
5909
+ /* ==========================================================================
5910
+ Component: Icon Button
5911
+ Standalone icon button for actions, toolbar items, and table rows.
5912
+ A minimal interactive element that wraps a single icon.
5913
+
5914
+ Usage:
5915
+ <button class="ds-icon-btn">
5916
+ <svg>...</svg>
5917
+ </button>
5918
+ <button class="ds-icon-btn ds-icon-btn--sm">...</button>
5919
+ <button class="ds-icon-btn ds-icon-btn--danger">...</button>
5920
+
5921
+ Sizes:
5922
+ .ds-icon-btn--xs — 24px (var(--ds-size-1))
5923
+ .ds-icon-btn--sm — 28px
5924
+ (default) — 32px (var(--ds-size-2))
5925
+ .ds-icon-btn--lg — 40px (var(--ds-size-3))
5926
+
5927
+ Modifiers:
5928
+ .ds-icon-btn--danger — Error color on hover
5929
+ .ds-icon-btn--ghost — No background change, color-only transition
5930
+ ========================================================================== */
5931
+
5932
+ .ds-icon-btn {
5933
+ display: inline-flex;
5934
+ align-items: center;
5935
+ justify-content: center;
5936
+ width: var(--ds-size-2);
5937
+ height: var(--ds-size-2);
5938
+ padding: var(--ds-space-1-5);
5939
+ border: none;
5940
+ border-radius: var(--ds-radius-lg);
5941
+ background: transparent;
5942
+ color: var(--ds-color-text-tertiary);
5943
+ cursor: pointer;
5944
+ flex-shrink: 0;
5945
+ transition:
5946
+ color var(--ds-duration-fast) var(--ds-ease-default),
5947
+ background-color var(--ds-duration-fast) var(--ds-ease-default);
5948
+ }
5949
+
5950
+ .ds-icon-btn:hover {
5951
+ background-color: var(--ds-color-surface-hover);
5952
+ color: var(--ds-color-text-secondary);
5953
+ }
5954
+
5955
+ .ds-icon-btn:focus-visible {
5956
+ outline: none;
5957
+ box-shadow: 0 0 0 var(--ds-ring-width) var(--ds-ring-color);
5958
+ }
5959
+
5960
+ .ds-icon-btn:disabled,
5961
+ .ds-icon-btn[aria-disabled="true"] {
5962
+ opacity: var(--ds-opacity-disabled);
5963
+ cursor: not-allowed;
5964
+ pointer-events: none;
5965
+ }
5966
+
5967
+ /* Sizes */
5968
+ .ds-icon-btn--xs {
5969
+ width: var(--ds-size-1);
5970
+ height: var(--ds-size-1);
5971
+ padding: var(--ds-space-0-5);
5972
+ border-radius: var(--ds-radius-md);
5973
+ }
5974
+
5975
+ .ds-icon-btn--sm {
5976
+ width: 1.75rem;
5977
+ height: 1.75rem;
5978
+ padding: var(--ds-space-1);
5979
+ border-radius: var(--ds-radius-md);
5980
+ }
5981
+
5982
+ .ds-icon-btn--lg {
5983
+ width: var(--ds-size-3);
5984
+ height: var(--ds-size-3);
5985
+ padding: var(--ds-space-2);
5986
+ }
5987
+
5988
+ /* Danger variant */
5989
+ .ds-icon-btn--danger:hover {
5990
+ color: var(--ds-color-error);
5991
+ background-color: color-mix(in srgb, var(--ds-color-error) 10%, transparent);
5992
+ }
5993
+
5994
+ /* Ghost variant (color only, no bg) */
5995
+ .ds-icon-btn--ghost {
5996
+ padding: 0;
5997
+ width: auto;
5998
+ height: auto;
5999
+ }
6000
+
6001
+ .ds-icon-btn--ghost:hover {
6002
+ background: transparent;
6003
+ color: var(--ds-color-text);
6004
+ }
6005
+
6006
+ /* ==========================================================================
6007
+ Component: Bottom Nav
6008
+ Mobile bottom navigation bar. Hidden on desktop, shown on mobile.
6009
+ Fixed to bottom with safe-area insets for notched devices.
6010
+
6011
+ Usage:
6012
+ <nav class="ds-bottom-nav">
6013
+ <a href="/" class="ds-bottom-nav__item ds-bottom-nav__item--active">
6014
+ <span class="ds-bottom-nav__icon">
6015
+ <svg>...</svg>
6016
+ <span class="ds-bottom-nav__badge">3</span>
6017
+ </span>
6018
+ <span class="ds-bottom-nav__label">Home</span>
6019
+ </a>
6020
+ <button class="ds-bottom-nav__item ds-bottom-nav__item--create">
6021
+ <span class="ds-bottom-nav__create-icon">+</span>
6022
+ <span class="ds-bottom-nav__label">New</span>
6023
+ </button>
6024
+ </nav>
6025
+
6026
+ Modifiers:
6027
+ .ds-bottom-nav__item--active — Active/current tab
6028
+ .ds-bottom-nav__item--create — Elevated center action button
6029
+ ========================================================================== */
6030
+
6031
+ .ds-bottom-nav {
6032
+ display: none;
6033
+ position: fixed;
6034
+ bottom: 0;
6035
+ left: 0;
6036
+ right: 0;
6037
+ z-index: var(--ds-z-dropdown);
6038
+ background: var(--ds-color-surface);
6039
+ border-top: 1px solid var(--ds-color-border);
6040
+ padding-bottom: env(safe-area-inset-bottom, 0px);
6041
+ }
6042
+
6043
+ @media (max-width: 1023px) {
6044
+ .ds-bottom-nav {
6045
+ display: flex;
6046
+ justify-content: space-around;
6047
+ align-items: stretch;
6048
+ }
6049
+ }
6050
+
6051
+ @media (min-width: 1024px) {
6052
+ .ds-bottom-nav { display: none !important; }
6053
+ }
6054
+
6055
+ /* Nav item */
6056
+ .ds-bottom-nav__item {
6057
+ display: flex;
6058
+ flex-direction: column;
6059
+ align-items: center;
6060
+ justify-content: center;
6061
+ gap: var(--ds-space-1);
6062
+ flex: 1;
6063
+ padding: var(--ds-space-2) 0;
6064
+ color: var(--ds-color-text-tertiary);
6065
+ text-decoration: none;
6066
+ border: none;
6067
+ background: none;
6068
+ cursor: pointer;
6069
+ font: inherit;
6070
+ transition: color var(--ds-duration-fast) var(--ds-ease-default);
6071
+ -webkit-tap-highlight-color: transparent;
6072
+ }
6073
+
6074
+ .ds-bottom-nav__item--active {
6075
+ color: var(--ds-color-text);
6076
+ }
6077
+
6078
+ /* Icon wrapper */
6079
+ .ds-bottom-nav__icon {
6080
+ position: relative;
6081
+ display: flex;
6082
+ align-items: center;
6083
+ justify-content: center;
6084
+ }
6085
+
6086
+ /* Notification badge */
6087
+ .ds-bottom-nav__badge {
6088
+ position: absolute;
6089
+ top: -4px;
6090
+ right: -8px;
6091
+ min-width: 16px;
6092
+ height: 16px;
6093
+ padding: 0 4px;
6094
+ border-radius: var(--ds-radius-full);
6095
+ background: var(--ds-color-error);
6096
+ color: #fff;
6097
+ font-size: 10px;
6098
+ font-weight: 600;
6099
+ line-height: 16px;
6100
+ text-align: center;
6101
+ }
6102
+
6103
+ /* Label */
6104
+ .ds-bottom-nav__label {
6105
+ font-size: 10px;
6106
+ font-weight: var(--ds-weight-medium);
6107
+ letter-spacing: 0.02em;
6108
+ }
6109
+
6110
+ /* Create button — elevated center icon */
6111
+ .ds-bottom-nav__create-icon {
6112
+ display: flex;
6113
+ align-items: center;
6114
+ justify-content: center;
6115
+ width: 2.25rem;
6116
+ height: 2.25rem;
6117
+ border-radius: var(--ds-radius-full);
6118
+ background: var(--ds-color-text);
6119
+ color: var(--ds-color-bg-base);
6120
+ }
6121
+
6122
+ .ds-bottom-nav__item--create {
6123
+ color: var(--ds-color-text-tertiary);
6124
+ }
6125
+
6126
+ /* ==========================================================================
6127
+ Component: Spinner
6128
+ CSS-only loading spinner using border animation.
6129
+
6130
+ Usage:
6131
+ <span class="ds-spinner"></span>
6132
+ <span class="ds-spinner ds-spinner--sm"></span>
6133
+ <span class="ds-spinner ds-spinner--muted"></span>
6134
+
6135
+ Sizes:
6136
+ .ds-spinner--sm — 1.25rem (20px)
6137
+ (default) — 1.5rem (24px)
6138
+ .ds-spinner--lg — 2rem (32px)
6139
+
6140
+ Variants:
6141
+ (default) — Border + interactive top
6142
+ .ds-spinner--muted — Inverted colors (for dark backgrounds)
6143
+ .ds-spinner--light — Light colors (for inverted/colored backgrounds)
6144
+ ========================================================================== */
6145
+
6146
+ .ds-spinner {
6147
+ display: inline-block;
6148
+ width: 1.5rem;
6149
+ height: 1.5rem;
6150
+ border: 2px solid var(--ds-color-border);
6151
+ border-top-color: var(--ds-color-interactive);
6152
+ border-radius: var(--ds-radius-full);
6153
+ animation: ds-spin 0.8s linear infinite;
6154
+ }
6155
+
6156
+ @keyframes ds-spin {
6157
+ to { transform: rotate(360deg); }
6158
+ }
6159
+
6160
+ /* Sizes */
6161
+ .ds-spinner--sm {
6162
+ width: 1rem;
6163
+ height: 1rem;
6164
+ }
6165
+
6166
+ .ds-spinner--md {
6167
+ width: 1.25rem;
6168
+ height: 1.25rem;
6169
+ }
6170
+
6171
+ .ds-spinner--lg {
6172
+ width: 2rem;
6173
+ height: 2rem;
6174
+ }
6175
+
6176
+ /* Color variants */
6177
+ .ds-spinner--muted {
6178
+ border-color: var(--ds-color-inverted);
6179
+ border-top-color: transparent;
6180
+ }
6181
+
6182
+ .ds-spinner--light {
6183
+ border-color: var(--ds-color-on-inverted);
6184
+ border-top-color: transparent;
6185
+ }
6186
+
6187
+
6188
+ /* ==========================================================================
6189
+ Utilities: Layout
6190
+ Container with clamp padding, generous responsive grid.
6191
+ ========================================================================== */
6192
+
6193
+ /* --- Container --- */
6194
+ .ds-container {
6195
+ width: 100%;
6196
+ max-width: var(--ds-container-max);
6197
+ margin-inline: auto;
6198
+ padding-inline: var(--ds-container-padding);
6199
+ }
6200
+
6201
+ /* --- Section (generous vertical rhythm) --- */
6202
+ .ds-section {
6203
+ padding-block: var(--ds-section-padding);
6204
+ }
6205
+
6206
+ /* --- Flex --- */
6207
+ .ds-flex { display: flex; }
6208
+ .ds-inline-flex { display: inline-flex; }
6209
+ .ds-flex-col { flex-direction: column; }
6210
+ .ds-flex-row { flex-direction: row; }
6211
+ .ds-flex-wrap { flex-wrap: wrap; }
6212
+ .ds-flex-1 { flex: 1 1 0%; }
6213
+ .ds-flex-auto { flex: 1 1 auto; }
6214
+ .ds-flex-none { flex: none; }
6215
+ .ds-shrink-0 { flex-shrink: 0; }
6216
+
6217
+ /* --- Alignment --- */
6218
+ .ds-items-start { align-items: flex-start; }
6219
+ .ds-items-center { align-items: center; }
6220
+ .ds-items-end { align-items: flex-end; }
6221
+ .ds-items-stretch { align-items: stretch; }
6222
+ .ds-items-baseline { align-items: baseline; }
6223
+
6224
+ .ds-justify-start { justify-content: flex-start; }
6225
+ .ds-justify-center { justify-content: center; }
6226
+ .ds-justify-end { justify-content: flex-end; }
6227
+ .ds-justify-between { justify-content: space-between; }
6228
+ .ds-justify-around { justify-content: space-around; }
6229
+ .ds-justify-evenly { justify-content: space-evenly; }
6230
+
6231
+ .ds-self-start { align-self: flex-start; }
6232
+ .ds-self-center { align-self: center; }
6233
+ .ds-self-end { align-self: flex-end; }
6234
+
6235
+ /* --- Grid --- */
6236
+ .ds-grid {
6237
+ display: grid;
6238
+ gap: var(--ds-space-6);
6239
+ }
6240
+
6241
+ .ds-grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
6242
+ .ds-grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
6243
+ .ds-grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
6244
+ .ds-grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
6245
+
6246
+ /* --- Column Span --- */
6247
+ .ds-col-span-1 { grid-column: span 1 / span 1; }
6248
+ .ds-col-span-2 { grid-column: span 2 / span 2; }
6249
+ .ds-col-span-3 { grid-column: span 3 / span 3; }
6250
+ .ds-col-span-4 { grid-column: span 4 / span 4; }
6251
+ .ds-col-span-full { grid-column: 1 / -1; }
6252
+
6253
+ @media (min-width: 640px) {
6254
+ .ds-sm\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
6255
+ .ds-sm\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
6256
+ }
6257
+
6258
+ @media (min-width: 768px) {
6259
+ .ds-md\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
6260
+ .ds-md\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
6261
+ .ds-md\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
6262
+ .ds-md\:col-span-1 { grid-column: span 1 / span 1; }
6263
+ .ds-md\:col-span-2 { grid-column: span 2 / span 2; }
6264
+ .ds-md\:col-span-3 { grid-column: span 3 / span 3; }
6265
+ .ds-md\:flex-row { flex-direction: row; }
6266
+ }
6267
+
6268
+ @media (min-width: 1024px) {
6269
+ .ds-lg\:grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
6270
+ .ds-lg\:grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
6271
+ .ds-lg\:grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
6272
+ .ds-lg\:col-span-1 { grid-column: span 1 / span 1; }
6273
+ .ds-lg\:col-span-2 { grid-column: span 2 / span 2; }
6274
+ .ds-lg\:col-span-3 { grid-column: span 3 / span 3; }
6275
+ }
6276
+
6277
+ /* --- Gap --- */
6278
+ .ds-gap-0 { gap: var(--ds-space-0); }
6279
+ .ds-gap-1 { gap: var(--ds-space-1); }
6280
+ .ds-gap-2 { gap: var(--ds-space-2); }
6281
+ .ds-gap-3 { gap: var(--ds-space-3); }
6282
+ .ds-gap-4 { gap: var(--ds-space-4); }
6283
+ .ds-gap-5 { gap: var(--ds-space-5); }
6284
+ .ds-gap-6 { gap: var(--ds-space-6); }
6285
+ .ds-gap-8 { gap: var(--ds-space-8); }
6286
+ .ds-gap-10 { gap: var(--ds-space-10); }
6287
+ .ds-gap-12 { gap: var(--ds-space-12); }
6288
+ .ds-gap-16 { gap: var(--ds-space-16); }
6289
+ .ds-gap-20 { gap: var(--ds-space-20); }
6290
+
6291
+ /* --- Display --- */
6292
+ .ds-block { display: block; }
6293
+ .ds-inline-block { display: inline-block; }
6294
+ .ds-inline { display: inline; }
6295
+ .ds-hidden { display: none; }
6296
+
6297
+ @media (min-width: 768px) {
6298
+ .ds-md\:hidden { display: none; }
6299
+ .ds-md\:block { display: block; }
6300
+ .ds-md\:flex { display: flex; }
6301
+ }
6302
+
6303
+ /* --- Position --- */
6304
+ .ds-relative { position: relative; }
6305
+ .ds-absolute { position: absolute; }
6306
+ .ds-fixed { position: fixed; }
6307
+ .ds-sticky { position: sticky; top: 0; }
6308
+
6309
+ /* --- Overflow --- */
6310
+ .ds-overflow-hidden { overflow: hidden; }
6311
+ .ds-overflow-auto { overflow: auto; }
6312
+ .ds-overflow-x-auto { overflow-x: auto; }
6313
+
6314
+ /* --- Width/Height --- */
6315
+ .ds-w-full { width: 100%; }
6316
+ .ds-h-full { height: 100%; }
6317
+ .ds-h-screen { height: 100dvh; }
6318
+ .ds-min-h-screen { min-height: 100dvh; }
6319
+
6320
+ /* --- Stack (vertical spacing) ---
6321
+ :where() lowers specificity so ds-mt-* can override individual children. */
6322
+ :where(.ds-stack) > * + * {
6323
+ margin-top: var(--ds-space-4);
6324
+ }
6325
+
6326
+ :where(.ds-stack--sm) > * + * { margin-top: var(--ds-space-2); }
6327
+ :where(.ds-stack--lg) > * + * { margin-top: var(--ds-space-8); }
6328
+ :where(.ds-stack--xl) > * + * { margin-top: var(--ds-space-12); }
6329
+ :where(.ds-stack--2xl) > * + * { margin-top: var(--ds-space-16); }
6330
+ :where(.ds-stack--3xl) > * + * { margin-top: var(--ds-space-24); }
6331
+
6332
+ /* --- Center content --- */
6333
+ .ds-center {
6334
+ display: flex;
6335
+ align-items: center;
6336
+ justify-content: center;
6337
+ }
6338
+
6339
+ /* --- Fixed Sizing --- */
6340
+ .ds-w-0\.5 { width: var(--ds-space-0-5); }
6341
+ .ds-w-2\.5 { width: var(--ds-space-2-5); }
6342
+ .ds-w-3 { width: var(--ds-space-3); }
6343
+ .ds-w-4 { width: var(--ds-space-4); }
6344
+ .ds-w-5 { width: var(--ds-space-5); }
6345
+ .ds-w-8 { width: var(--ds-space-8); }
6346
+ .ds-w-9 { width: 2.25rem; }
6347
+ .ds-w-10 { width: var(--ds-space-10); }
6348
+
6349
+ .ds-h-2\.5 { height: var(--ds-space-2-5); }
6350
+ .ds-h-3 { height: var(--ds-space-3); }
6351
+ .ds-h-4 { height: var(--ds-space-4); }
6352
+ .ds-h-5 { height: var(--ds-space-5); }
6353
+ .ds-h-8 { height: var(--ds-space-8); }
5498
6354
  .ds-h-9 { height: 2.25rem; }
5499
6355
  .ds-h-10 { height: var(--ds-space-10); }
5500
6356
  .ds-h-11 { height: 2.75rem; }
@@ -6050,4 +6906,121 @@ tr:hover .ds-sortable__handle,
6050
6906
  border-width: 0;
6051
6907
  }
6052
6908
 
6909
+ /* ==========================================================================
6910
+ Utilities: Interactive
6911
+ Hover states, reveal patterns, focus rings, and cursor utilities.
6912
+ ========================================================================== */
6913
+
6914
+ /* --- Hover Row (background elevation on hover) --- */
6915
+ .ds-hover-row {
6916
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
6917
+ }
6918
+ .ds-hover-row:hover {
6919
+ background-color: var(--ds-color-bg-elevated);
6920
+ }
6921
+
6922
+ /* Subtle variant (semi-transparent) */
6923
+ .ds-hover-row--subtle {
6924
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
6925
+ }
6926
+ .ds-hover-row--subtle:hover {
6927
+ background-color: color-mix(in srgb, var(--ds-color-bg-elevated) 50%, transparent);
6928
+ }
6929
+
6930
+ /* --- Group Reveal (children appear on parent hover) --- */
6931
+ .ds-reveal {
6932
+ opacity: 0;
6933
+ transition: opacity var(--ds-duration-fast) var(--ds-ease-default);
6934
+ }
6935
+ .group:hover > .ds-reveal,
6936
+ .group:hover .ds-reveal {
6937
+ opacity: 1;
6938
+ }
6939
+
6940
+ /* --- Editable Cell (pointer + bg on hover) --- */
6941
+ .ds-editable {
6942
+ cursor: pointer;
6943
+ border-radius: var(--ds-radius-default);
6944
+ transition: background-color var(--ds-duration-fast) var(--ds-ease-default);
6945
+ }
6946
+ .ds-editable:hover {
6947
+ background-color: var(--ds-color-surface-hover);
6948
+ }
6949
+
6950
+ /* --- Hover Border --- */
6951
+ .ds-hover-border {
6952
+ transition: border-color var(--ds-duration-fast) var(--ds-ease-default);
6953
+ }
6954
+ .ds-hover-border:hover {
6955
+ border-color: var(--ds-color-border-hover);
6956
+ }
6957
+
6958
+ /* --- Hover Zoom (scale on hover) --- */
6959
+ .ds-hover-zoom {
6960
+ transition: transform var(--ds-duration-fast) var(--ds-ease-default);
6961
+ }
6962
+ .ds-hover-zoom:hover {
6963
+ transform: scale(1.05);
6964
+ }
6965
+
6966
+ /* --- Opacity Hover (dim → full on hover) --- */
6967
+ .ds-opacity-hover {
6968
+ opacity: 0.5;
6969
+ transition: opacity var(--ds-duration-fast) var(--ds-ease-default);
6970
+ }
6971
+ .ds-opacity-hover:hover {
6972
+ opacity: 1;
6973
+ }
6974
+
6975
+ /* --- Focus Ring (inset ring for custom inputs) --- */
6976
+ .ds-focus-ring:focus {
6977
+ border-color: var(--ds-color-border-active);
6978
+ box-shadow: inset 0 0 0 1px var(--ds-color-border-active);
6979
+ outline: none;
6980
+ }
6981
+
6982
+ /* --- Ring States --- */
6983
+ .ds-ring-active {
6984
+ box-shadow: 0 0 0 2px var(--ds-color-border-active);
6985
+ }
6986
+
6987
+ .ds-ring-selected {
6988
+ box-shadow: 0 0 0 2px var(--ds-color-interactive), 0 0 0 3px var(--ds-color-surface);
6989
+ }
6990
+
6991
+ /* --- Semi-transparent Backgrounds --- */
6992
+ .ds-bg-elevated-50 {
6993
+ background-color: color-mix(in srgb, var(--ds-color-bg-elevated) 50%, transparent);
6994
+ }
6995
+
6996
+ .ds-bg-base-50 {
6997
+ background-color: color-mix(in srgb, var(--ds-color-bg-base) 50%, transparent);
6998
+ }
6999
+
7000
+ /* --- Hover Color Variants --- */
7001
+ .ds-hover-text-error:hover {
7002
+ color: var(--ds-color-error);
7003
+ }
7004
+
7005
+ .ds-hover-bg-error:hover {
7006
+ background-color: color-mix(in srgb, var(--ds-color-error) 10%, transparent);
7007
+ }
7008
+
7009
+ /* --- Cursor Utilities --- */
7010
+ .ds-cursor-grab {
7011
+ cursor: grab;
7012
+ }
7013
+ .ds-cursor-grab:active {
7014
+ cursor: grabbing;
7015
+ }
7016
+
7017
+ /* --- Grip Icon (secondary → primary on group hover) --- */
7018
+ .ds-grip {
7019
+ color: var(--ds-color-text-secondary);
7020
+ transition: color var(--ds-duration-fast) var(--ds-ease-default);
7021
+ }
7022
+ .group:hover .ds-grip {
7023
+ color: var(--ds-color-text);
7024
+ }
7025
+
6053
7026