@r2digisolutions/components 0.6.9 → 0.7.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,37 @@
1
+ import type { Snippet } from 'svelte';
2
+ export interface TreePanelNode {
3
+ id: string;
4
+ label: string;
5
+ /** Emoji or short glyph shown instead of the default folder / branch icon. */
6
+ icon?: string | null;
7
+ /** Extra column value (sort order, count, …). */
8
+ meta?: string | number | null;
9
+ children?: TreePanelNode[];
10
+ }
11
+ interface TreePanelProps {
12
+ items?: TreePanelNode[];
13
+ collapsedIds?: string[];
14
+ emptyMessage?: string;
15
+ hierarchyLabel?: string;
16
+ metaLabel?: string;
17
+ actionsLabel?: string;
18
+ showMeta?: boolean;
19
+ addChildLabel?: string;
20
+ editLabel?: string;
21
+ deleteLabel?: string;
22
+ expandLabel?: string;
23
+ collapseLabel?: string;
24
+ onAddChild?: (node: TreePanelNode) => void;
25
+ onEdit?: (node: TreePanelNode) => void;
26
+ onDelete?: (node: TreePanelNode) => void;
27
+ /** Extra content after the label (badges, counts). */
28
+ meta?: Snippet<[TreePanelNode, number]>;
29
+ /** Replaces the default add / edit / delete buttons. */
30
+ trailing?: Snippet<[TreePanelNode, number]>;
31
+ leading?: Snippet<[TreePanelNode, number]>;
32
+ empty?: Snippet;
33
+ class?: string;
34
+ }
35
+ declare const TreePanel: import("svelte").Component<TreePanelProps, {}, "collapsedIds">;
36
+ type TreePanel = ReturnType<typeof TreePanel>;
37
+ export default TreePanel;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import TreePanel, { type TreePanelNode } from './TreePanel.svelte';
3
+
4
+ let {
5
+ showMeta = false,
6
+ empty = false
7
+ }: {
8
+ showMeta?: boolean;
9
+ empty?: boolean;
10
+ } = $props();
11
+
12
+ const catalog: TreePanelNode[] = [
13
+ {
14
+ id: 'products',
15
+ label: 'Productos',
16
+ children: [
17
+ { id: 'lighting', label: 'Iluminación', meta: 1 },
18
+ { id: 'furniture', label: 'Mobiliario', meta: 2 }
19
+ ]
20
+ },
21
+ {
22
+ id: 'services',
23
+ label: 'Servicios',
24
+ children: [
25
+ { id: 'consulting', label: 'Consultoría', meta: 1 },
26
+ { id: 'renovation', label: 'Reforma', meta: 2 }
27
+ ]
28
+ }
29
+ ];
30
+
31
+ let collapsedIds = $state<string[]>([]);
32
+ let lastAction = $state('—');
33
+ </script>
34
+
35
+ <div class="max-w-2xl">
36
+ <TreePanel
37
+ items={empty ? [] : catalog}
38
+ bind:collapsedIds
39
+ {showMeta}
40
+ emptyMessage="No hay categorías todavía."
41
+ hierarchyLabel="Jerarquía"
42
+ metaLabel="Orden"
43
+ actionsLabel="Acciones"
44
+ addChildLabel="Añadir subcategoría"
45
+ editLabel="Editar"
46
+ deleteLabel="Eliminar"
47
+ onAddChild={(node) => (lastAction = `Añadir hijo de ${node.label}`)}
48
+ onEdit={(node) => (lastAction = `Editar ${node.label}`)}
49
+ onDelete={(node) => (lastAction = `Eliminar ${node.label}`)}
50
+ />
51
+ <p class="mt-3 text-xs text-secondary">
52
+ Última acción: <span class="font-medium text-primary">{lastAction}</span>
53
+ </p>
54
+ </div>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ showMeta?: boolean;
3
+ empty?: boolean;
4
+ };
5
+ declare const TreePanelStory: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type TreePanelStory = ReturnType<typeof TreePanelStory>;
7
+ export default TreePanelStory;
package/dist/index.d.ts CHANGED
@@ -227,6 +227,8 @@ export { default as Carousel } from './components/molecules/Carousel/Carousel.sv
227
227
  export type { CarouselItem } from './components/molecules/Carousel/Carousel.svelte';
228
228
  export { default as TreeView } from './components/molecules/TreeView/TreeView.svelte';
229
229
  export type { TreeNode } from './components/molecules/TreeView/TreeView.svelte';
230
+ export { default as TreePanel } from './components/molecules/TreePanel/TreePanel.svelte';
231
+ export type { TreePanelNode } from './components/molecules/TreePanel/TreePanel.svelte';
230
232
  export { default as TreeSelect } from './components/molecules/TreeSelect/TreeSelect.svelte';
231
233
  export { default as Cascader } from './components/molecules/Cascader/Cascader.svelte';
232
234
  export type { CascaderOption } from './components/molecules/Cascader/Cascader.svelte';
@@ -818,7 +820,7 @@ export { default as CanvasEditorTemplate } from './components/templates/CanvasEd
818
820
  export { default as VideoEditorTemplate } from './components/templates/VideoEditorTemplate/VideoEditorTemplate.svelte';
819
821
  export { default as AudioEditorTemplate } from './components/templates/AudioEditorTemplate/AudioEditorTemplate.svelte';
820
822
  export { themeStore } from './utils/theme.svelte.js';
821
- export type { Theme } from './utils/theme.svelte.js';
823
+ export type { Theme, ThemePalette } from './utils/theme.svelte.js';
822
824
  export { createId } from './utils/id.js';
823
825
  export { resolveAccessor, accessorPatchKey, resolveRowKey } from './utils/columnAccessor.js';
824
826
  export type { ColumnAccessor, RowKey } from './utils/columnAccessor.js';
package/dist/index.js CHANGED
@@ -176,6 +176,7 @@ export { default as ConfirmDialog } from './components/molecules/ConfirmDialog/C
176
176
  export { default as ContextMenu } from './components/molecules/ContextMenu/ContextMenu.svelte';
177
177
  export { default as Carousel } from './components/molecules/Carousel/Carousel.svelte';
178
178
  export { default as TreeView } from './components/molecules/TreeView/TreeView.svelte';
179
+ export { default as TreePanel } from './components/molecules/TreePanel/TreePanel.svelte';
179
180
  export { default as TreeSelect } from './components/molecules/TreeSelect/TreeSelect.svelte';
180
181
  export { default as Cascader } from './components/molecules/Cascader/Cascader.svelte';
181
182
  export { default as MaskedInput } from './components/molecules/MaskedInput/MaskedInput.svelte';
package/dist/styles.css CHANGED
@@ -10,51 +10,72 @@
10
10
  ─────────────────────────────────────────────────────────────────────────── */
11
11
 
12
12
  @theme {
13
- /* Brand colors */
14
- --color-brand-50: oklch(97% 0.02 265);
15
- --color-brand-100: oklch(93% 0.05 265);
16
- --color-brand-200: oklch(86% 0.1 265);
17
- --color-brand-300: oklch(76% 0.15 265);
18
- --color-brand-400: oklch(65% 0.2 265);
19
- --color-brand-500: oklch(55% 0.25 265);
20
- --color-brand-600: oklch(47% 0.26 265);
21
- --color-brand-700: oklch(39% 0.24 265);
22
- --color-brand-800: oklch(31% 0.2 265);
23
- --color-brand-900: oklch(22% 0.14 265);
24
- --color-brand-950: oklch(15% 0.08 265);
25
-
26
- /* Semantic surface tokens */
27
- --color-surface: var(--surface);
28
- --color-surface-elevated: var(--surface-elevated);
29
- --color-surface-overlay: var(--surface-overlay);
30
- --color-border: var(--border);
31
- --color-border-strong: var(--border-strong);
32
- --color-text-primary: var(--text-primary);
33
- --color-text-secondary: var(--text-secondary);
34
- --color-text-muted: var(--text-muted);
35
- --color-text-inverse: var(--text-inverse);
36
-
37
- /* Typography */
38
- --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
39
- --font-mono: 'JetBrains Mono', ui-monospace, monospace;
40
-
41
- /* Radii */
42
- --radius-sm: 0.375rem;
43
- --radius-md: 0.5rem;
44
- --radius-lg: 0.75rem;
45
- --radius-xl: 1rem;
46
- --radius-2xl: 1.5rem;
47
- --radius-full: 9999px;
48
-
49
- /* Shadows */
50
- --shadow-sm: 0 1px 3px 0 oklch(0% 0 0 / 0.08), 0 1px 2px -1px oklch(0% 0 0 / 0.06);
51
- --shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.1), 0 2px 4px -2px oklch(0% 0 0 / 0.08);
52
- --shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.12), 0 4px 6px -4px oklch(0% 0 0 / 0.08);
53
- --shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.14), 0 8px 10px -6px oklch(0% 0 0 / 0.08);
54
-
55
- /* Transitions */
56
- --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
57
- --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
13
+ /* Brand colors */
14
+ --color-brand-50: oklch(97% 0.02 265);
15
+ --color-brand-100: oklch(93% 0.05 265);
16
+ --color-brand-200: oklch(86% 0.1 265);
17
+ --color-brand-300: oklch(76% 0.15 265);
18
+ --color-brand-400: oklch(65% 0.2 265);
19
+ --color-brand-500: oklch(55% 0.25 265);
20
+ --color-brand-600: oklch(47% 0.26 265);
21
+ --color-brand-700: oklch(39% 0.24 265);
22
+ --color-brand-800: oklch(31% 0.2 265);
23
+ --color-brand-900: oklch(22% 0.14 265);
24
+ --color-brand-950: oklch(15% 0.08 265);
25
+
26
+ /* Semantic surface tokens */
27
+ --color-surface: var(--surface);
28
+ --color-surface-elevated: var(--surface-elevated);
29
+ --color-surface-overlay: var(--surface-overlay);
30
+ --color-border: var(--border);
31
+ --color-border-strong: var(--border-strong);
32
+ --color-text-primary: var(--text-primary);
33
+ --color-text-secondary: var(--text-secondary);
34
+ --color-text-muted: var(--text-muted);
35
+ --color-text-inverse: var(--text-inverse);
36
+
37
+ /* Typography */
38
+ --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
39
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
40
+
41
+ /* Radii */
42
+ --radius-sm: 0.375rem;
43
+ --radius-md: 0.5rem;
44
+ --radius-lg: 0.75rem;
45
+ --radius-xl: 1rem;
46
+ --radius-2xl: 1.5rem;
47
+ --radius-full: 9999px;
48
+
49
+ /* Shadows */
50
+ --shadow-sm: 0 1px 3px 0 oklch(0% 0 0 / 0.08), 0 1px 2px -1px oklch(0% 0 0 / 0.06);
51
+ --shadow-md: 0 4px 6px -1px oklch(0% 0 0 / 0.1), 0 2px 4px -2px oklch(0% 0 0 / 0.08);
52
+ --shadow-lg: 0 10px 15px -3px oklch(0% 0 0 / 0.12), 0 4px 6px -4px oklch(0% 0 0 / 0.08);
53
+ --shadow-xl: 0 20px 25px -5px oklch(0% 0 0 / 0.14), 0 8px 10px -6px oklch(0% 0 0 / 0.08);
54
+
55
+ /* Transitions */
56
+ --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
57
+ --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
58
+ }
59
+
60
+ /* ─────────────────────────────────────────────────────────────────────────────
61
+ Gray palette — <html data-theme="slate" | "neutral">
62
+ slate (default): cool indigo cast (hue 265)
63
+ neutral: chroma 0, same family as Tailwind `neutral`
64
+ ─────────────────────────────────────────────────────────────────────────── */
65
+
66
+ :root {
67
+ --r2-gray-h: 265;
68
+ --r2-gray-c: 0.02;
69
+ }
70
+
71
+ :root[data-theme='slate'] {
72
+ --r2-gray-h: 265;
73
+ --r2-gray-c: 0.02;
74
+ }
75
+
76
+ :root[data-theme='neutral'] {
77
+ --r2-gray-h: 0;
78
+ --r2-gray-c: 0;
58
79
  }
59
80
 
60
81
  /* ─────────────────────────────────────────────────────────────────────────────
@@ -62,46 +83,46 @@
62
83
  ─────────────────────────────────────────────────────────────────────────── */
63
84
 
64
85
  :root {
65
- color-scheme: light;
66
-
67
- --surface: oklch(99% 0.005 265);
68
- --surface-elevated: oklch(100% 0 0);
69
- --surface-overlay: oklch(97% 0.01 265);
70
- --border: oklch(88% 0.02 265);
71
- --border-strong: oklch(75% 0.04 265);
72
- --text-primary: oklch(15% 0.02 265);
73
- --text-secondary: oklch(35% 0.03 265);
74
- --text-muted: oklch(45% 0.02 265);
75
- --text-inverse: oklch(99% 0 0);
76
-
77
- /* Alert tones */
78
- --alert-info-bg: oklch(96% 0.03 240);
79
- --alert-info-border: oklch(82% 0.06 240);
80
- --alert-info-fg: oklch(28% 0.08 240);
81
- --alert-info-muted: oklch(40% 0.06 240);
82
- --alert-info-icon: oklch(52% 0.14 240);
83
- --alert-info-accent: oklch(55% 0.16 240);
84
-
85
- --alert-success-bg: oklch(96% 0.04 150);
86
- --alert-success-border: oklch(82% 0.07 150);
87
- --alert-success-fg: oklch(28% 0.08 150);
88
- --alert-success-muted: oklch(38% 0.06 150);
89
- --alert-success-icon: oklch(50% 0.14 150);
90
- --alert-success-accent: oklch(55% 0.16 150);
91
-
92
- --alert-warning-bg: oklch(96% 0.05 85);
93
- --alert-warning-border: oklch(84% 0.1 85);
94
- --alert-warning-fg: oklch(30% 0.08 75);
95
- --alert-warning-muted: oklch(42% 0.07 75);
96
- --alert-warning-icon: oklch(58% 0.15 75);
97
- --alert-warning-accent: oklch(68% 0.16 75);
98
-
99
- --alert-error-bg: oklch(96% 0.03 25);
100
- --alert-error-border: oklch(84% 0.08 25);
101
- --alert-error-fg: oklch(30% 0.1 25);
102
- --alert-error-muted: oklch(42% 0.08 25);
103
- --alert-error-icon: oklch(55% 0.18 25);
104
- --alert-error-accent: oklch(58% 0.2 25);
86
+ color-scheme: light;
87
+
88
+ --surface: oklch(99% calc(var(--r2-gray-c) * 0.25) var(--r2-gray-h));
89
+ --surface-elevated: oklch(100% 0 0);
90
+ --surface-overlay: oklch(97% calc(var(--r2-gray-c) * 0.5) var(--r2-gray-h));
91
+ --border: oklch(88% var(--r2-gray-c) var(--r2-gray-h));
92
+ --border-strong: oklch(75% calc(var(--r2-gray-c) * 2) var(--r2-gray-h));
93
+ --text-primary: oklch(15% var(--r2-gray-c) var(--r2-gray-h));
94
+ --text-secondary: oklch(35% calc(var(--r2-gray-c) * 1.5) var(--r2-gray-h));
95
+ --text-muted: oklch(45% var(--r2-gray-c) var(--r2-gray-h));
96
+ --text-inverse: oklch(99% 0 0);
97
+
98
+ /* Alert tones */
99
+ --alert-info-bg: oklch(96% 0.03 240);
100
+ --alert-info-border: oklch(82% 0.06 240);
101
+ --alert-info-fg: oklch(28% 0.08 240);
102
+ --alert-info-muted: oklch(40% 0.06 240);
103
+ --alert-info-icon: oklch(52% 0.14 240);
104
+ --alert-info-accent: oklch(55% 0.16 240);
105
+
106
+ --alert-success-bg: oklch(96% 0.04 150);
107
+ --alert-success-border: oklch(82% 0.07 150);
108
+ --alert-success-fg: oklch(28% 0.08 150);
109
+ --alert-success-muted: oklch(38% 0.06 150);
110
+ --alert-success-icon: oklch(50% 0.14 150);
111
+ --alert-success-accent: oklch(55% 0.16 150);
112
+
113
+ --alert-warning-bg: oklch(96% 0.05 85);
114
+ --alert-warning-border: oklch(84% 0.1 85);
115
+ --alert-warning-fg: oklch(30% 0.08 75);
116
+ --alert-warning-muted: oklch(42% 0.07 75);
117
+ --alert-warning-icon: oklch(58% 0.15 75);
118
+ --alert-warning-accent: oklch(68% 0.16 75);
119
+
120
+ --alert-error-bg: oklch(96% 0.03 25);
121
+ --alert-error-border: oklch(84% 0.08 25);
122
+ --alert-error-fg: oklch(30% 0.1 25);
123
+ --alert-error-muted: oklch(42% 0.08 25);
124
+ --alert-error-icon: oklch(55% 0.18 25);
125
+ --alert-error-accent: oklch(58% 0.2 25);
105
126
  }
106
127
 
107
128
  /* ─────────────────────────────────────────────────────────────────────────────
@@ -109,46 +130,46 @@
109
130
  ─────────────────────────────────────────────────────────────────────────── */
110
131
 
111
132
  :root.dark {
112
- color-scheme: dark;
113
-
114
- --surface: oklch(14% 0.02 265);
115
- --surface-elevated: oklch(18% 0.025 265);
116
- --surface-overlay: oklch(20% 0.03 265);
117
- --border: oklch(28% 0.03 265);
118
- --border-strong: oklch(40% 0.04 265);
119
- --text-primary: oklch(96% 0.01 265);
120
- --text-secondary: oklch(75% 0.03 265);
121
- --text-muted: oklch(55% 0.03 265);
122
- --text-inverse: oklch(15% 0.02 265);
123
-
124
- /* Alert tones */
125
- --alert-info-bg: oklch(24% 0.04 240);
126
- --alert-info-border: oklch(38% 0.06 240);
127
- --alert-info-fg: oklch(92% 0.03 240);
128
- --alert-info-muted: oklch(78% 0.04 240);
129
- --alert-info-icon: oklch(72% 0.12 240);
130
- --alert-info-accent: oklch(65% 0.14 240);
131
-
132
- --alert-success-bg: oklch(24% 0.04 150);
133
- --alert-success-border: oklch(38% 0.06 150);
134
- --alert-success-fg: oklch(92% 0.03 150);
135
- --alert-success-muted: oklch(78% 0.04 150);
136
- --alert-success-icon: oklch(72% 0.12 150);
137
- --alert-success-accent: oklch(65% 0.14 150);
138
-
139
- --alert-warning-bg: oklch(25% 0.04 85);
140
- --alert-warning-border: oklch(40% 0.07 85);
141
- --alert-warning-fg: oklch(93% 0.03 85);
142
- --alert-warning-muted: oklch(80% 0.04 85);
143
- --alert-warning-icon: oklch(78% 0.12 85);
144
- --alert-warning-accent: oklch(72% 0.14 85);
145
-
146
- --alert-error-bg: oklch(24% 0.05 25);
147
- --alert-error-border: oklch(40% 0.08 25);
148
- --alert-error-fg: oklch(93% 0.03 25);
149
- --alert-error-muted: oklch(80% 0.04 25);
150
- --alert-error-icon: oklch(72% 0.14 25);
151
- --alert-error-accent: oklch(65% 0.18 25);
133
+ color-scheme: dark;
134
+
135
+ --surface: oklch(14% var(--r2-gray-c) var(--r2-gray-h));
136
+ --surface-elevated: oklch(18% calc(var(--r2-gray-c) * 1.25) var(--r2-gray-h));
137
+ --surface-overlay: oklch(20% calc(var(--r2-gray-c) * 1.5) var(--r2-gray-h));
138
+ --border: oklch(28% calc(var(--r2-gray-c) * 1.5) var(--r2-gray-h));
139
+ --border-strong: oklch(40% calc(var(--r2-gray-c) * 2) var(--r2-gray-h));
140
+ --text-primary: oklch(96% calc(var(--r2-gray-c) * 0.5) var(--r2-gray-h));
141
+ --text-secondary: oklch(75% calc(var(--r2-gray-c) * 1.5) var(--r2-gray-h));
142
+ --text-muted: oklch(55% calc(var(--r2-gray-c) * 1.5) var(--r2-gray-h));
143
+ --text-inverse: oklch(15% var(--r2-gray-c) var(--r2-gray-h));
144
+
145
+ /* Alert tones */
146
+ --alert-info-bg: oklch(24% 0.04 240);
147
+ --alert-info-border: oklch(38% 0.06 240);
148
+ --alert-info-fg: oklch(92% 0.03 240);
149
+ --alert-info-muted: oklch(78% 0.04 240);
150
+ --alert-info-icon: oklch(72% 0.12 240);
151
+ --alert-info-accent: oklch(65% 0.14 240);
152
+
153
+ --alert-success-bg: oklch(24% 0.04 150);
154
+ --alert-success-border: oklch(38% 0.06 150);
155
+ --alert-success-fg: oklch(92% 0.03 150);
156
+ --alert-success-muted: oklch(78% 0.04 150);
157
+ --alert-success-icon: oklch(72% 0.12 150);
158
+ --alert-success-accent: oklch(65% 0.14 150);
159
+
160
+ --alert-warning-bg: oklch(25% 0.04 85);
161
+ --alert-warning-border: oklch(40% 0.07 85);
162
+ --alert-warning-fg: oklch(93% 0.03 85);
163
+ --alert-warning-muted: oklch(80% 0.04 85);
164
+ --alert-warning-icon: oklch(78% 0.12 85);
165
+ --alert-warning-accent: oklch(72% 0.14 85);
166
+
167
+ --alert-error-bg: oklch(24% 0.05 25);
168
+ --alert-error-border: oklch(40% 0.08 25);
169
+ --alert-error-fg: oklch(93% 0.03 25);
170
+ --alert-error-muted: oklch(80% 0.04 25);
171
+ --alert-error-icon: oklch(72% 0.14 25);
172
+ --alert-error-accent: oklch(65% 0.18 25);
152
173
  }
153
174
 
154
175
  /* ─────────────────────────────────────────────────────────────────────────────
@@ -156,41 +177,41 @@
156
177
  ─────────────────────────────────────────────────────────────────────────── */
157
178
 
158
179
  @layer base {
159
- *,
160
- *::before,
161
- *::after {
162
- box-sizing: border-box;
163
- }
164
-
165
- html {
166
- -webkit-text-size-adjust: 100%;
167
- -webkit-font-smoothing: antialiased;
168
- -moz-osx-font-smoothing: grayscale;
169
- scroll-behavior: smooth;
170
- }
171
-
172
- body {
173
- margin: 0;
174
- background-color: var(--surface);
175
- color: var(--text-primary);
176
- font-family: var(--font-sans);
177
- line-height: 1.6;
178
- transition:
179
- background-color 0.2s var(--ease-smooth),
180
- color 0.2s var(--ease-smooth);
181
- }
182
-
183
- /* Focus visible styles */
184
- :focus-visible {
185
- outline: 2px solid oklch(55% 0.25 265);
186
- outline-offset: 2px;
187
- border-radius: var(--radius-sm);
188
- }
189
-
190
- /* Selection */
191
- ::selection {
192
- background-color: oklch(55% 0.25 265 / 0.25);
193
- }
180
+ *,
181
+ *::before,
182
+ *::after {
183
+ box-sizing: border-box;
184
+ }
185
+
186
+ html {
187
+ -webkit-text-size-adjust: 100%;
188
+ -webkit-font-smoothing: antialiased;
189
+ -moz-osx-font-smoothing: grayscale;
190
+ scroll-behavior: smooth;
191
+ }
192
+
193
+ body {
194
+ margin: 0;
195
+ background-color: var(--surface);
196
+ color: var(--text-primary);
197
+ font-family: var(--font-sans);
198
+ line-height: 1.6;
199
+ transition:
200
+ background-color 0.2s var(--ease-smooth),
201
+ color 0.2s var(--ease-smooth);
202
+ }
203
+
204
+ /* Focus visible styles */
205
+ :focus-visible {
206
+ outline: 2px solid oklch(55% 0.25 265);
207
+ outline-offset: 2px;
208
+ border-radius: var(--radius-sm);
209
+ }
210
+
211
+ /* Selection */
212
+ ::selection {
213
+ background-color: oklch(55% 0.25 265 / 0.25);
214
+ }
194
215
  }
195
216
 
196
217
  /* ─────────────────────────────────────────────────────────────────────────────
@@ -198,46 +219,70 @@
198
219
  ─────────────────────────────────────────────────────────────────────────── */
199
220
 
200
221
  @layer utilities {
201
- /* Touch-friendly minimum tap target */
202
- .touch-target {
203
- min-height: 44px;
204
- min-width: 44px;
205
- }
206
-
207
- /* Smooth transitions */
208
- .transition-smooth {
209
- transition: all 0.2s var(--ease-smooth);
210
- }
211
-
212
- .transition-spring {
213
- transition: all 0.3s var(--ease-spring);
214
- }
215
-
216
- /* Glass morphism */
217
- .glass {
218
- backdrop-filter: blur(12px) saturate(180%);
219
- background-color: oklch(100% 0 0 / 0.08);
220
- border: 1px solid oklch(100% 0 0 / 0.12);
221
- }
222
-
223
- .dark .glass {
224
- background-color: oklch(0% 0 0 / 0.2);
225
- border-color: oklch(100% 0 0 / 0.08);
226
- }
227
-
228
- /* Surface utilities */
229
- .bg-surface { background-color: var(--surface); }
230
- .bg-surface-elevated { background-color: var(--surface-elevated); }
231
- .bg-surface-overlay { background-color: var(--surface-overlay); }
232
- .border-border { border-color: var(--border); }
233
- .border-border-strong { border-color: var(--border-strong); }
234
- .text-primary { color: var(--text-primary); }
235
- .text-secondary { color: var(--text-secondary); }
236
- .text-muted { color: var(--text-muted); }
237
-
238
- /* SVG chart labels — Tailwind fill-* needs these (text-* alone does not paint <text>) */
239
- .fill-primary { fill: var(--text-primary); }
240
- .fill-secondary { fill: var(--text-secondary); }
241
- .fill-muted { fill: var(--text-muted); }
242
- .stroke-border { stroke: var(--border); }
222
+ /* Touch-friendly minimum tap target */
223
+ .touch-target {
224
+ min-height: 44px;
225
+ min-width: 44px;
226
+ }
227
+
228
+ /* Smooth transitions */
229
+ .transition-smooth {
230
+ transition: all 0.2s var(--ease-smooth);
231
+ }
232
+
233
+ .transition-spring {
234
+ transition: all 0.3s var(--ease-spring);
235
+ }
236
+
237
+ /* Glass morphism */
238
+ .glass {
239
+ backdrop-filter: blur(12px) saturate(180%);
240
+ background-color: oklch(100% 0 0 / 0.08);
241
+ border: 1px solid oklch(100% 0 0 / 0.12);
242
+ }
243
+
244
+ .dark .glass {
245
+ background-color: oklch(0% 0 0 / 0.2);
246
+ border-color: oklch(100% 0 0 / 0.08);
247
+ }
248
+
249
+ /* Surface utilities */
250
+ .bg-surface {
251
+ background-color: var(--surface);
252
+ }
253
+ .bg-surface-elevated {
254
+ background-color: var(--surface-elevated);
255
+ }
256
+ .bg-surface-overlay {
257
+ background-color: var(--surface-overlay);
258
+ }
259
+ .border-border {
260
+ border-color: var(--border);
261
+ }
262
+ .border-border-strong {
263
+ border-color: var(--border-strong);
264
+ }
265
+ .text-primary {
266
+ color: var(--text-primary);
267
+ }
268
+ .text-secondary {
269
+ color: var(--text-secondary);
270
+ }
271
+ .text-muted {
272
+ color: var(--text-muted);
273
+ }
274
+
275
+ /* SVG chart labels — Tailwind fill-* needs these (text-* alone does not paint <text>) */
276
+ .fill-primary {
277
+ fill: var(--text-primary);
278
+ }
279
+ .fill-secondary {
280
+ fill: var(--text-secondary);
281
+ }
282
+ .fill-muted {
283
+ fill: var(--text-muted);
284
+ }
285
+ .stroke-border {
286
+ stroke: var(--border);
287
+ }
243
288
  }
@@ -1,19 +1,33 @@
1
1
  /**
2
- * Theme store — Light/Dark mode management for R2DigiSolutions components.
2
+ * Theme store — light/dark + gray palette for R2DigiSolutions components.
3
+ *
4
+ * Mode: `class="dark"` on `<html>`
5
+ * Palette: `data-theme="slate" | "neutral"` on `<html>`
3
6
  *
4
7
  * - Persists preference to localStorage
5
8
  * - Detects system preference via prefers-color-scheme
6
- * - Applies 'dark' class to <html> element
7
9
  * - Reactive via Svelte 5 runes ($state)
8
10
  */
9
11
  export type Theme = 'light' | 'dark' | 'system';
12
+ export type ThemePalette = 'slate' | 'neutral';
10
13
  declare class ThemeStore {
11
14
  #private;
12
15
  constructor();
13
16
  get theme(): Theme;
17
+ get palette(): ThemePalette;
14
18
  get resolved(): 'light' | 'dark';
15
19
  get isDark(): boolean;
16
20
  set(theme: Theme): void;
21
+ /**
22
+ * Gray family for surfaces, borders and body text.
23
+ * `slate` = cool (hue 265). `neutral` = chroma 0, no blue cast.
24
+ *
25
+ * Pass `{ persist: false }` for a page-level override that does not
26
+ * stick in localStorage.
27
+ */
28
+ setPalette(palette: ThemePalette, opts?: {
29
+ persist?: boolean;
30
+ }): void;
17
31
  toggle(): void;
18
32
  }
19
33
  export declare const themeStore: ThemeStore;