@hyvnt/hyvui 0.2.0 → 0.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 (86) hide show
  1. package/README.md +264 -253
  2. package/dist/components/ambient/CornerBrackets.svelte +83 -87
  3. package/dist/components/ambient/DataStream.svelte +111 -94
  4. package/dist/components/ambient/GlyphMark.svelte +69 -69
  5. package/dist/components/ambient/GridOverlay.svelte +26 -28
  6. package/dist/components/ambient/ParallaxLayer.svelte +37 -41
  7. package/dist/components/ambient/ScanBand.svelte +95 -91
  8. package/dist/components/ambient/SignalRing.svelte +100 -100
  9. package/dist/components/ambient/ThreadLine.svelte +71 -78
  10. package/dist/components/ambient/Vignette.svelte +24 -26
  11. package/dist/components/depth/DepthLayer.svelte +22 -27
  12. package/dist/components/depth/DepthStage.svelte +63 -62
  13. package/dist/components/depth/FloatCard.svelte +113 -104
  14. package/dist/components/depth/HorizonGrid.svelte +216 -160
  15. package/dist/components/depth/Plinth.svelte +52 -57
  16. package/dist/components/display/Avatar.svelte +64 -69
  17. package/dist/components/display/Badge.svelte +59 -63
  18. package/dist/components/display/Blockquote.svelte +31 -34
  19. package/dist/components/display/CodeBlock.svelte +71 -76
  20. package/dist/components/display/MetricCard.svelte +77 -83
  21. package/dist/components/display/Table.svelte +99 -104
  22. package/dist/components/feedback/Alert.svelte +71 -76
  23. package/dist/components/feedback/EmptyState.svelte +68 -68
  24. package/dist/components/feedback/ErrorState.svelte +73 -73
  25. package/dist/components/feedback/Skeleton.svelte +52 -52
  26. package/dist/components/feedback/StatusDot.svelte +49 -54
  27. package/dist/components/feedback/StatusLine.svelte +122 -122
  28. package/dist/components/feedback/Toast.svelte +130 -136
  29. package/dist/components/inputs/Button.svelte +240 -237
  30. package/dist/components/inputs/Checkbox.svelte +104 -105
  31. package/dist/components/inputs/FileUpload.svelte +165 -163
  32. package/dist/components/inputs/Input.svelte +145 -147
  33. package/dist/components/inputs/Select.svelte +156 -150
  34. package/dist/components/inputs/Textarea.svelte +153 -154
  35. package/dist/components/inputs/Toggle.svelte +120 -120
  36. package/dist/components/layout/Card.svelte +70 -76
  37. package/dist/components/layout/Drawer.svelte +133 -109
  38. package/dist/components/layout/Grid.svelte +118 -43
  39. package/dist/components/layout/Grid.svelte.d.ts +8 -2
  40. package/dist/components/layout/Modal.svelte +176 -159
  41. package/dist/components/layout/Panel.svelte +49 -54
  42. package/dist/components/layout/Popover.svelte +178 -67
  43. package/dist/components/layout/Popover.svelte.d.ts +10 -1
  44. package/dist/components/layout/Stack.svelte +53 -53
  45. package/dist/components/navigation/Breadcrumb.svelte +70 -73
  46. package/dist/components/navigation/DropdownMenu.svelte +167 -124
  47. package/dist/components/navigation/DropdownMenu.svelte.d.ts +12 -2
  48. package/dist/components/navigation/SidebarNav.svelte +86 -90
  49. package/dist/components/navigation/Tabs.svelte +81 -86
  50. package/dist/components/navigation/Topbar.svelte +85 -85
  51. package/dist/components/patterns/ActionBar.svelte +71 -76
  52. package/dist/components/patterns/ConfirmDialog.svelte +63 -64
  53. package/dist/components/patterns/PageHeader.svelte +109 -114
  54. package/dist/components/patterns/SearchBar.svelte +54 -59
  55. package/dist/components/patterns/TerminalBoot.svelte +104 -104
  56. package/dist/components/primitives/Divider.svelte +26 -29
  57. package/dist/components/primitives/Icon.svelte +44 -49
  58. package/dist/components/primitives/Label.svelte +39 -44
  59. package/dist/components/primitives/Surface.svelte +89 -87
  60. package/dist/components/primitives/Text.svelte +98 -98
  61. package/dist/components/scenes/ArchiveScene.svelte +92 -95
  62. package/dist/components/scenes/ArchiveScene.svelte.d.ts +7 -1
  63. package/dist/components/scenes/LogScene.svelte +72 -77
  64. package/dist/components/scenes/NarrativeScene.svelte +91 -92
  65. package/dist/components/scenes/ReadoutScene.svelte +120 -107
  66. package/dist/components/scenes/ReadoutScene.svelte.d.ts +3 -1
  67. package/dist/components/scenes/StageScene.svelte +97 -104
  68. package/dist/examples/FieldReport.svelte +226 -223
  69. package/dist/examples/ObservationDeck.svelte +333 -317
  70. package/dist/examples/SignalLost.svelte +191 -191
  71. package/dist/styles.css +113 -0
  72. package/dist/system/actions/echo.js +9 -9
  73. package/dist/system/actions/resolve.js +9 -9
  74. package/dist/system/actions/reveal.js +1 -1
  75. package/dist/system/actions/surface.js +13 -1
  76. package/dist/system/depth/depth.css +49 -49
  77. package/dist/system/depth/depth.js +1 -1
  78. package/dist/system/expressions.css +80 -80
  79. package/dist/system/override-template.css +72 -72
  80. package/dist/system/register.css +74 -74
  81. package/dist/system/scroll-lock.d.ts +6 -0
  82. package/dist/system/scroll-lock.js +23 -0
  83. package/dist/tokens/tokens.css +100 -86
  84. package/dist/tokens/tokens.js +4 -4
  85. package/dist/utils/motion.js +1 -1
  86. package/package.json +67 -60
@@ -1,124 +1,167 @@
1
- <script lang="ts">
2
- import { cn } from '../../utils/cn.js';
3
- import Surface from '../primitives/Surface.svelte';
4
-
5
- interface MenuItem {
6
- label: string;
7
- value: string;
8
- disabled?: boolean;
9
- destructive?: boolean;
10
- }
11
-
12
- interface Props {
13
- /** Controls menu visibility. */
14
- open?: boolean;
15
- /** Menu items. */
16
- items?: MenuItem[];
17
- /** Additional CSS classes. */
18
- class?: string;
19
- /** Fires when an item is selected with its value. */
20
- onselect?: (value: string) => void;
21
- }
22
-
23
- let {
24
- open = false,
25
- items = [],
26
- class: className = '',
27
- onselect,
28
- }: Props = $props();
29
- </script>
30
-
31
- {#if open}
32
- <div class={cn('hyvui-dropdown', className)}>
33
- <Surface variant="card" class="hyvui-dropdown-surface">
34
- {#each items as item}
35
- <button
36
- type="button"
37
- class={cn(
38
- 'hyvui-dropdown-item',
39
- item.disabled && 'hyvui-dropdown-item-disabled',
40
- item.destructive && 'hyvui-dropdown-item-destructive'
41
- )}
42
- disabled={item.disabled}
43
- onclick={() => onselect?.(item.value)}
44
- >
45
- {item.label}
46
- </button>
47
- {/each}
48
- </Surface>
49
- </div>
50
- {/if}
51
-
52
- <style>
53
- .hyvui-dropdown {
54
- position: absolute;
55
- z-index: var(--z-overlay);
56
- min-width: 13rem;
57
- animation: dropdown-in 0.2s cubic-bezier(0.22, 1, 0.36, 1);
58
- }
59
-
60
- :global(.hyvui-dropdown-surface) {
61
- padding: var(--space-xs);
62
- }
63
-
64
- .hyvui-dropdown-item {
65
- display: block;
66
- width: 100%;
67
- text-align: left;
68
- font-family: var(--font-mono);
69
- font-size: 0.74rem;
70
- font-weight: 400;
71
- letter-spacing: 0.14em;
72
- text-transform: uppercase;
73
- color: var(--text-soft);
74
- background: none;
75
- border: none;
76
- border-radius: var(--radius-sm);
77
- padding: 0.75rem 0.85rem;
78
- cursor: pointer;
79
- transition:
80
- transform var(--transition-fast),
81
- color var(--transition-fast),
82
- background var(--transition-fast);
83
- }
84
-
85
- .hyvui-dropdown-item:hover:not(:disabled) {
86
- transform: translateX(4px);
87
- background: linear-gradient(90deg, rgba(199, 156, 87, 0.12), transparent 72%);
88
- color: var(--text);
89
- }
90
-
91
- .hyvui-dropdown-item-disabled {
92
- opacity: 0.4;
93
- cursor: not-allowed;
94
- }
95
-
96
- .hyvui-dropdown-item-destructive {
97
- color: var(--status-fail);
98
- }
99
-
100
- @keyframes dropdown-in {
101
- from {
102
- opacity: 0;
103
- transform: translateY(4px);
104
- }
105
- to {
106
- opacity: 1;
107
- transform: translateY(0);
108
- }
109
- }
110
-
111
- @media (prefers-reduced-motion: reduce) {
112
- .hyvui-dropdown {
113
- animation: none;
114
- }
115
-
116
- .hyvui-dropdown-item {
117
- transition: none;
118
- }
119
-
120
- .hyvui-dropdown-item:hover:not(:disabled) {
121
- transform: none;
122
- }
123
- }
124
- </style>
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+ import Popover from '../layout/Popover.svelte';
4
+ import type { Placement } from '@floating-ui/dom';
5
+ import type { Snippet } from 'svelte';
6
+
7
+ interface MenuItem {
8
+ label: string;
9
+ value: string;
10
+ disabled?: boolean;
11
+ destructive?: boolean;
12
+ }
13
+
14
+ interface Props {
15
+ /** Menu items. */
16
+ items?: MenuItem[];
17
+ /** Controlled open state. If undefined, the menu manages its own state. */
18
+ open?: boolean;
19
+ /** Placement for the floating menu. */
20
+ placement?: Placement;
21
+ /** Offset in pixels from trigger. */
22
+ offset?: number;
23
+ /** Additional CSS classes. */
24
+ class?: string;
25
+ /** Trigger snippet (required for a usable menu). */
26
+ trigger?: Snippet;
27
+ /** Fires when an item is selected with its value. */
28
+ onselect?: (value: string) => void;
29
+ /** Fires when open state changes. */
30
+ onopenchange?: (open: boolean) => void;
31
+ }
32
+
33
+ let {
34
+ items = [],
35
+ open,
36
+ placement = 'bottom-end',
37
+ offset = 8,
38
+ class: className = '',
39
+ trigger,
40
+ onselect,
41
+ onopenchange
42
+ }: Props = $props();
43
+
44
+ let triggerEl: HTMLElement | null = $state(null);
45
+ let internalOpen = $state(false);
46
+
47
+ const controlled = $derived(open !== undefined);
48
+ const isOpen = $derived(controlled ? !!open : internalOpen);
49
+
50
+ function setOpen(next: boolean) {
51
+ if (!controlled) internalOpen = next;
52
+ onopenchange?.(next);
53
+ }
54
+
55
+ function toggle() {
56
+ setOpen(!isOpen);
57
+ }
58
+
59
+ function close() {
60
+ setOpen(false);
61
+ }
62
+ </script>
63
+
64
+ <div class={cn('hyvui-dropdown-root', className)}>
65
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
66
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
67
+ <span class="hyvui-dropdown-trigger" bind:this={triggerEl} onclick={toggle}>
68
+ {#if trigger}{@render trigger()}{/if}
69
+ </span>
70
+
71
+ <Popover
72
+ open={isOpen}
73
+ anchor={triggerEl}
74
+ {placement}
75
+ {offset}
76
+ onclose={close}
77
+ class="hyvui-dropdown-popover"
78
+ >
79
+ <div class="hyvui-dropdown-menu" role="menu">
80
+ {#each items as item}
81
+ <button
82
+ type="button"
83
+ role="menuitem"
84
+ class={cn(
85
+ 'hyvui-dropdown-item',
86
+ item.disabled && 'hyvui-dropdown-item-disabled',
87
+ item.destructive && 'hyvui-dropdown-item-destructive'
88
+ )}
89
+ disabled={item.disabled}
90
+ onclick={() => {
91
+ if (item.disabled) return;
92
+ onselect?.(item.value);
93
+ close();
94
+ }}
95
+ >
96
+ {item.label}
97
+ </button>
98
+ {/each}
99
+ </div>
100
+ </Popover>
101
+ </div>
102
+
103
+ <style>
104
+ .hyvui-dropdown-root {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ }
108
+
109
+ .hyvui-dropdown-trigger {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ }
113
+
114
+ :global(.hyvui-dropdown-popover) {
115
+ max-inline-size: min(90dvw, 18rem);
116
+ }
117
+
118
+ .hyvui-dropdown-menu {
119
+ padding: var(--space-xs);
120
+ }
121
+
122
+ .hyvui-dropdown-item {
123
+ display: block;
124
+ width: 100%;
125
+ text-align: left;
126
+ font-family: var(--font-mono);
127
+ font-size: 0.74rem;
128
+ font-weight: 400;
129
+ letter-spacing: 0.14em;
130
+ text-transform: uppercase;
131
+ color: var(--text-soft);
132
+ background: none;
133
+ border: none;
134
+ border-radius: var(--radius-sm);
135
+ padding: 0.75rem 0.85rem;
136
+ cursor: pointer;
137
+ transition:
138
+ transform var(--transition-fast),
139
+ color var(--transition-fast),
140
+ background var(--transition-fast);
141
+ }
142
+
143
+ .hyvui-dropdown-item:hover:not(:disabled) {
144
+ transform: translateX(4px);
145
+ background: linear-gradient(90deg, rgba(199, 156, 87, 0.12), transparent 72%);
146
+ color: var(--text);
147
+ }
148
+
149
+ .hyvui-dropdown-item-disabled {
150
+ opacity: 0.4;
151
+ cursor: not-allowed;
152
+ }
153
+
154
+ .hyvui-dropdown-item-destructive {
155
+ color: var(--status-fail);
156
+ }
157
+
158
+ @media (prefers-reduced-motion: reduce) {
159
+ .hyvui-dropdown-item {
160
+ transition: none;
161
+ }
162
+
163
+ .hyvui-dropdown-item:hover:not(:disabled) {
164
+ transform: none;
165
+ }
166
+ }
167
+ </style>
@@ -1,3 +1,5 @@
1
+ import type { Placement } from '@floating-ui/dom';
2
+ import type { Snippet } from 'svelte';
1
3
  interface MenuItem {
2
4
  label: string;
3
5
  value: string;
@@ -5,14 +7,22 @@ interface MenuItem {
5
7
  destructive?: boolean;
6
8
  }
7
9
  interface Props {
8
- /** Controls menu visibility. */
9
- open?: boolean;
10
10
  /** Menu items. */
11
11
  items?: MenuItem[];
12
+ /** Controlled open state. If undefined, the menu manages its own state. */
13
+ open?: boolean;
14
+ /** Placement for the floating menu. */
15
+ placement?: Placement;
16
+ /** Offset in pixels from trigger. */
17
+ offset?: number;
12
18
  /** Additional CSS classes. */
13
19
  class?: string;
20
+ /** Trigger snippet (required for a usable menu). */
21
+ trigger?: Snippet;
14
22
  /** Fires when an item is selected with its value. */
15
23
  onselect?: (value: string) => void;
24
+ /** Fires when open state changes. */
25
+ onopenchange?: (open: boolean) => void;
16
26
  }
17
27
  declare const DropdownMenu: import("svelte").Component<Props, {}, "">;
18
28
  type DropdownMenu = ReturnType<typeof DropdownMenu>;
@@ -1,90 +1,86 @@
1
- <script lang="ts">
2
- import { cn } from '../../utils/cn.js';
3
-
4
- interface NavItem {
5
- label: string;
6
- href: string;
7
- active?: boolean;
8
- }
9
-
10
- interface Props {
11
- /** Navigation items. */
12
- items?: NavItem[];
13
- /** Additional CSS classes. */
14
- class?: string;
15
- /** Fires when a nav item is clicked. */
16
- onnavigate?: (item: NavItem) => void;
17
- }
18
-
19
- let {
20
- items = [],
21
- class: className = '',
22
- onnavigate,
23
- }: Props = $props();
24
- </script>
25
-
26
- <nav class={cn('hyvui-sidebar-nav', className)}>
27
- {#each items as item}
28
- <a
29
- href={item.href}
30
- class={cn('hyvui-sidebar-link', item.active && 'hyvui-sidebar-link-active')}
31
- onclick={(e) => {
32
- if (onnavigate) {
33
- e.preventDefault();
34
- onnavigate(item);
35
- }
36
- }}
37
- >
38
- {item.label}
39
- </a>
40
- {/each}
41
- </nav>
42
-
43
- <style>
44
- .hyvui-sidebar-nav {
45
- display: flex;
46
- flex-direction: column;
47
- gap: var(--space-2xs);
48
- }
49
-
50
- .hyvui-sidebar-link {
51
- position: relative;
52
- font-family: var(--font-mono);
53
- font-size: 0.7rem;
54
- font-weight: 400;
55
- letter-spacing: 0.16em;
56
- text-transform: uppercase;
57
- color: var(--muted);
58
- text-decoration: none;
59
- padding: 0.55rem 0.9rem;
60
- border-left: 1px solid transparent;
61
- min-width: 0;
62
- transition:
63
- color var(--transition-fast),
64
- transform var(--transition-fast),
65
- background var(--transition-fast),
66
- border-color var(--transition-fast);
67
- }
68
-
69
- .hyvui-sidebar-link:hover {
70
- color: var(--text-soft);
71
- background: linear-gradient(90deg, rgba(199, 156, 87, 0.08), transparent 76%);
72
- }
73
-
74
- .hyvui-sidebar-link-active {
75
- color: var(--accent);
76
- border-left-color: var(--line-strong);
77
- background: linear-gradient(90deg, rgba(199, 156, 87, 0.14), transparent 72%);
78
- transform: translateX(4px);
79
- }
80
-
81
- @media (prefers-reduced-motion: reduce) {
82
- .hyvui-sidebar-link {
83
- transition: none;
84
- }
85
-
86
- .hyvui-sidebar-link-active {
87
- transform: none;
88
- }
89
- }
90
- </style>
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+
4
+ interface NavItem {
5
+ label: string;
6
+ href: string;
7
+ active?: boolean;
8
+ }
9
+
10
+ interface Props {
11
+ /** Navigation items. */
12
+ items?: NavItem[];
13
+ /** Additional CSS classes. */
14
+ class?: string;
15
+ /** Fires when a nav item is clicked. */
16
+ onnavigate?: (item: NavItem) => void;
17
+ }
18
+
19
+ let { items = [], class: className = '', onnavigate }: Props = $props();
20
+ </script>
21
+
22
+ <nav class={cn('hyvui-sidebar-nav', className)}>
23
+ {#each items as item}
24
+ <a
25
+ href={item.href}
26
+ class={cn('hyvui-sidebar-link', item.active && 'hyvui-sidebar-link-active')}
27
+ onclick={(e) => {
28
+ if (onnavigate) {
29
+ e.preventDefault();
30
+ onnavigate(item);
31
+ }
32
+ }}
33
+ >
34
+ {item.label}
35
+ </a>
36
+ {/each}
37
+ </nav>
38
+
39
+ <style>
40
+ .hyvui-sidebar-nav {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--space-2xs);
44
+ }
45
+
46
+ .hyvui-sidebar-link {
47
+ position: relative;
48
+ font-family: var(--font-mono);
49
+ font-size: 0.7rem;
50
+ font-weight: 400;
51
+ letter-spacing: 0.16em;
52
+ text-transform: uppercase;
53
+ color: var(--muted);
54
+ text-decoration: none;
55
+ padding: 0.55rem 0.9rem;
56
+ border-left: 1px solid transparent;
57
+ min-width: 0;
58
+ transition:
59
+ color var(--transition-fast),
60
+ transform var(--transition-fast),
61
+ background var(--transition-fast),
62
+ border-color var(--transition-fast);
63
+ }
64
+
65
+ .hyvui-sidebar-link:hover {
66
+ color: var(--text-soft);
67
+ background: linear-gradient(90deg, rgba(199, 156, 87, 0.08), transparent 76%);
68
+ }
69
+
70
+ .hyvui-sidebar-link-active {
71
+ color: var(--accent);
72
+ border-left-color: var(--line-strong);
73
+ background: linear-gradient(90deg, rgba(199, 156, 87, 0.14), transparent 72%);
74
+ transform: translateX(4px);
75
+ }
76
+
77
+ @media (prefers-reduced-motion: reduce) {
78
+ .hyvui-sidebar-link {
79
+ transition: none;
80
+ }
81
+
82
+ .hyvui-sidebar-link-active {
83
+ transform: none;
84
+ }
85
+ }
86
+ </style>
@@ -1,86 +1,81 @@
1
- <script lang="ts">
2
- import { cn } from '../../utils/cn.js';
3
-
4
- interface TabItem {
5
- id: string;
6
- label: string;
7
- }
8
-
9
- interface Props {
10
- /** Available tabs. */
11
- tabs?: TabItem[];
12
- /** Currently active tab id. */
13
- active?: string;
14
- /** Additional CSS classes. */
15
- class?: string;
16
- /** Fires when a tab is selected with the new tab id. */
17
- onchange?: (id: string) => void;
18
- }
19
-
20
- let {
21
- tabs = [],
22
- active = '',
23
- class: className = '',
24
- onchange,
25
- }: Props = $props();
26
- </script>
27
-
28
- <div class={cn('hyvui-tabs', className)} role="tablist">
29
- {#each tabs as tab}
30
- <button
31
- type="button"
32
- role="tab"
33
- aria-selected={tab.id === active}
34
- class={cn('hyvui-tab', tab.id === active && 'hyvui-tab-active')}
35
- onclick={() => onchange?.(tab.id)}
36
- >
37
- {tab.label}
38
- </button>
39
- {/each}
40
- </div>
41
-
42
- <style>
43
- .hyvui-tabs {
44
- display: flex;
45
- flex-wrap: wrap;
46
- gap: var(--space-xs);
47
- border-bottom: 1px solid var(--line);
48
- padding-bottom: var(--space-xs);
49
- }
50
-
51
- .hyvui-tab {
52
- position: relative;
53
- font-family: var(--font-mono);
54
- font-size: 0.7rem;
55
- font-weight: 400;
56
- letter-spacing: 0.16em;
57
- text-transform: uppercase;
58
- color: var(--muted);
59
- background: transparent;
60
- border: 1px solid transparent;
61
- border-radius: var(--radius-md);
62
- padding: 0.65rem 0.85rem;
63
- cursor: pointer;
64
- transition:
65
- color var(--transition-fast),
66
- border-color var(--transition-fast),
67
- background var(--transition-fast);
68
- }
69
-
70
- .hyvui-tab:hover {
71
- color: var(--text-soft);
72
- background: linear-gradient(180deg, rgba(199, 156, 87, 0.08), transparent 76%);
73
- }
74
-
75
- .hyvui-tab-active {
76
- color: var(--accent);
77
- border-color: color-mix(in srgb, var(--accent) 34%, transparent);
78
- background: linear-gradient(180deg, rgba(199, 156, 87, 0.12), transparent 76%);
79
- }
80
-
81
- @media (prefers-reduced-motion: reduce) {
82
- .hyvui-tab {
83
- transition: none;
84
- }
85
- }
86
- </style>
1
+ <script lang="ts">
2
+ import { cn } from '../../utils/cn.js';
3
+
4
+ interface TabItem {
5
+ id: string;
6
+ label: string;
7
+ }
8
+
9
+ interface Props {
10
+ /** Available tabs. */
11
+ tabs?: TabItem[];
12
+ /** Currently active tab id. */
13
+ active?: string;
14
+ /** Additional CSS classes. */
15
+ class?: string;
16
+ /** Fires when a tab is selected with the new tab id. */
17
+ onchange?: (id: string) => void;
18
+ }
19
+
20
+ let { tabs = [], active = '', class: className = '', onchange }: Props = $props();
21
+ </script>
22
+
23
+ <div class={cn('hyvui-tabs', className)} role="tablist">
24
+ {#each tabs as tab}
25
+ <button
26
+ type="button"
27
+ role="tab"
28
+ aria-selected={tab.id === active}
29
+ class={cn('hyvui-tab', tab.id === active && 'hyvui-tab-active')}
30
+ onclick={() => onchange?.(tab.id)}
31
+ >
32
+ {tab.label}
33
+ </button>
34
+ {/each}
35
+ </div>
36
+
37
+ <style>
38
+ .hyvui-tabs {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ gap: var(--space-xs);
42
+ border-bottom: 1px solid var(--line);
43
+ padding-bottom: var(--space-xs);
44
+ }
45
+
46
+ .hyvui-tab {
47
+ position: relative;
48
+ font-family: var(--font-mono);
49
+ font-size: 0.7rem;
50
+ font-weight: 400;
51
+ letter-spacing: 0.16em;
52
+ text-transform: uppercase;
53
+ color: var(--muted);
54
+ background: transparent;
55
+ border: 1px solid transparent;
56
+ border-radius: var(--radius-md);
57
+ padding: 0.65rem 0.85rem;
58
+ cursor: pointer;
59
+ transition:
60
+ color var(--transition-fast),
61
+ border-color var(--transition-fast),
62
+ background var(--transition-fast);
63
+ }
64
+
65
+ .hyvui-tab:hover {
66
+ color: var(--text-soft);
67
+ background: linear-gradient(180deg, rgba(199, 156, 87, 0.08), transparent 76%);
68
+ }
69
+
70
+ .hyvui-tab-active {
71
+ color: var(--accent);
72
+ border-color: color-mix(in srgb, var(--accent) 34%, transparent);
73
+ background: linear-gradient(180deg, rgba(199, 156, 87, 0.12), transparent 76%);
74
+ }
75
+
76
+ @media (prefers-reduced-motion: reduce) {
77
+ .hyvui-tab {
78
+ transition: none;
79
+ }
80
+ }
81
+ </style>