@dryui/ui 1.1.5 → 1.3.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 (74) hide show
  1. package/dist/accordion/accordion-button-trigger.svelte +6 -0
  2. package/dist/alert/alert.svelte +2 -1
  3. package/dist/alert-dialog/alert-dialog-content.svelte +9 -69
  4. package/dist/alpha-slider/alpha-slider-input.svelte +4 -0
  5. package/dist/avatar/avatar.svelte +3 -4
  6. package/dist/backdrop/backdrop.svelte +12 -9
  7. package/dist/badge/badge.svelte +84 -72
  8. package/dist/button/button.svelte +3 -6
  9. package/dist/button-group/button-group.svelte +2 -2
  10. package/dist/calendar/calendar-button-grid.svelte +1 -1
  11. package/dist/card/card-root.svelte +9 -4
  12. package/dist/chip-group/chip-group-root.svelte +2 -2
  13. package/dist/code-block/code-block-button.svelte +6 -2
  14. package/dist/context-menu/context-menu-content.svelte +12 -79
  15. package/dist/diagram/diagram.svelte +3 -4
  16. package/dist/dialog/dialog-content.svelte +5 -103
  17. package/dist/drag-and-drop/drag-and-drop-item.svelte +1 -1
  18. package/dist/drawer/drawer-dialog-content.svelte +5 -155
  19. package/dist/dropdown-menu/dropdown-menu-content.svelte +14 -71
  20. package/dist/heading/heading.svelte +7 -6
  21. package/dist/hover-card/hover-card-content.svelte +3 -12
  22. package/dist/icon/icon.svelte +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +1 -1
  25. package/dist/input/input.svelte +3 -3
  26. package/dist/internal/calendar-grid-button.svelte +29 -26
  27. package/dist/internal/calendar-grid-button.svelte.d.ts +1 -0
  28. package/dist/internal/close-button-base.svelte +15 -2
  29. package/dist/internal/close-button-base.svelte.d.ts +1 -0
  30. package/dist/internal/modal-content.svelte +331 -0
  31. package/dist/internal/modal-content.svelte.d.ts +26 -0
  32. package/dist/link-preview/link-preview-content.svelte +3 -11
  33. package/dist/logo-mark/logo-mark.svelte +2 -3
  34. package/dist/mask-reveal/mask-reveal.svelte +17 -14
  35. package/dist/mega-menu/mega-menu-panel.svelte +14 -2
  36. package/dist/mega-menu/mega-menu-panel.svelte.d.ts +1 -0
  37. package/dist/menubar/menubar-content.svelte +21 -81
  38. package/dist/noise/noise.svelte +1 -0
  39. package/dist/option-picker/option-picker-preview.svelte +11 -10
  40. package/dist/popover/popover-content.svelte +10 -36
  41. package/dist/progress/progress.svelte +2 -2
  42. package/dist/prompt-input/index.d.ts +13 -1
  43. package/dist/prompt-input/prompt-input-button-textarea.svelte +13 -1
  44. package/dist/prompt-input/prompt-input-button-textarea.svelte.d.ts +3 -0
  45. package/dist/shimmer/shimmer.svelte +28 -3
  46. package/dist/slider/slider-input.svelte +3 -4
  47. package/dist/spacer/spacer.svelte +2 -2
  48. package/dist/spinner/spinner.svelte +2 -2
  49. package/dist/tabs/tabs-content.svelte +1 -1
  50. package/dist/tabs/tabs-list.svelte +1 -0
  51. package/dist/tag/tag-button.svelte +2 -3
  52. package/dist/text/text.svelte +4 -15
  53. package/dist/themes/dark.css +40 -24
  54. package/dist/themes/default.css +16 -12
  55. package/dist/themes/token-scope.d.ts +1 -0
  56. package/dist/themes/token-scope.js +1 -0
  57. package/dist/timeline/timeline-icon.svelte +11 -1
  58. package/dist/timeline/timeline-item.svelte +18 -5
  59. package/dist/timeline/timeline-root.svelte +3 -10
  60. package/dist/toast/toast-close-button.svelte +19 -7
  61. package/dist/toast/toast-provider.svelte +22 -1
  62. package/dist/toggle/toggle-button.svelte +7 -3
  63. package/dist/toggle-group/toggle-group-root.svelte +2 -2
  64. package/dist/tooltip/tooltip-content.svelte +8 -32
  65. package/dist/tour/tour-root.css +26 -26
  66. package/dist/tree/tree-item-label.svelte +8 -14
  67. package/dist/tree/tree-item-label.svelte.d.ts +2 -2
  68. package/dist/typography/heading.svelte +7 -6
  69. package/dist/typography/text.svelte +4 -3
  70. package/package.json +6 -2
  71. package/skills/dryui/SKILL.md +3 -3
  72. package/skills/dryui/rules/native-web-transitions.md +2 -2
  73. package/dist/themes/use-theme-override.svelte.d.ts +0 -1
  74. package/dist/themes/use-theme-override.svelte.js +0 -1
@@ -1,11 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createAnchorPosition } from '@dryui/primitives';
4
+ import { createAnchoredPopover, createMenuNavigation } from '@dryui/primitives';
5
5
  import { getMenubarCtx, getMenubarMenuCtx } from './context.svelte.js';
6
6
 
7
- const MENU_ITEM_SELECTOR = '[role="menuitem"]:not([data-disabled])';
8
-
9
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
10
8
  placement?: 'bottom' | 'bottom-start' | 'bottom-end';
11
9
  offset?: number;
@@ -33,57 +31,31 @@
33
31
  root?.querySelector<HTMLElement>(`[data-menubar-trigger="${menuCtx.menuId}"]`) ?? null;
34
32
  });
35
33
 
36
- const anchor = createAnchorPosition(
37
- () => triggerEl,
38
- () => el ?? null,
39
- {
40
- get placement() {
41
- return placement;
42
- },
43
- get offset() {
44
- return offset;
45
- }
46
- }
47
- );
48
-
49
- $effect(() => {
50
- if (!el) return;
51
-
52
- el.style.cssText = typeof style === 'string' ? style : '';
53
- const positionStyles = anchor.styles;
54
- for (const [key, value] of Object.entries(positionStyles)) {
55
- el.style.setProperty(key, value);
56
- }
57
- });
58
-
59
- function getMenuItems(container: HTMLElement): HTMLElement[] {
60
- return Array.from(container.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR));
61
- }
62
-
63
- function focusItem(items: HTMLElement[], index: number): void {
64
- if (items.length === 0) return;
65
- const clamped = ((index % items.length) + items.length) % items.length;
66
- items[clamped]?.focus();
67
- }
68
-
69
- $effect(() => {
70
- if (menuCtx.open && el && !el.matches(':popover-open')) {
71
- el.showPopover();
34
+ const popover = createAnchoredPopover({
35
+ triggerEl: () => triggerEl,
36
+ contentEl: () => el ?? null,
37
+ open: () => menuCtx.open,
38
+ placement: () => placement,
39
+ offset: () => offset,
40
+ onAfterShow: (contentEl) => {
72
41
  requestAnimationFrame(() => {
73
- if (!el) return;
74
- const items = getMenuItems(el);
75
- const first = items[0];
76
- if (first) {
77
- first.focus();
42
+ const focusable = contentEl.querySelector<HTMLElement>(
43
+ '[role="menuitem"]:not([data-disabled])'
44
+ );
45
+ if (focusable) {
46
+ focusable.focus();
78
47
  } else {
79
- el.focus();
48
+ contentEl.focus();
80
49
  }
81
50
  });
82
- } else if (!menuCtx.open && el?.matches(':popover-open')) {
83
- el.hidePopover();
84
51
  }
85
52
  });
86
53
 
54
+ const menu = createMenuNavigation({
55
+ container: () => el ?? null,
56
+ orientation: 'vertical'
57
+ });
58
+
87
59
  function handleKeydown(e: KeyboardEvent) {
88
60
  if (!el) return;
89
61
  switch (e.key) {
@@ -107,40 +79,7 @@
107
79
  return;
108
80
  }
109
81
  }
110
- const items = getMenuItems(el);
111
- const currentIndex = items.indexOf(document.activeElement as HTMLElement);
112
-
113
- switch (e.key) {
114
- case 'ArrowDown': {
115
- e.preventDefault();
116
- focusItem(items, currentIndex + 1);
117
- return;
118
- }
119
- case 'ArrowUp': {
120
- e.preventDefault();
121
- focusItem(items, currentIndex - 1);
122
- return;
123
- }
124
- case 'Home': {
125
- e.preventDefault();
126
- focusItem(items, 0);
127
- return;
128
- }
129
- case 'End': {
130
- e.preventDefault();
131
- focusItem(items, items.length - 1);
132
- return;
133
- }
134
- default: {
135
- if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
136
- const char = e.key.toLowerCase();
137
- const match = items.find((item) =>
138
- item.textContent?.trim().toLowerCase().startsWith(char)
139
- );
140
- if (match) match.focus();
141
- }
142
- }
143
- }
82
+ menu.handleKeydown(e);
144
83
  }
145
84
  </script>
146
85
 
@@ -153,6 +92,7 @@
153
92
  data-menubar-content
154
93
  data-state={menuCtx.open ? 'open' : 'closed'}
155
94
  class={className}
95
+ use:popover.applyPosition={style}
156
96
  ontoggle={(e) => {
157
97
  const newState = (e as ToggleEvent).newState === 'open';
158
98
  if (!newState && menuCtx.open) {
@@ -85,6 +85,7 @@
85
85
  [data-noise-texture] {
86
86
  position: absolute;
87
87
  inset: -24%;
88
+ z-index: 2;
88
89
  pointer-events: none;
89
90
  opacity: var(--dry-noise-opacity);
90
91
  mix-blend-mode: var(--dry-noise-blend);
@@ -11,19 +11,20 @@
11
11
 
12
12
  let { color, shape = 'rounded', variant = 'default', children, ...rest }: Props = $props();
13
13
 
14
- function attachPreviewVars(previewColor: string | undefined) {
15
- return (node: HTMLSpanElement) => {
16
- if (previewColor) {
17
- node.style.setProperty('--dry-option-picker-preview-bg', previewColor);
18
- } else {
19
- node.style.removeProperty('--dry-option-picker-preview-bg');
20
- }
21
- };
22
- }
14
+ let el: HTMLSpanElement | undefined = $state();
15
+
16
+ $effect(() => {
17
+ if (!el) return;
18
+ if (color) {
19
+ el.style.setProperty('--dry-option-picker-preview-bg', color);
20
+ } else {
21
+ el.style.removeProperty('--dry-option-picker-preview-bg');
22
+ }
23
+ });
23
24
  </script>
24
25
 
25
26
  <span
26
- {@attach attachPreviewVars(color)}
27
+ bind:this={el}
27
28
  data-option-picker-preview
28
29
  data-option-picker-preview-shape={shape}
29
30
  data-variant={variant !== 'default' ? variant : undefined}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { createPositionedPopover } from '@dryui/primitives';
4
+ import { createAnchoredPopover, createDismiss } from '@dryui/primitives';
5
5
  import type { Placement } from '@dryui/primitives';
6
6
  import { getPopoverCtx } from './context.svelte.js';
7
7
 
@@ -24,47 +24,21 @@
24
24
 
25
25
  let contentEl = $state<HTMLDivElement>();
26
26
 
27
- const popover = createPositionedPopover({
27
+ const popover = createAnchoredPopover({
28
28
  triggerEl: () => ctx.triggerEl,
29
29
  contentEl: () => contentEl ?? null,
30
+ open: () => ctx.open,
30
31
  placement: () => placement,
31
32
  offset: () => offset
32
33
  });
33
34
 
34
- $effect(() => {
35
- if (!contentEl) return;
36
- if (ctx.open) {
37
- popover.showPopover(contentEl);
38
- } else {
39
- popover.hidePopover(contentEl);
40
- }
41
- });
42
-
43
- $effect(() => {
44
- if (!ctx.open) return;
45
-
46
- function handlePointerDown(event: PointerEvent) {
47
- const target = event.target as Node;
48
- if (contentEl?.contains(target) || ctx.triggerEl?.contains(target)) {
49
- return;
50
- }
51
- ctx.close();
52
- }
53
-
54
- function handleKeydown(event: KeyboardEvent) {
55
- if (event.key !== 'Escape') return;
56
- event.preventDefault();
57
- ctx.close();
58
- ctx.triggerEl?.focus();
59
- }
60
-
61
- document.addEventListener('pointerdown', handlePointerDown);
62
- document.addEventListener('keydown', handleKeydown, true);
63
-
64
- return () => {
65
- document.removeEventListener('pointerdown', handlePointerDown);
66
- document.removeEventListener('keydown', handleKeydown, true);
67
- };
35
+ createDismiss({
36
+ enabled: () => ctx.open,
37
+ onDismiss: () => ctx.close(),
38
+ contentEl: () => contentEl ?? null,
39
+ triggerEl: () => ctx.triggerEl,
40
+ preventDefaultOnEscape: true,
41
+ returnFocusTo: () => ctx.triggerEl
68
42
  });
69
43
  </script>
70
44
 
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
 
4
5
  interface ProgressSegment {
5
6
  value: number;
@@ -97,8 +98,7 @@
97
98
  data-max={max}
98
99
  data-segmented={segments ? '' : undefined}
99
100
  data-has-label={showLabel ? labelPosition : undefined}
100
- data-size={size}
101
- data-color={color}
101
+ {...variantAttrs({ size, color })}
102
102
  data-with-label={showLabel ? '' : undefined}
103
103
  {...rest}
104
104
  >
@@ -1,2 +1,14 @@
1
- export type { PromptInputProps } from '@dryui/primitives';
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ButtonSize, ButtonVariant } from '../button/index.js';
4
+ export interface PromptInputProps extends HTMLAttributes<HTMLDivElement> {
5
+ value?: string;
6
+ placeholder?: string;
7
+ disabled?: boolean;
8
+ submitLabel?: string;
9
+ submitSize?: ButtonSize;
10
+ submitVariant?: ButtonVariant;
11
+ onpromptsubmit?: (value: string) => void;
12
+ actions?: Snippet;
13
+ }
2
14
  export { default as PromptInput } from './prompt-input-button-textarea.svelte';
@@ -2,12 +2,15 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  import Button from '../button/button.svelte';
5
+ import type { ButtonSize, ButtonVariant } from '../button/index.js';
5
6
 
6
7
  interface Props extends HTMLAttributes<HTMLDivElement> {
7
8
  value?: string;
8
9
  placeholder?: string;
9
10
  disabled?: boolean;
10
11
  submitLabel?: string;
12
+ submitSize?: ButtonSize;
13
+ submitVariant?: ButtonVariant;
11
14
  onpromptsubmit?: (value: string) => void;
12
15
  actions?: Snippet;
13
16
  }
@@ -17,6 +20,8 @@
17
20
  placeholder = 'Type a message...',
18
21
  disabled = false,
19
22
  submitLabel = 'Send',
23
+ submitSize = 'md',
24
+ submitVariant = 'solid',
20
25
  onpromptsubmit,
21
26
  actions,
22
27
  class: className,
@@ -79,7 +84,14 @@
79
84
  {#if actions}
80
85
  {@render actions()}
81
86
  {/if}
82
- <Button variant="solid" type="button" {disabled} onclick={submit} aria-label={submitLabel}>
87
+ <Button
88
+ variant={submitVariant}
89
+ size={submitSize}
90
+ type="button"
91
+ {disabled}
92
+ onclick={submit}
93
+ aria-label={submitLabel}
94
+ >
83
95
  <span data-prompt-submit-label>{submitLabel}</span>
84
96
  <svg
85
97
  width="20"
@@ -1,10 +1,13 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ButtonSize, ButtonVariant } from '../button/index.js';
3
4
  interface Props extends HTMLAttributes<HTMLDivElement> {
4
5
  value?: string;
5
6
  placeholder?: string;
6
7
  disabled?: boolean;
7
8
  submitLabel?: string;
9
+ submitSize?: ButtonSize;
10
+ submitVariant?: ButtonVariant;
8
11
  onpromptsubmit?: (value: string) => void;
9
12
  actions?: Snippet;
10
13
  }
@@ -16,15 +16,33 @@
16
16
  ...rest
17
17
  }: Props = $props();
18
18
 
19
- function applyStyles(node: HTMLSpanElement) {
19
+ function setup(node: HTMLSpanElement) {
20
20
  $effect(() => {
21
21
  node.style.setProperty('--dry-shimmer-color', color);
22
22
  node.style.setProperty('--dry-shimmer-duration', `${duration}s`);
23
23
  });
24
+
25
+ $effect(() => {
26
+ if (typeof IntersectionObserver === 'undefined') {
27
+ node.dataset.active = '';
28
+ return;
29
+ }
30
+ const io = new IntersectionObserver(
31
+ (entries) => {
32
+ for (const entry of entries) {
33
+ if (entry.isIntersecting) node.dataset.active = '';
34
+ else delete node.dataset.active;
35
+ }
36
+ },
37
+ { rootMargin: '100px' }
38
+ );
39
+ io.observe(node);
40
+ return () => io.disconnect();
41
+ });
24
42
  }
25
43
  </script>
26
44
 
27
- <span data-shimmer class={className} {...rest} {@attach applyStyles}>
45
+ <span data-shimmer class={className} {...rest} {@attach setup}>
28
46
  <span data-shimmer-base>
29
47
  {@render children()}
30
48
  </span>
@@ -40,6 +58,8 @@
40
58
  grid-column: var(--dry-shimmer-column, auto);
41
59
  justify-self: var(--dry-shimmer-justify-self, auto);
42
60
  isolation: isolate;
61
+ content-visibility: auto;
62
+ contain-intrinsic-size: auto;
43
63
  }
44
64
 
45
65
  [data-shimmer-base],
@@ -62,8 +82,13 @@
62
82
  mask-size: 200% 100%;
63
83
  -webkit-mask-repeat: no-repeat;
64
84
  mask-repeat: no-repeat;
65
- will-change: mask-position;
66
85
  animation: shimmer-sweep var(--dry-shimmer-duration, 3s) linear infinite;
86
+ animation-play-state: paused;
87
+ }
88
+
89
+ [data-shimmer][data-active] [data-shimmer-streak] {
90
+ animation-play-state: running;
91
+ will-change: mask-position;
67
92
  }
68
93
 
69
94
  @keyframes shimmer-sweep {
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLInputAttributes } from 'svelte/elements';
4
- import { getFormControlCtx } from '@dryui/primitives';
4
+ import { getFormControlCtx, variantAttrs } from '@dryui/primitives';
5
5
 
6
6
  interface Props extends Omit<HTMLInputAttributes, 'size'> {
7
7
  value?: number;
@@ -42,7 +42,7 @@
42
42
  }
43
43
  </script>
44
44
 
45
- <span class="wrapper" data-variant={variant} data-size={size} use:applyStyles>
45
+ <span class="wrapper" {...variantAttrs({ variant, size })} use:applyStyles>
46
46
  <input
47
47
  type="range"
48
48
  bind:value
@@ -57,8 +57,7 @@
57
57
  aria-errormessage={ctx?.errorMessageId}
58
58
  data-disabled={isDisabled || undefined}
59
59
  data-orientation={orientation}
60
- data-variant={variant}
61
- data-size={size}
60
+ {...variantAttrs({ variant, size })}
62
61
  class={className}
63
62
  {...rest}
64
63
  />
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
 
4
5
  interface Props extends HTMLAttributes<HTMLDivElement> {
5
6
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
@@ -11,8 +12,7 @@
11
12
 
12
13
  <div
13
14
  aria-hidden="true"
14
- data-axis={axis}
15
- data-size={size || undefined}
15
+ {...variantAttrs({ axis, size: size || undefined })}
16
16
  class={className}
17
17
  {...rest}
18
18
  ></div>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { SVGAttributes } from 'svelte/elements';
3
+ import { variantAttrs } from '@dryui/primitives';
3
4
 
4
5
  interface Props extends SVGAttributes<SVGSVGElement> {
5
6
  size?: 'sm' | 'md' | 'lg';
@@ -22,8 +23,7 @@
22
23
  viewBox="0 0 24 24"
23
24
  fill="none"
24
25
  xmlns="http://www.w3.org/2000/svg"
25
- data-size={size}
26
- data-color={color}
26
+ {...variantAttrs({ size, color })}
27
27
  class={className}
28
28
  {...rest}
29
29
  >
@@ -19,7 +19,7 @@
19
19
  role="tabpanel"
20
20
  id="{ctx.id}-panel-{value}"
21
21
  aria-labelledby="{ctx.id}-tab-{value}"
22
- tabindex={0}
22
+ tabindex={isSelected ? 0 : -1}
23
23
  data-state={isSelected ? 'active' : 'inactive'}
24
24
  data-tabs-content
25
25
  hidden={!isSelected || undefined}
@@ -76,6 +76,7 @@
76
76
  display: grid;
77
77
  grid-auto-flow: column;
78
78
  grid-auto-columns: max-content;
79
+ justify-content: var(--dry-tabs-list-justify, start);
79
80
  border-bottom: 1px solid var(--dry-color-stroke-weak);
80
81
  gap: 0;
81
82
  }
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import { variantAttrs } from '@dryui/primitives';
4
5
  import Button from '../button/button.svelte';
5
6
  import { resolveAlias } from '../internal/color-aliases.js';
6
7
  import type { TagColor } from './index.js';
@@ -31,9 +32,7 @@
31
32
  <span
32
33
  class={className}
33
34
  data-tag
34
- data-variant={variant}
35
- data-color={resolvedColor}
36
- data-size={size}
35
+ {...variantAttrs({ variant, color: resolvedColor, size })}
37
36
  {...rest}
38
37
  >
39
38
  {@render children()}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
+ import { variantAttrs } from '@dryui/primitives';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLElement> {
6
7
  as?: 'p' | 'span' | 'div';
@@ -28,21 +29,13 @@
28
29
  {#if as === 'span'}
29
30
  <span
30
31
  class={className}
31
- data-color={color}
32
- data-size={size}
33
- data-font={font}
34
- data-weight={weight || undefined}
35
- data-variant={variant}
32
+ {...variantAttrs({ color, size, font, weight: weight || undefined, variant })}
36
33
  {...rest}>{@render children()}</span
37
34
  >
38
35
  {:else if as === 'div'}
39
36
  <div
40
37
  class={className}
41
- data-color={color}
42
- data-size={size}
43
- data-font={font}
44
- data-weight={weight || undefined}
45
- data-variant={variant}
38
+ {...variantAttrs({ color, size, font, weight: weight || undefined, variant })}
46
39
  {...rest}
47
40
  >
48
41
  {@render children()}
@@ -50,11 +43,7 @@
50
43
  {:else}
51
44
  <p
52
45
  class={className}
53
- data-color={color}
54
- data-size={size}
55
- data-font={font}
56
- data-weight={weight || undefined}
57
- data-variant={variant}
46
+ {...variantAttrs({ color, size, font, weight: weight || undefined, variant })}
58
47
  {...rest}
59
48
  >
60
49
  {@render children()}
@@ -65,21 +65,27 @@
65
65
  --dry-color-bg-brand: var(--dry-color-fill-brand);
66
66
  --dry-color-bg-inverse: #ffffff;
67
67
 
68
- /* ─── Component: Toggle ───────────────────────────────────────── */
69
- --dry-toggle-track-bg: #ffffff0f;
70
- --dry-toggle-track-stroke: #ffffff99;
71
- --dry-toggle-selected-bg: #a3b2ff;
72
- --dry-toggle-selected-stroke: #a3b2ff;
68
+ /* ─── Component: Toggle ─────────────────────────────────────────
69
+ Derived from semantic tokens so brand overrides (e.g. a consumer
70
+ retheming --dry-color-fill-brand) flow through to toggles too.
71
+ Thumb bg is intentionally a neutral literal — it's a surface knob,
72
+ not a brand-tinted element. */
73
+ --dry-toggle-track-bg: var(--dry-color-fill-brand-weak);
74
+ --dry-toggle-track-stroke: var(--dry-color-stroke-strong);
75
+ --dry-toggle-selected-bg: var(--dry-color-fill-brand);
76
+ --dry-toggle-selected-stroke: var(--dry-color-fill-brand);
73
77
  --dry-toggle-thumb-bg: #292b33;
74
- --dry-toggle-hover-bg: #ffffff0f;
75
- --dry-toggle-press-bg: #ffffff1f;
76
- --dry-toggle-disabled-fill: #ffffff1f;
77
- --dry-toggle-disabled-stroke: #ffffff1f;
78
- --dry-toggle-focus-ring: #a3b2ff;
78
+ --dry-toggle-hover-bg: var(--dry-color-fill-brand-weak);
79
+ --dry-toggle-press-bg: var(--dry-color-fill-active);
80
+ --dry-toggle-disabled-fill: var(--dry-color-stroke-weak);
81
+ --dry-toggle-disabled-stroke: var(--dry-color-stroke-weak);
82
+ --dry-toggle-focus-ring: var(--dry-color-fill-brand);
79
83
  --dry-toggle-label-color: var(--dry-color-text-strong);
80
- --dry-toggle-label-disabled-color: #ffffff1f;
84
+ --dry-toggle-label-disabled-color: var(--dry-color-text-disabled);
81
85
 
82
- /* ─── Component: Beam ─────────────────────────────────────────── */
86
+ /* ─── Component: Beam ─────────────────────────────────────────
87
+ Blend mode is intentionally mode-specific (multiply darkens on
88
+ light bg, screen lightens on dark bg) — not brand-derivable. */
83
89
  --dry-beam-default-blend: screen;
84
90
 
85
91
  /* ─── Status: Error ───────────────────────────────────────────── */
@@ -152,6 +158,8 @@
152
158
  --dry-color-overlay-backdrop: hsla(0, 0%, 0%, 0.6);
153
159
  --dry-color-overlay-backdrop-strong: hsla(0, 0%, 0%, 0.75);
154
160
 
161
+ /* Dark-mode enhancements — intentional. Light mode relies on component-level handling. */
162
+
155
163
  /* ─── Scrollbar ───────────────────────────────────────────────── */
156
164
  --dry-scrollbar-thumb: var(--dry-color-stroke-weak);
157
165
  --dry-scrollbar-thumb-hover: var(--dry-color-stroke-strong);
@@ -275,21 +283,27 @@
275
283
  --dry-color-bg-brand: var(--dry-color-fill-brand);
276
284
  --dry-color-bg-inverse: #ffffff;
277
285
 
278
- /* ─── Component: Toggle ───────────────────────────────────────── */
279
- --dry-toggle-track-bg: #ffffff0f;
280
- --dry-toggle-track-stroke: #ffffff99;
281
- --dry-toggle-selected-bg: #a3b2ff;
282
- --dry-toggle-selected-stroke: #a3b2ff;
286
+ /* ─── Component: Toggle ─────────────────────────────────────────
287
+ Derived from semantic tokens so brand overrides (e.g. a consumer
288
+ retheming --dry-color-fill-brand) flow through to toggles too.
289
+ Thumb bg is intentionally a neutral literal — it's a surface knob,
290
+ not a brand-tinted element. */
291
+ --dry-toggle-track-bg: var(--dry-color-fill-brand-weak);
292
+ --dry-toggle-track-stroke: var(--dry-color-stroke-strong);
293
+ --dry-toggle-selected-bg: var(--dry-color-fill-brand);
294
+ --dry-toggle-selected-stroke: var(--dry-color-fill-brand);
283
295
  --dry-toggle-thumb-bg: #292b33;
284
- --dry-toggle-hover-bg: #ffffff0f;
285
- --dry-toggle-press-bg: #ffffff1f;
286
- --dry-toggle-disabled-fill: #ffffff1f;
287
- --dry-toggle-disabled-stroke: #ffffff1f;
288
- --dry-toggle-focus-ring: #a3b2ff;
296
+ --dry-toggle-hover-bg: var(--dry-color-fill-brand-weak);
297
+ --dry-toggle-press-bg: var(--dry-color-fill-active);
298
+ --dry-toggle-disabled-fill: var(--dry-color-stroke-weak);
299
+ --dry-toggle-disabled-stroke: var(--dry-color-stroke-weak);
300
+ --dry-toggle-focus-ring: var(--dry-color-fill-brand);
289
301
  --dry-toggle-label-color: var(--dry-color-text-strong);
290
- --dry-toggle-label-disabled-color: #ffffff1f;
302
+ --dry-toggle-label-disabled-color: var(--dry-color-text-disabled);
291
303
 
292
- /* ─── Component: Beam ─────────────────────────────────────────── */
304
+ /* ─── Component: Beam ─────────────────────────────────────────
305
+ Blend mode is intentionally mode-specific (multiply darkens on
306
+ light bg, screen lightens on dark bg) — not brand-derivable. */
293
307
  --dry-beam-default-blend: screen;
294
308
 
295
309
  /* ─── Status: Error ───────────────────────────────────────────── */
@@ -363,6 +377,8 @@
363
377
  --dry-color-overlay-backdrop: hsla(0, 0%, 0%, 0.6);
364
378
  --dry-color-overlay-backdrop-strong: hsla(0, 0%, 0%, 0.75);
365
379
 
380
+ /* Dark-mode enhancements — intentional. Light mode relies on component-level handling. */
381
+
366
382
  /* ─── Scrollbar ───────────────────────────────────────────────── */
367
383
  --dry-scrollbar-thumb: var(--dry-color-stroke-weak);
368
384
  --dry-scrollbar-thumb-hover: var(--dry-color-stroke-strong);