@dorsk/tsumikit 0.17.0 → 0.18.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.
@@ -297,6 +297,25 @@
297
297
  border-color: var(--ok);
298
298
  filter: brightness(1.08);
299
299
  }
300
+ /* Other tones on a primary action tint the accent fill rather than repaint the
301
+ text, so the on-accent foreground stays readable (bare tone-as-text would be
302
+ accent-on-accent). success keeps its own filled guard above. */
303
+ .btn-primary.btn-tone-accent,
304
+ .btn-primary.btn-tone-info,
305
+ .btn-primary.btn-tone-warn,
306
+ .btn-primary.btn-tone-danger {
307
+ background: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
308
+ border-color: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
309
+ color: var(--text-on-accent);
310
+ }
311
+ .btn-primary.btn-tone-accent:hover:not(:disabled),
312
+ .btn-primary.btn-tone-info:hover:not(:disabled),
313
+ .btn-primary.btn-tone-warn:hover:not(:disabled),
314
+ .btn-primary.btn-tone-danger:hover:not(:disabled) {
315
+ background: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
316
+ border-color: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
317
+ filter: brightness(1.08);
318
+ }
300
319
 
301
320
  /* Icon-only buttons (IconButton). Square box = consistent 2.25rem tap target. */
302
321
  .btn-icon {
@@ -0,0 +1,111 @@
1
+ <script lang="ts" module>
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ // Navigation trail (e.g. "Musique > Artist > Album > Track"). The last item —
10
+ // or any item without an `href` — renders unlinked and is marked
11
+ // aria-current="page". When the trail exceeds `maxItems` the middle collapses
12
+ // to an ellipsis, always keeping the first item and the tail. `separator` is a
13
+ // snippet for custom markup, else a char via the `char` prop.
14
+ import type { Snippet } from 'svelte';
15
+ import Icon from '../atoms/Icon.svelte';
16
+
17
+ let {
18
+ items,
19
+ char,
20
+ separator,
21
+ maxItems = 0
22
+ }: {
23
+ items: BreadcrumbItem[];
24
+ /** Separator character; ignored when the `separator` snippet is set. */
25
+ char?: string;
26
+ separator?: Snippet;
27
+ /** Collapse the middle to an ellipsis when the trail is longer than this
28
+ * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
29
+ maxItems?: number;
30
+ } = $props();
31
+
32
+ type Crumb = BreadcrumbItem | { ellipsis: true };
33
+
34
+ const shown = $derived<Crumb[]>(
35
+ maxItems > 0 && items.length > maxItems
36
+ ? [items[0], { ellipsis: true }, ...items.slice(items.length - (maxItems - 1))]
37
+ : items
38
+ );
39
+ </script>
40
+
41
+ <nav aria-label="breadcrumb" data-tsu="Breadcrumb">
42
+ <ol class="crumbs">
43
+ {#each shown as item, i (i)}
44
+ <li class="crumb">
45
+ {#if 'ellipsis' in item}
46
+ <span class="ellipsis" aria-hidden="true">…</span>
47
+ {:else if item.href && i < shown.length - 1}
48
+ <a class="link" href={item.href}>{item.label}</a>
49
+ {:else}
50
+ <span class="current" aria-current="page">{item.label}</span>
51
+ {/if}
52
+ {#if i < shown.length - 1}
53
+ <span class="sep" aria-hidden="true">
54
+ {#if separator}{@render separator()}{:else if char}{char}{:else}<Icon
55
+ name="chevron-right"
56
+ size={14}
57
+ />{/if}
58
+ </span>
59
+ {/if}
60
+ </li>
61
+ {/each}
62
+ </ol>
63
+ </nav>
64
+
65
+ <style>
66
+ .crumbs {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ align-items: center;
70
+ gap: var(--sp-1);
71
+ margin: 0;
72
+ padding: 0;
73
+ list-style: none;
74
+ font-size: var(--fs-sm);
75
+ }
76
+ .crumb {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: var(--sp-1);
80
+ min-width: 0;
81
+ }
82
+ .link {
83
+ color: var(--text-muted);
84
+ text-decoration: none;
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ max-width: 16ch;
89
+ transition: color 0.12s var(--ease);
90
+ }
91
+ .link:hover {
92
+ color: var(--text);
93
+ text-decoration: underline;
94
+ }
95
+ .current {
96
+ color: var(--text);
97
+ font-weight: var(--fw-medium);
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ max-width: 24ch;
102
+ }
103
+ .ellipsis {
104
+ color: var(--text-muted);
105
+ }
106
+ .sep {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ color: var(--text-subtle, var(--text-muted));
110
+ }
111
+ </style>
@@ -0,0 +1,17 @@
1
+ export interface BreadcrumbItem {
2
+ label: string;
3
+ href?: string;
4
+ }
5
+ import type { Snippet } from 'svelte';
6
+ type $$ComponentProps = {
7
+ items: BreadcrumbItem[];
8
+ /** Separator character; ignored when the `separator` snippet is set. */
9
+ char?: string;
10
+ separator?: Snippet;
11
+ /** Collapse the middle to an ellipsis when the trail is longer than this
12
+ * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
13
+ maxItems?: number;
14
+ };
15
+ declare const Breadcrumb: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type Breadcrumb = ReturnType<typeof Breadcrumb>;
17
+ export default Breadcrumb;
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ export { default as NavSection } from './components/layouts/NavSection.svelte';
26
26
  export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
27
27
  export { default as Stack } from './components/layouts/Stack.svelte';
28
28
  export { type AccordionItem, default as Accordion, } from './components/molecules/Accordion.svelte';
29
+ export { type BreadcrumbItem, default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
29
30
  export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
30
31
  export { default as CopyButton } from './components/molecules/CopyButton.svelte';
31
32
  export { default as Dropzone } from './components/molecules/Dropzone.svelte';
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ export { default as NavSection } from './components/layouts/NavSection.svelte';
33
33
  export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
34
34
  export { default as Stack } from './components/layouts/Stack.svelte';
35
35
  export { default as Accordion, } from './components/molecules/Accordion.svelte';
36
+ export { default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
36
37
  export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
37
38
  export { default as CopyButton } from './components/molecules/CopyButton.svelte';
38
39
  export { default as Dropzone } from './components/molecules/Dropzone.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",