@rokkit/ui 1.0.4 → 1.1.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 (59) hide show
  1. package/package.json +7 -6
  2. package/src/MarkdownRenderer.svelte +13 -1
  3. package/src/components/AlertList.svelte +1 -13
  4. package/src/components/Avatar.svelte +1 -18
  5. package/src/components/Badge.svelte +1 -16
  6. package/src/components/BreadCrumbs.svelte +2 -21
  7. package/src/components/Card.svelte +1 -18
  8. package/src/components/Carousel.svelte +2 -27
  9. package/src/components/CodeBlock.svelte +130 -0
  10. package/src/components/CodeGroup.svelte +408 -0
  11. package/src/components/Connector.svelte +2 -8
  12. package/src/components/Divider.svelte +1 -8
  13. package/src/components/Frame.svelte +30 -0
  14. package/src/components/ItemContent.svelte +4 -2
  15. package/src/components/Message.svelte +1 -22
  16. package/src/components/NavContent.svelte +1 -16
  17. package/src/components/Pill.svelte +1 -24
  18. package/src/components/ProgressBar.svelte +1 -8
  19. package/src/components/Rating.svelte +2 -22
  20. package/src/components/ResponsiveGrid.svelte +1 -14
  21. package/src/components/Reveal.svelte +1 -23
  22. package/src/components/Select.svelte +90 -9
  23. package/src/components/Shine.svelte +1 -19
  24. package/src/components/Stack.svelte +1 -16
  25. package/src/components/StatusList.svelte +3 -2
  26. package/src/components/Stepper.svelte +2 -41
  27. package/src/components/Tilt.svelte +1 -13
  28. package/src/components/Toggle.svelte +83 -33
  29. package/src/components/index.ts +3 -0
  30. package/src/index.ts +4 -0
  31. package/src/types/alert-list.ts +28 -0
  32. package/src/types/avatar.ts +41 -0
  33. package/src/types/badge.ts +37 -0
  34. package/src/types/breadcrumbs.ts +43 -0
  35. package/src/types/card.ts +38 -0
  36. package/src/types/carousel.ts +49 -0
  37. package/src/types/code.ts +102 -1
  38. package/src/types/connector.ts +22 -0
  39. package/src/types/divider.ts +20 -0
  40. package/src/types/frame.ts +29 -0
  41. package/src/types/index.ts +66 -0
  42. package/src/types/item-content.ts +26 -0
  43. package/src/types/message.ts +54 -0
  44. package/src/types/nav-content.ts +31 -0
  45. package/src/types/pill.ts +43 -0
  46. package/src/types/progress-bar.ts +19 -0
  47. package/src/types/rating.ts +43 -0
  48. package/src/types/responsive-grid.ts +25 -0
  49. package/src/types/reveal.ts +49 -0
  50. package/src/types/shine.ts +37 -0
  51. package/src/types/stack.ts +40 -0
  52. package/src/types/status-list.ts +42 -0
  53. package/src/types/stepper.ts +74 -0
  54. package/src/types/tilt.ts +28 -0
  55. package/src/types/toggle.ts +12 -0
  56. package/src/types/tree.ts +46 -4
  57. package/src/utils/palette.ts +4 -2
  58. package/src/utils/shiki.ts +22 -1
  59. package/src/utils/upload.js +8 -8
@@ -63,6 +63,7 @@
63
63
  filterPlaceholder = 'Search...',
64
64
  align = 'start',
65
65
  direction = 'down',
66
+ maxRows = 8,
66
67
  icons: userIcons = {} as SelectIcons,
67
68
  onchange,
68
69
  class: className = '',
@@ -79,6 +80,7 @@
79
80
  filterPlaceholder?: string
80
81
  align?: 'start' | 'end'
81
82
  direction?: 'up' | 'down'
83
+ maxRows?: number
82
84
  icons?: SelectIcons
83
85
  onchange?: (value: unknown, item: unknown) => void
84
86
  class?: string
@@ -216,9 +218,29 @@
216
218
  requestAnimationFrame(() => {
217
219
  if (filterable) {
218
220
  filterInputRef?.focus()
219
- } else {
220
- focusSelectedOrFirst()
221
+ return
221
222
  }
223
+ focusSelectedOrFirst()
224
+ // Move DOM focus to the wrapper's focused item so subsequent
225
+ // keydown events fire inside the dropdown (Navigator is
226
+ // listening there). preventScroll + dropdown-only scroll
227
+ // to avoid moving ancestor containers.
228
+ requestAnimationFrame(() => {
229
+ if (!dropdownRef) return
230
+ const key = wrapper.focusedKey
231
+ if (!key) return
232
+ const el = dropdownRef.querySelector(`[data-path="${key}"]`) as HTMLElement | null
233
+ if (el) {
234
+ el.focus({ preventScroll: true })
235
+ const top = el.offsetTop
236
+ const bottom = top + el.offsetHeight
237
+ if (top < dropdownRef.scrollTop) {
238
+ dropdownRef.scrollTop = top
239
+ } else if (bottom > dropdownRef.scrollTop + dropdownRef.clientHeight) {
240
+ dropdownRef.scrollTop = bottom - dropdownRef.clientHeight
241
+ }
242
+ }
243
+ })
222
244
  })
223
245
  },
224
246
  onclose: () => {
@@ -254,19 +276,78 @@
254
276
  return () => nav.destroy()
255
277
  })
256
278
 
257
- // DOM focus sync
279
+ // Apply maxRows by measuring a real option after open and capping max-height.
280
+ // Runs once per open to avoid jitter; the CSS var still wins if the consumer
281
+ // sets --select-dropdown-max-height explicitly.
258
282
  $effect(() => {
259
- const key = wrapper.focusedKey
260
- if (!isOpen || !dropdownRef || !key) return
283
+ if (!isOpen || !dropdownRef) return
261
284
  requestAnimationFrame(() => {
262
- const target = dropdownRef?.querySelector(`[data-path="${key}"]`) as HTMLElement | null
263
- if (target && target !== document.activeElement) {
264
- target.focus()
265
- target.scrollIntoView?.({ block: 'nearest' })
285
+ if (!dropdownRef) return
286
+ const firstOption = dropdownRef.querySelector('[data-select-option]') as HTMLElement | null
287
+ if (!firstOption) return
288
+ const itemH = firstOption.offsetHeight
289
+ if (itemH > 0) {
290
+ dropdownRef.style.setProperty(
291
+ '--select-dropdown-max-height',
292
+ `${maxRows * itemH}px`
293
+ )
266
294
  }
267
295
  })
268
296
  })
269
297
 
298
+ // Position the dropdown relative to the trigger using `position: fixed`
299
+ // so it escapes any ancestor `overflow: auto/hidden` clipping (common
300
+ // when Select lives inside a scrollable card/modal/canvas). Reposition
301
+ // on resize and ancestor scroll; close on the same so it doesn't drift
302
+ // away from the trigger.
303
+ function positionDropdown() {
304
+ if (!dropdownRef || !triggerRef) return
305
+ const r = triggerRef.getBoundingClientRect()
306
+ const gap = 4
307
+ dropdownRef.style.position = 'fixed'
308
+ dropdownRef.style.minWidth = `${r.width}px`
309
+ if (direction === 'up') {
310
+ dropdownRef.style.top = 'auto'
311
+ dropdownRef.style.bottom = `${window.innerHeight - r.top + gap}px`
312
+ } else {
313
+ dropdownRef.style.top = `${r.bottom + gap}px`
314
+ dropdownRef.style.bottom = 'auto'
315
+ }
316
+ if (align === 'end') {
317
+ dropdownRef.style.left = 'auto'
318
+ dropdownRef.style.right = `${window.innerWidth - r.right}px`
319
+ } else {
320
+ dropdownRef.style.left = `${r.left}px`
321
+ dropdownRef.style.right = 'auto'
322
+ }
323
+ }
324
+
325
+ $effect(() => {
326
+ if (!isOpen || !dropdownRef || !triggerRef) return
327
+ positionDropdown()
328
+ const onResize = () => positionDropdown()
329
+ const onScroll = (e: Event) => {
330
+ // Don't close on the dropdown's OWN internal scroll (mouse wheel
331
+ // inside the list, or scrollItemIntoView from keyboard nav).
332
+ // Only close when an ANCESTOR / outer container scrolls — at that
333
+ // point the trigger has moved and the fixed-position dropdown
334
+ // would drift away from it.
335
+ if (dropdownRef && e.target instanceof Node && dropdownRef.contains(e.target)) return
336
+ isOpen = false
337
+ }
338
+ window.addEventListener('resize', onResize)
339
+ window.addEventListener('scroll', onScroll, true)
340
+ return () => {
341
+ window.removeEventListener('resize', onResize)
342
+ window.removeEventListener('scroll', onScroll, true)
343
+ }
344
+ })
345
+
346
+ // Focus sync is owned by Navigator (#syncFocus). The previous duplicate
347
+ // $effect here competed with Navigator's own focus + scroll logic and
348
+ // caused race conditions (page-scroll on arrow nav, layout shift on
349
+ // reopen). Single source of truth lives in the Navigator class.
350
+
270
351
  // ─── Filter keyboard (native listener, fires before Navigator) ───────────
271
352
 
272
353
  function selectFocused() {
@@ -1,24 +1,6 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
2
  import { id } from '@rokkit/core'
4
-
5
- interface ShineProps {
6
- /** Light color (default: 'rgb(var(--primary-500))') */
7
- color?: string
8
- /** Light source distance/height — controls spread (default: 300) */
9
- radius?: number
10
- /** Gaussian blur depth (default: 1) */
11
- depth?: number
12
- /** Height of the surface for the light filter (default: 2) */
13
- surfaceScale?: number
14
- /** The bigger the value the bigger the reflection (default: 0.75) */
15
- specularConstant?: number
16
- /** Controls focus for the light source — bigger = brighter (default: 120) */
17
- specularExponent?: number
18
- /** Additional CSS class */
19
- class?: string
20
- children?: Snippet
21
- }
3
+ import type { ShineProps } from '../types/shine.js'
22
4
 
23
5
  const {
24
6
  color = 'rgb(var(--primary-500))',
@@ -1,20 +1,5 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
-
4
- interface StackProps {
5
- /** Layout direction */
6
- direction?: 'vertical' | 'horizontal'
7
- /** Gap size between children */
8
- gap?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
9
- /** Cross-axis alignment */
10
- align?: 'start' | 'center' | 'end' | 'stretch'
11
- /** Main-axis justification */
12
- justify?: 'start' | 'center' | 'end' | 'between' | 'around'
13
- /** Stack children */
14
- children: Snippet
15
- /** Additional CSS class */
16
- class?: string
17
- }
2
+ import type { StackProps } from '../types/stack.js'
18
3
 
19
4
  const {
20
5
  direction = 'vertical',
@@ -1,9 +1,10 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import { DEFAULT_STATE_ICONS } from '@rokkit/core'
3
+ import type { StatusListProps } from '../types/status-list.js'
3
4
 
4
5
  const DEFAULT_ICONS = DEFAULT_STATE_ICONS.badge
5
6
 
6
- let { class: className = '', items, icons = {} } = $props()
7
+ const { class: className = '', items, icons = {} }: StatusListProps = $props()
7
8
 
8
9
  const resolvedIcons = $derived({ ...DEFAULT_ICONS, ...icons })
9
10
  </script>
@@ -1,46 +1,7 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
2
  import { DEFAULT_STATE_ICONS } from '@rokkit/core'
4
3
  import { messages } from '@rokkit/states'
5
-
6
- interface StepperStep {
7
- /** Step label (shown below circle) */
8
- text: string
9
- /** Short text inside circle (default: step number) */
10
- label?: string
11
- /** Step is finished (shows checkmark) */
12
- completed?: boolean
13
- /** Step cannot be navigated to */
14
- disabled?: boolean
15
- /** Number of sub-stages within this step (default: 1) */
16
- stages?: number
17
- }
18
-
19
- interface StepperIcons {
20
- /** Icon class for check/completed state */
21
- check?: string
22
- }
23
-
24
- interface StepperProps {
25
- /** Array of step definitions */
26
- steps?: StepperStep[]
27
- /** Current step index (bindable) */
28
- current?: number
29
- /** Current sub-stage within step (bindable, 0-based) */
30
- currentStage?: number
31
- /** Only allow clicking completed steps + first incomplete */
32
- linear?: boolean
33
- /** Layout orientation */
34
- orientation?: 'horizontal' | 'vertical'
35
- /** Custom icons */
36
- icons?: StepperIcons
37
- /** Callback when a step or dot is clicked */
38
- onclick?: (step: number, stage?: number) => void
39
- /** Content snippet rendered below the stepper */
40
- content?: Snippet<[StepperStep, number]>
41
- /** Additional CSS class */
42
- class?: string
43
- }
4
+ import type { StepperIcons, StepperProps } from '../types/stepper.js'
44
5
 
45
6
  const defaultIcons: StepperIcons = {
46
7
  check: DEFAULT_STATE_ICONS.action.check
@@ -57,7 +18,7 @@
57
18
  onclick,
58
19
  content,
59
20
  class: className = ''
60
- }: StepperProps & { label?: string } = $props()
21
+ }: StepperProps = $props()
61
22
 
62
23
  const icons = $derived<StepperIcons>({ ...defaultIcons, ...userIcons })
63
24
 
@@ -1,17 +1,5 @@
1
1
  <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
-
4
- interface TiltProps {
5
- /** Maximum rotation angle in degrees (default: 10) */
6
- maxRotation?: number
7
- /** Whether to adjust brightness based on mouse Y position */
8
- setBrightness?: boolean
9
- /** CSS perspective value in pixels (default: 600) */
10
- perspective?: number
11
- /** Additional CSS class */
12
- class?: string
13
- children?: Snippet
14
- }
2
+ import type { TiltProps } from '../types/tilt.js'
15
3
 
16
4
  const {
17
5
  maxRotation = 10,
@@ -6,6 +6,7 @@
6
6
  import { resolveSnippet } from '@rokkit/core'
7
7
 
8
8
  let {
9
+ variant = 'group',
9
10
  options = [],
10
11
  fields: userFields = {},
11
12
  value = $bindable(),
@@ -23,12 +24,12 @@
23
24
  const proxyTree = $derived(new ProxyTree(options, userFields))
24
25
  const wrapper = $derived(new Wrapper(proxyTree, { onselect: handleSelect }))
25
26
 
26
- // ─── Navigator (horizontal, always attached) ───────────────────────────────
27
+ // ─── Navigator (horizontal — only for group variant) ───────────────────────
27
28
 
28
29
  let containerRef = $state<HTMLElement | null>(null)
29
30
 
30
31
  $effect(() => {
31
- if (!containerRef) return
32
+ if (!containerRef || variant !== 'group') return
32
33
  const dir = getComputedStyle(containerRef).direction
33
34
  const nav = new Navigator(containerRef, wrapper, { orientation: 'horizontal', dir })
34
35
  return () => nav.destroy()
@@ -49,37 +50,44 @@
49
50
  onchange?.(extractedValue, proxy.original)
50
51
  }
51
52
  }
53
+
54
+ // ─── Button variant — derive the next option in the cycle ─────────────────
55
+ // The single button represents the NEXT value (what you'd switch to on click).
56
+ // We compute the index of the current value, then point at index+1 (mod len).
57
+
58
+ const nodes = $derived(wrapper.flatView)
59
+ const currentIndex = $derived(nodes.findIndex((n) => n.proxy.value === value))
60
+ const nextIndex = $derived(
61
+ nodes.length === 0 ? -1 : (currentIndex >= 0 ? (currentIndex + 1) % nodes.length : 0)
62
+ )
63
+ const nextNode = $derived(nextIndex >= 0 ? nodes[nextIndex] : null)
64
+
65
+ function cycle() {
66
+ if (disabled || !nextNode) return
67
+ const proxy = nextNode.proxy
68
+ handleSelect(proxy.value, proxy)
69
+ }
52
70
  </script>
53
71
 
54
- <div
55
- bind:this={containerRef}
56
- data-toggle
57
- data-toggle-size={size}
58
- data-toggle-disabled={disabled || undefined}
59
- data-toggle-labels={showLabels || undefined}
60
- class={className || undefined}
61
- role="radiogroup"
62
- aria-label={label}
63
- aria-disabled={disabled || undefined}
64
- >
65
- {#each wrapper.flatView as node (node.key)}
66
- {@const proxy = node.proxy}
67
- {@const sel = proxy.value === value}
68
- {@const content = resolveSnippet(snippets, proxy)}
69
- <button
70
- type="button"
71
- data-toggle-option
72
- data-path={node.key}
73
- data-selected={sel || undefined}
74
- data-disabled={proxy.disabled || undefined}
75
- role="radio"
76
- aria-checked={sel}
77
- aria-label={proxy.label}
78
- title={proxy.get('description') || proxy.label}
79
- disabled={proxy.disabled || disabled}
80
- >
72
+ {#if variant === 'button'}
73
+ {@const proxy = nextNode?.proxy}
74
+ {@const content = proxy ? resolveSnippet(snippets, proxy) : null}
75
+ <button
76
+ type="button"
77
+ data-toggle
78
+ data-toggle-variant="button"
79
+ data-toggle-size={size}
80
+ data-toggle-disabled={disabled || undefined}
81
+ data-toggle-labels={showLabels || undefined}
82
+ class={className || undefined}
83
+ title={proxy?.get('description') || proxy?.label || label}
84
+ aria-label={proxy?.label || label}
85
+ disabled={disabled || !proxy}
86
+ onclick={cycle}
87
+ >
88
+ {#if proxy}
81
89
  {#if content}
82
- {@render content(proxy, sel)}
90
+ {@render content(proxy, false)}
83
91
  {:else}
84
92
  {#if proxy.get('icon')}
85
93
  <span data-toggle-icon class={proxy.get('icon')} aria-hidden="true"></span>
@@ -88,6 +96,48 @@
88
96
  <span data-toggle-label>{proxy.label}</span>
89
97
  {/if}
90
98
  {/if}
91
- </button>
92
- {/each}
93
- </div>
99
+ {/if}
100
+ </button>
101
+ {:else}
102
+ <div
103
+ bind:this={containerRef}
104
+ data-toggle
105
+ data-toggle-variant="group"
106
+ data-toggle-size={size}
107
+ data-toggle-disabled={disabled || undefined}
108
+ data-toggle-labels={showLabels || undefined}
109
+ class={className || undefined}
110
+ role="radiogroup"
111
+ aria-label={label}
112
+ aria-disabled={disabled || undefined}
113
+ >
114
+ {#each wrapper.flatView as node (node.key)}
115
+ {@const proxy = node.proxy}
116
+ {@const sel = proxy.value === value}
117
+ {@const content = resolveSnippet(snippets, proxy)}
118
+ <button
119
+ type="button"
120
+ data-toggle-option
121
+ data-path={node.key}
122
+ data-selected={sel || undefined}
123
+ data-disabled={proxy.disabled || undefined}
124
+ role="radio"
125
+ aria-checked={sel}
126
+ aria-label={proxy.label}
127
+ title={proxy.get('description') || proxy.label}
128
+ disabled={proxy.disabled || disabled}
129
+ >
130
+ {#if content}
131
+ {@render content(proxy, sel)}
132
+ {:else}
133
+ {#if proxy.get('icon')}
134
+ <span data-toggle-icon class={proxy.get('icon')} aria-hidden="true"></span>
135
+ {/if}
136
+ {#if showLabels && proxy.label}
137
+ <span data-toggle-label>{proxy.label}</span>
138
+ {/if}
139
+ {/if}
140
+ </button>
141
+ {/each}
142
+ </div>
143
+ {/if}
@@ -2,6 +2,9 @@
2
2
  export { default as Button } from './Button.svelte'
3
3
  export { default as ButtonGroup } from './ButtonGroup.svelte'
4
4
  export { default as Code } from './Code.svelte'
5
+ export { default as CodeBlock } from './CodeBlock.svelte'
6
+ export { default as CodeGroup } from './CodeGroup.svelte'
7
+ export { default as Frame } from './Frame.svelte'
5
8
  export { default as Menu } from './Menu.svelte'
6
9
  export { default as Dropdown } from './Dropdown.svelte'
7
10
  export { default as Select } from './Select.svelte'
package/src/index.ts CHANGED
@@ -3,6 +3,9 @@ export {
3
3
  Button,
4
4
  ButtonGroup,
5
5
  Code,
6
+ CodeBlock,
7
+ CodeGroup,
8
+ Frame,
6
9
  Menu,
7
10
  Dropdown,
8
11
  Select,
@@ -27,6 +30,7 @@ export {
27
30
  Pill,
28
31
  Rating,
29
32
  Connector,
33
+ ItemContent,
30
34
  Stepper,
31
35
  Switch,
32
36
  Table,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AlertList Component Types
3
+ *
4
+ * Renders a portalled toast stack at a screen-edge position, reading from
5
+ * the shared `alerts` state.
6
+ */
7
+
8
+ /**
9
+ * Screen position for the toast stack.
10
+ */
11
+ export type AlertListPosition =
12
+ | 'top-right'
13
+ | 'top-center'
14
+ | 'top-left'
15
+ | 'bottom-right'
16
+ | 'bottom-center'
17
+ | 'bottom-left'
18
+
19
+ /**
20
+ * Props for the AlertList component.
21
+ */
22
+ export interface AlertListProps {
23
+ /** Screen position for the toast stack. */
24
+ position?: AlertListPosition
25
+
26
+ /** Additional CSS class. */
27
+ class?: string
28
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Avatar Component Types
3
+ */
4
+
5
+ /** Avatar size scale. */
6
+ export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
7
+
8
+ /** Avatar shape. */
9
+ export type AvatarShape = 'circle' | 'square'
10
+
11
+ /** Online presence status. */
12
+ export type AvatarStatus = 'online' | 'offline' | 'away' | 'busy'
13
+
14
+ /**
15
+ * Props for the Avatar component.
16
+ */
17
+ export interface AvatarProps {
18
+ /** Image source URL. */
19
+ src?: string
20
+
21
+ /** Alt text for the image. */
22
+ alt?: string
23
+
24
+ /** Explicit initials to display as fallback. */
25
+ initials?: string
26
+
27
+ /** Full name — auto-derives initials if `initials` prop not provided. */
28
+ name?: string
29
+
30
+ /** Size variant. */
31
+ size?: AvatarSize
32
+
33
+ /** Online presence status. */
34
+ status?: AvatarStatus
35
+
36
+ /** Shape variant. */
37
+ shape?: AvatarShape
38
+
39
+ /** Additional CSS class. */
40
+ class?: string
41
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Badge Component Types
3
+ */
4
+
5
+ import type { Snippet } from 'svelte'
6
+
7
+ /** Semantic color variant for a badge. */
8
+ export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'error'
9
+
10
+ /**
11
+ * Props for the Badge component.
12
+ *
13
+ * Badge can be used standalone or wrapped around a child via the `children`
14
+ * snippet, in which case it positions itself absolutely over the child.
15
+ */
16
+ export interface BadgeProps {
17
+ /** Numeric count to display. */
18
+ count?: number
19
+
20
+ /** Maximum count before rendering as `${max}+`. Default `99`. */
21
+ max?: number
22
+
23
+ /** Visual variant. */
24
+ variant?: BadgeVariant
25
+
26
+ /** Render as a small dot without content. */
27
+ dot?: boolean
28
+
29
+ /**
30
+ * Content to wrap. When provided, the badge positions absolutely over
31
+ * the rendered child.
32
+ */
33
+ children?: Snippet
34
+
35
+ /** Additional CSS class. */
36
+ class?: string
37
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * BreadCrumbs Component Types
3
+ */
4
+
5
+ import type { Snippet } from 'svelte'
6
+ import type { ProxyItem } from '@rokkit/states'
7
+
8
+ /**
9
+ * Icon overrides for the BreadCrumbs component.
10
+ */
11
+ export interface BreadCrumbsIcons {
12
+ /** Icon class used for the separator between crumbs. */
13
+ separator?: string
14
+ }
15
+
16
+ /**
17
+ * Props for the BreadCrumbs component.
18
+ */
19
+ export interface BreadCrumbsProps {
20
+ /** Array of breadcrumb items. */
21
+ items?: unknown[]
22
+
23
+ /** Custom field mappings used by the internal `ProxyItem`. */
24
+ fields?: Record<string, string>
25
+
26
+ /** Accessible label for the surrounding `<nav>`. */
27
+ label?: string
28
+
29
+ /** Custom icon overrides. */
30
+ icons?: BreadCrumbsIcons
31
+
32
+ /** Called when a crumb is clicked. */
33
+ onclick?: (value: unknown, item: unknown) => void
34
+
35
+ /**
36
+ * Custom snippet for rendering each crumb. Receives the crumb's
37
+ * `ProxyItem` and a flag indicating whether it is the last (current) crumb.
38
+ */
39
+ crumb?: Snippet<[ProxyItem, boolean]>
40
+
41
+ /** Additional CSS class. */
42
+ class?: string
43
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Card Component Types
3
+ */
4
+
5
+ import type { Snippet } from 'svelte'
6
+
7
+ /** Semantic color variant for a card. */
8
+ export type CardVariant = 'default' | 'primary' | 'secondary' | 'tertiary'
9
+
10
+ /**
11
+ * Props for the Card component.
12
+ *
13
+ * When `href` is provided the card renders as an anchor. When `onclick` is
14
+ * provided (and no `href`) it renders as a button. Otherwise it renders as
15
+ * a plain container.
16
+ */
17
+ export interface CardProps {
18
+ /** Optional href — renders the card as an `<a>` link. */
19
+ href?: string
20
+
21
+ /** Click handler — applies only when no `href` is provided. */
22
+ onclick?: () => void
23
+
24
+ /** Visual variant. */
25
+ variant?: CardVariant
26
+
27
+ /** Additional CSS class. */
28
+ class?: string
29
+
30
+ /** Card header snippet. */
31
+ header?: Snippet
32
+
33
+ /** Card footer snippet. */
34
+ footer?: Snippet
35
+
36
+ /** Card body content. */
37
+ children?: Snippet
38
+ }