seblify 0.1.4

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 (138) hide show
  1. package/README.md +18 -0
  2. package/dist/components/AppShell.svelte +136 -0
  3. package/dist/components/AppShell.svelte.d.ts +13 -0
  4. package/dist/components/Banner.svelte +56 -0
  5. package/dist/components/Banner.svelte.d.ts +9 -0
  6. package/dist/components/Button.svelte +64 -0
  7. package/dist/components/Button.svelte.d.ts +13 -0
  8. package/dist/components/Icon.svelte +67 -0
  9. package/dist/components/Icon.svelte.d.ts +8 -0
  10. package/dist/components/Sidebar.svelte +106 -0
  11. package/dist/components/Sidebar.svelte.d.ts +14 -0
  12. package/dist/components/Snackbar.svelte +26 -0
  13. package/dist/components/Snackbar.svelte.d.ts +7 -0
  14. package/dist/components/Toast.svelte +27 -0
  15. package/dist/components/Toast.svelte.d.ts +7 -0
  16. package/dist/components/action/input/field/pin-input/PinInput.svelte +156 -0
  17. package/dist/components/action/input/field/pin-input/PinInput.svelte.d.ts +10 -0
  18. package/dist/components/action/input/select/checkbox/Checkbox.svelte +71 -0
  19. package/dist/components/action/input/select/checkbox/Checkbox.svelte.d.ts +10 -0
  20. package/dist/components/action/input/select/segment-control/SegmentControl.svelte +150 -0
  21. package/dist/components/action/input/select/segment-control/SegmentControl.svelte.d.ts +15 -0
  22. package/dist/components/action/input/select/slider/Slider.svelte +79 -0
  23. package/dist/components/action/input/select/slider/Slider.svelte.d.ts +11 -0
  24. package/dist/components/action/input/select/switch/Switch.svelte +122 -0
  25. package/dist/components/action/input/select/switch/Switch.svelte.d.ts +10 -0
  26. package/dist/components/action/input/select/theme-switch/ThemeSwitch.svelte +43 -0
  27. package/dist/components/action/input/select/theme-switch/ThemeSwitch.svelte.d.ts +10 -0
  28. package/dist/components/component-doc.d.ts +23 -0
  29. package/dist/components/component-doc.js +1 -0
  30. package/dist/components/display/annotation/badge/Badge.svelte +296 -0
  31. package/dist/components/display/annotation/badge/Badge.svelte.d.ts +16 -0
  32. package/dist/components/display/annotation/tag/Tag.svelte +227 -0
  33. package/dist/components/display/annotation/tag/Tag.svelte.d.ts +16 -0
  34. package/dist/components/display/annotation/tag-group/TagGroup.svelte +33 -0
  35. package/dist/components/display/annotation/tag-group/TagGroup.svelte.d.ts +12 -0
  36. package/dist/components/display/feedback/loading/loading-dots/LoadingDots.svelte +136 -0
  37. package/dist/components/display/feedback/loading/loading-dots/LoadingDots.svelte.d.ts +8 -0
  38. package/dist/components/display/feedback/loading/spinner/Spinner.svelte +110 -0
  39. package/dist/components/display/feedback/loading/spinner/Spinner.svelte.d.ts +8 -0
  40. package/dist/components/display/media/icon/Icon.svelte +41 -0
  41. package/dist/components/display/media/icon/Icon.svelte.d.ts +8 -0
  42. package/dist/components/display/media/icon/IconGlyph.svelte +56 -0
  43. package/dist/components/display/media/icon/IconGlyph.svelte.d.ts +8 -0
  44. package/dist/components/display/typography/eyebrow/Eyebrow.svelte +48 -0
  45. package/dist/components/display/typography/eyebrow/Eyebrow.svelte.d.ts +8 -0
  46. package/dist/components/display/typography/highlight/Highlight.svelte +160 -0
  47. package/dist/components/display/typography/highlight/Highlight.svelte.d.ts +10 -0
  48. package/dist/components/framework/layout/alignment/cluster/Cluster.svelte +142 -0
  49. package/dist/components/framework/layout/alignment/cluster/Cluster.svelte.d.ts +17 -0
  50. package/dist/components/framework/layout/alignment/grid/Grid.svelte +120 -0
  51. package/dist/components/framework/layout/alignment/grid/Grid.svelte.d.ts +18 -0
  52. package/dist/components/framework/layout/alignment/row/Row.svelte +142 -0
  53. package/dist/components/framework/layout/alignment/row/Row.svelte.d.ts +17 -0
  54. package/dist/components/framework/layout/alignment/stack/Stack.svelte +142 -0
  55. package/dist/components/framework/layout/alignment/stack/Stack.svelte.d.ts +17 -0
  56. package/dist/components/framework/layout/container/app-shell/AppShell.svelte +82 -0
  57. package/dist/components/framework/layout/container/app-shell/AppShell.svelte.d.ts +9 -0
  58. package/dist/components/framework/layout/container/page-header/PageHeader.svelte +114 -0
  59. package/dist/components/framework/layout/container/page-header/PageHeader.svelte.d.ts +11 -0
  60. package/dist/components/framework/layout/container/sidebar/Sidebar.svelte +134 -0
  61. package/dist/components/framework/layout/container/sidebar/Sidebar.svelte.d.ts +9 -0
  62. package/dist/components/framework/navigation/context/breadcrumb/Breadcrumb.svelte +72 -0
  63. package/dist/components/framework/navigation/context/breadcrumb/Breadcrumb.svelte.d.ts +10 -0
  64. package/dist/components/framework/navigation/sidenav/Sidenav.svelte +86 -0
  65. package/dist/components/framework/navigation/sidenav/Sidenav.svelte.d.ts +8 -0
  66. package/dist/components/framework/navigation/sidenav/SidenavTree.svelte +265 -0
  67. package/dist/components/framework/navigation/sidenav/SidenavTree.svelte.d.ts +14 -0
  68. package/dist/components/framework/navigation/sidenav/sidenav.d.ts +6 -0
  69. package/dist/components/framework/navigation/sidenav/sidenav.js +1 -0
  70. package/dist/components/framework/surface/paper/Paper.svelte +179 -0
  71. package/dist/components/framework/surface/paper/Paper.svelte.d.ts +18 -0
  72. package/dist/design-system/reserved-props/allowed-options.d.ts +46 -0
  73. package/dist/design-system/reserved-props/allowed-options.js +1 -0
  74. package/dist/design-system/reserved-props/blocked-props.d.ts +9 -0
  75. package/dist/design-system/reserved-props/blocked-props.js +1 -0
  76. package/dist/design-system/reserved-props/complex-types.d.ts +1 -0
  77. package/dist/design-system/reserved-props/complex-types.js +1 -0
  78. package/dist/design-system/reserved-props/type.d.ts +16 -0
  79. package/dist/design-system/reserved-props/type.js +1 -0
  80. package/dist/design-system/theme/tokens.d.ts +7 -0
  81. package/dist/design-system/theme/tokens.js +219 -0
  82. package/dist/icons/generated/chevron-double-down.svelte +16 -0
  83. package/dist/icons/generated/chevron-double-down.svelte.d.ts +7 -0
  84. package/dist/icons/generated/chevron-double-left.svelte +16 -0
  85. package/dist/icons/generated/chevron-double-left.svelte.d.ts +7 -0
  86. package/dist/icons/generated/chevron-double-right.svelte +16 -0
  87. package/dist/icons/generated/chevron-double-right.svelte.d.ts +7 -0
  88. package/dist/icons/generated/chevron-double-up.svelte +16 -0
  89. package/dist/icons/generated/chevron-double-up.svelte.d.ts +7 -0
  90. package/dist/icons/generated/chevron-down.svelte +16 -0
  91. package/dist/icons/generated/chevron-down.svelte.d.ts +7 -0
  92. package/dist/icons/generated/chevron-left.svelte +16 -0
  93. package/dist/icons/generated/chevron-left.svelte.d.ts +7 -0
  94. package/dist/icons/generated/chevron-right.svelte +16 -0
  95. package/dist/icons/generated/chevron-right.svelte.d.ts +7 -0
  96. package/dist/icons/generated/chevron-up.svelte +16 -0
  97. package/dist/icons/generated/chevron-up.svelte.d.ts +7 -0
  98. package/dist/icons/generated/fallback.svelte +17 -0
  99. package/dist/icons/generated/fallback.svelte.d.ts +7 -0
  100. package/dist/icons/generated/heart-circle-outline.svelte +16 -0
  101. package/dist/icons/generated/heart-circle-outline.svelte.d.ts +7 -0
  102. package/dist/icons/generated/heart-circle.svelte +16 -0
  103. package/dist/icons/generated/heart-circle.svelte.d.ts +7 -0
  104. package/dist/icons/generated/heart-half-full.svelte +16 -0
  105. package/dist/icons/generated/heart-half-full.svelte.d.ts +7 -0
  106. package/dist/icons/generated/heart-half-outline.svelte +16 -0
  107. package/dist/icons/generated/heart-half-outline.svelte.d.ts +7 -0
  108. package/dist/icons/generated/heart-half.svelte +16 -0
  109. package/dist/icons/generated/heart-half.svelte.d.ts +7 -0
  110. package/dist/icons/generated/heart-multiple-outline.svelte +23 -0
  111. package/dist/icons/generated/heart-multiple-outline.svelte.d.ts +7 -0
  112. package/dist/icons/generated/heart-multiple.svelte +23 -0
  113. package/dist/icons/generated/heart-multiple.svelte.d.ts +7 -0
  114. package/dist/icons/generated/heart-outline.svelte +16 -0
  115. package/dist/icons/generated/heart-outline.svelte.d.ts +7 -0
  116. package/dist/icons/generated/heart.svelte +16 -0
  117. package/dist/icons/generated/heart.svelte.d.ts +7 -0
  118. package/dist/icons/generated/home-edit-outline.svelte +16 -0
  119. package/dist/icons/generated/home-edit-outline.svelte.d.ts +7 -0
  120. package/dist/icons/generated/home-edit.svelte +16 -0
  121. package/dist/icons/generated/home-edit.svelte.d.ts +7 -0
  122. package/dist/icons/generated/home-outline.svelte +16 -0
  123. package/dist/icons/generated/home-outline.svelte.d.ts +7 -0
  124. package/dist/icons/generated/home.svelte +16 -0
  125. package/dist/icons/generated/home.svelte.d.ts +7 -0
  126. package/dist/icons/generated/index.d.ts +24 -0
  127. package/dist/icons/generated/index.js +24 -0
  128. package/dist/icons/generated/manifest.d.ts +7 -0
  129. package/dist/icons/generated/manifest.js +198 -0
  130. package/dist/icons/generated/tag-heart-outline.svelte +16 -0
  131. package/dist/icons/generated/tag-heart-outline.svelte.d.ts +7 -0
  132. package/dist/icons/generated/tag-heart.svelte +16 -0
  133. package/dist/icons/generated/tag-heart.svelte.d.ts +7 -0
  134. package/dist/index.d.ts +32 -0
  135. package/dist/index.js +28 -0
  136. package/dist/svelte.d.ts +18 -0
  137. package/dist/theme.css +161 -0
  138. package/package.json +69 -0
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ import Breadcrumb, {
6
+ type BreadcrumbItem,
7
+ } from '../../../navigation/context/breadcrumb/Breadcrumb.svelte';
8
+ import Eyebrow from '../../../../display/typography/eyebrow/Eyebrow.svelte';
9
+
10
+ type Props = SeblifyProps<{
11
+ actions?: Snippet;
12
+ breadcrumbItems?: BreadcrumbItem[];
13
+ eyebrow?: string;
14
+ summary?: string;
15
+ title: string;
16
+ }>;
17
+
18
+ let {
19
+ actions,
20
+ breadcrumbItems = [],
21
+ eyebrow,
22
+ summary,
23
+ title,
24
+ }: Props = $props();
25
+ </script>
26
+
27
+ <header class="page-header">
28
+ {#if breadcrumbItems.length > 0}
29
+ <div class="page-header__breadcrumb">
30
+ <Breadcrumb items={breadcrumbItems} />
31
+ </div>
32
+ {/if}
33
+
34
+ <div class="page-header__content">
35
+ <div class="page-header__copy">
36
+ {#if eyebrow}
37
+ <div class="page-header__eyebrow">
38
+ <Eyebrow>{eyebrow}</Eyebrow>
39
+ </div>
40
+ {/if}
41
+
42
+ <h1>{title}</h1>
43
+
44
+ {#if summary}
45
+ <p>{summary}</p>
46
+ {/if}
47
+ </div>
48
+
49
+ {#if actions}
50
+ <div class="page-header__actions">
51
+ {@render actions()}
52
+ </div>
53
+ {/if}
54
+ </div>
55
+ </header>
56
+
57
+ <style>
58
+ .page-header {
59
+ display: grid;
60
+ gap: 20px;
61
+ max-width: 860px;
62
+ }
63
+
64
+ .page-header__breadcrumb {
65
+ min-width: 0;
66
+ }
67
+
68
+ .page-header__content {
69
+ display: flex;
70
+ gap: 24px;
71
+ align-items: end;
72
+ justify-content: space-between;
73
+ }
74
+
75
+ .page-header__copy {
76
+ min-width: 0;
77
+ }
78
+
79
+ .page-header__eyebrow {
80
+ margin: 0 0 12px;
81
+ }
82
+
83
+ h1 {
84
+ margin: 0 0 16px;
85
+ color: var(--seblify-color-text, #172033);
86
+ font-size: 3rem;
87
+ line-height: 1.05;
88
+ }
89
+
90
+ p {
91
+ margin: 0;
92
+ color: var(--seblify-color-text-muted, #516176);
93
+ font-size: 1.125rem;
94
+ line-height: 1.6;
95
+ }
96
+
97
+ .page-header__actions {
98
+ flex: 0 0 auto;
99
+ }
100
+
101
+ @media (max-width: 760px) {
102
+ .page-header__content {
103
+ display: block;
104
+ }
105
+
106
+ .page-header__actions {
107
+ margin-top: 16px;
108
+ }
109
+
110
+ h1 {
111
+ font-size: 2.25rem;
112
+ }
113
+ }
114
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type BreadcrumbItem } from '../../../navigation/context/breadcrumb/Breadcrumb.svelte';
3
+ declare const PageHeader: import("svelte").Component<{
4
+ actions?: Snippet | undefined;
5
+ breadcrumbItems?: BreadcrumbItem[] | undefined;
6
+ eyebrow?: string | undefined;
7
+ summary?: string | undefined;
8
+ title: string;
9
+ }, {}, "">;
10
+ type PageHeader = ReturnType<typeof PageHeader>;
11
+ export default PageHeader;
@@ -0,0 +1,134 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = SeblifyProps<{
6
+ children?: Snippet;
7
+ header?: Snippet;
8
+ label?: string;
9
+ open?: boolean;
10
+ }>;
11
+
12
+ let {
13
+ children,
14
+ header,
15
+ label = 'Sidebar',
16
+ open = $bindable(false),
17
+ }: Props = $props();
18
+
19
+ function close(): void {
20
+ open = false;
21
+ }
22
+ </script>
23
+
24
+ {#if open}
25
+ <button
26
+ aria-label="Close sidebar"
27
+ class="sidebar-backdrop"
28
+ onclick={close}
29
+ type="button"
30
+ ></button>
31
+ {/if}
32
+
33
+ <aside aria-label={label} class:open class="sidebar">
34
+ <div class="sidebar__inner">
35
+ {#if header}
36
+ <div class="sidebar__header">
37
+ {@render header()}
38
+ <button
39
+ aria-label="Close sidebar"
40
+ class="sidebar__close"
41
+ onclick={close}
42
+ type="button"
43
+ >
44
+ Close
45
+ </button>
46
+ </div>
47
+ {/if}
48
+
49
+ <div class="sidebar__body">
50
+ {@render children?.()}
51
+ </div>
52
+ </div>
53
+ </aside>
54
+
55
+ <style>
56
+ .sidebar {
57
+ position: sticky;
58
+ top: 0;
59
+ z-index: 30;
60
+ height: 100vh;
61
+ overflow: auto;
62
+ border-right: 1px solid var(--seblify-color-surface-muted, #d9e0e8);
63
+ background: var(--seblify-color-surface, #ffffff);
64
+ color: var(--seblify-color-text, #172033);
65
+ }
66
+
67
+ .sidebar__inner {
68
+ display: grid;
69
+ grid-template-rows: auto minmax(0, 1fr);
70
+ align-content: start;
71
+ gap: 8px;
72
+ min-height: 100%;
73
+ padding: var(--sidebar-padding, 18px 14px);
74
+ }
75
+
76
+ .sidebar__header {
77
+ display: flex;
78
+ gap: 12px;
79
+ align-items: flex-start;
80
+ justify-content: space-between;
81
+ }
82
+
83
+ .sidebar__body {
84
+ align-self: start;
85
+ min-width: 0;
86
+ }
87
+
88
+ .sidebar__close,
89
+ .sidebar-backdrop {
90
+ display: none;
91
+ }
92
+
93
+ @media (max-width: 760px) {
94
+ .sidebar {
95
+ position: fixed;
96
+ inset: 0 auto 0 0;
97
+ width: min(320px, calc(100vw - 48px));
98
+ max-width: 100%;
99
+ border-right: 1px solid var(--seblify-color-surface-muted, #d9e0e8);
100
+ box-shadow: var(--seblify-shadow-raised, 0 12px 28px rgb(23 32 51 / 22%));
101
+ transform: translateX(-100%);
102
+ transition: transform 160ms ease;
103
+ }
104
+
105
+ .open {
106
+ transform: translateX(0);
107
+ }
108
+
109
+ .sidebar__close {
110
+ display: inline-flex;
111
+ min-height: 34px;
112
+ align-items: center;
113
+ border: 1px solid var(--seblify-color-border, #c8d2df);
114
+ border-radius: 6px;
115
+ padding: 0 10px;
116
+ color: var(--seblify-color-text, #172033);
117
+ background: var(--seblify-color-surface, #ffffff);
118
+ font: inherit;
119
+ font-size: 0.875rem;
120
+ font-weight: 800;
121
+ cursor: pointer;
122
+ }
123
+
124
+ .sidebar-backdrop {
125
+ position: fixed;
126
+ inset: 0;
127
+ z-index: 20;
128
+ display: block;
129
+ border: 0;
130
+ background: rgb(13 17 23 / 48%);
131
+ cursor: pointer;
132
+ }
133
+ }
134
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ declare const Sidebar: import("svelte").Component<{
3
+ children?: Snippet | undefined;
4
+ header?: Snippet | undefined;
5
+ label?: string | undefined;
6
+ open?: boolean | undefined;
7
+ }, {}, "open">;
8
+ type Sidebar = ReturnType<typeof Sidebar>;
9
+ export default Sidebar;
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ export interface BreadcrumbItem {
4
+ label: string;
5
+ href?: string;
6
+ }
7
+
8
+ type Props = SeblifyProps<{
9
+ items: BreadcrumbItem[];
10
+ label?: string;
11
+ }>;
12
+
13
+ let { items, label = 'Breadcrumb' }: Props = $props();
14
+ </script>
15
+
16
+ <nav aria-label={label} class="breadcrumb">
17
+ <ol>
18
+ {#each items as item, index}
19
+ {@const isCurrent = index === items.length - 1}
20
+ <li>
21
+ {#if item.href && !isCurrent}
22
+ <a href={item.href}>{item.label}</a>
23
+ {:else}
24
+ <span aria-current={isCurrent ? 'page' : undefined}>{item.label}</span
25
+ >
26
+ {/if}
27
+ </li>
28
+ {/each}
29
+ </ol>
30
+ </nav>
31
+
32
+ <style>
33
+ .breadcrumb {
34
+ color: var(--seblify-color-text-subtle, #667085);
35
+ font-size: 0.875rem;
36
+ font-weight: 700;
37
+ }
38
+
39
+ ol {
40
+ display: flex;
41
+ flex-wrap: wrap;
42
+ gap: 8px;
43
+ align-items: center;
44
+ margin: 0;
45
+ padding: 0;
46
+ list-style: none;
47
+ }
48
+
49
+ li {
50
+ display: inline-flex;
51
+ gap: 8px;
52
+ align-items: center;
53
+ }
54
+
55
+ li:not(:first-child)::before {
56
+ content: '/';
57
+ color: var(--seblify-color-border-strong, #aab7c5);
58
+ }
59
+
60
+ a {
61
+ color: var(--seblify-color-fill-accent, #146a6f);
62
+ text-decoration: none;
63
+ }
64
+
65
+ a:hover {
66
+ text-decoration: underline;
67
+ }
68
+
69
+ span[aria-current='page'] {
70
+ color: var(--seblify-color-text, #172033);
71
+ }
72
+ </style>
@@ -0,0 +1,10 @@
1
+ export interface BreadcrumbItem {
2
+ label: string;
3
+ href?: string;
4
+ }
5
+ declare const Breadcrumb: import("svelte").Component<{
6
+ items: BreadcrumbItem[];
7
+ label?: string | undefined;
8
+ }, {}, "">;
9
+ type Breadcrumb = ReturnType<typeof Breadcrumb>;
10
+ export default Breadcrumb;
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ import { untrack } from 'svelte';
4
+ import SidenavTree from './SidenavTree.svelte';
5
+ import type { SidenavItem } from './sidenav';
6
+
7
+ type Props = SeblifyProps<{
8
+ items: SidenavItem[];
9
+ currentHref?: string;
10
+ label?: string;
11
+ }>;
12
+
13
+ let { items, currentHref, label = 'Navigation' }: Props = $props();
14
+ let expandedItemId = $state<string | undefined>();
15
+ let collapsedItemId = $state<string | undefined>();
16
+ let retainedExpandedItemId = $state<string | undefined>();
17
+ let previousSelectedItemId: string | undefined;
18
+ let selectedItemIds = $derived(findSelectedItemIds(items, currentHref));
19
+ let selectedItemId = $derived(selectedItemIds.at(-1));
20
+
21
+ $effect(() => {
22
+ const nextSelectedItemId = selectedItemId;
23
+
24
+ untrack(() => {
25
+ if (!nextSelectedItemId) {
26
+ return;
27
+ }
28
+
29
+ if (nextSelectedItemId !== previousSelectedItemId) {
30
+ expandedItemId = undefined;
31
+ collapsedItemId = undefined;
32
+ }
33
+
34
+ previousSelectedItemId = nextSelectedItemId;
35
+ retainedExpandedItemId = nextSelectedItemId;
36
+ });
37
+ });
38
+
39
+ function setItemOpen(id: string, open: boolean): void {
40
+ expandedItemId = open ? id : undefined;
41
+ collapsedItemId = open ? undefined : id;
42
+ }
43
+
44
+ function findSelectedItemIds(
45
+ navItems: SidenavItem[],
46
+ href: string | undefined,
47
+ ): string[] {
48
+ if (!href) {
49
+ return [];
50
+ }
51
+
52
+ for (const item of navItems) {
53
+ if (item.href === href) {
54
+ return [item.id];
55
+ }
56
+
57
+ const childPath = findSelectedItemIds(item.children ?? [], href);
58
+
59
+ if (item.children?.length && childPath.length) {
60
+ return [item.id, ...childPath];
61
+ }
62
+ }
63
+
64
+ return [];
65
+ }
66
+ </script>
67
+
68
+ <nav aria-label={label} class="sidenav">
69
+ <SidenavTree
70
+ activeItemIds={selectedItemIds}
71
+ {collapsedItemId}
72
+ {currentHref}
73
+ depth={0}
74
+ {expandedItemId}
75
+ {items}
76
+ onToggle={setItemOpen}
77
+ {retainedExpandedItemId}
78
+ />
79
+ </nav>
80
+
81
+ <style>
82
+ .sidenav {
83
+ color: var(--seblify-color-text-muted, #516176);
84
+ font-size: 0.925rem;
85
+ }
86
+ </style>
@@ -0,0 +1,8 @@
1
+ import type { SidenavItem } from './sidenav';
2
+ declare const Sidenav: import("svelte").Component<{
3
+ items: SidenavItem[];
4
+ currentHref?: string | undefined;
5
+ label?: string | undefined;
6
+ }, {}, "">;
7
+ type Sidenav = ReturnType<typeof Sidenav>;
8
+ export default Sidenav;
@@ -0,0 +1,265 @@
1
+ <script lang="ts">
2
+ import type { SeblifyProps } from 'seblify';
3
+ import SidenavTree from './SidenavTree.svelte';
4
+ import type { SidenavItem } from './sidenav';
5
+
6
+ type Props = SeblifyProps<{
7
+ activeItemIds: string[];
8
+ collapsedItemId?: string;
9
+ items: SidenavItem[];
10
+ currentHref?: string;
11
+ depth: number;
12
+ expandedItemId?: string;
13
+ onToggle: (id: string, open: boolean) => void;
14
+ retainedExpandedItemId?: string;
15
+ }>;
16
+
17
+ let {
18
+ activeItemIds,
19
+ collapsedItemId,
20
+ items,
21
+ currentHref,
22
+ depth,
23
+ expandedItemId,
24
+ onToggle,
25
+ retainedExpandedItemId,
26
+ }: Props = $props();
27
+
28
+ function isCurrent(item: SidenavItem): boolean {
29
+ return Boolean(item.href && item.href === currentHref);
30
+ }
31
+
32
+ function hasCurrentDescendant(item: SidenavItem): boolean {
33
+ return Boolean(
34
+ item.children?.some(
35
+ (child) =>
36
+ Boolean(child.href && child.href === currentHref) ||
37
+ hasCurrentDescendant(child),
38
+ ),
39
+ );
40
+ }
41
+
42
+ function containsItemId(item: SidenavItem, id: string | undefined): boolean {
43
+ return Boolean(
44
+ id &&
45
+ item.children?.some(
46
+ (child) => child.id === id || containsItemId(child, id),
47
+ ),
48
+ );
49
+ }
50
+
51
+ function isOpen(item: SidenavItem): boolean {
52
+ if (collapsedItemId === item.id) {
53
+ return false;
54
+ }
55
+
56
+ if (activeItemIds.includes(item.id)) {
57
+ return true;
58
+ }
59
+
60
+ if (expandedItemId) {
61
+ return item.id === expandedItemId || containsItemId(item, expandedItemId);
62
+ }
63
+
64
+ return (
65
+ item.id === retainedExpandedItemId ||
66
+ containsItemId(item, retainedExpandedItemId)
67
+ );
68
+ }
69
+ </script>
70
+
71
+ <ul class:root={depth === 0} class="sidenav-list">
72
+ {#each items as item}
73
+ {@const children = item.children ?? []}
74
+ {@const hasChildren = children.length > 0}
75
+ {@const current = isCurrent(item)}
76
+ {@const selectedBranch =
77
+ hasChildren && (current || hasCurrentDescendant(item))}
78
+ {@const sectionHeader = !item.href && !hasChildren}
79
+ {@const expanded = hasChildren && isOpen(item)}
80
+ {@const activeBranch = hasChildren && (expanded || selectedBranch)}
81
+ <li
82
+ class:active-branch={activeBranch}
83
+ class:item-node={!sectionHeader}
84
+ class:section-header={sectionHeader}
85
+ >
86
+ {#if sectionHeader}
87
+ <div class="section-label">{item.label}</div>
88
+ {:else}
89
+ <div class="item-row" style:--sidenav-indent={`${depth * 12}px`}>
90
+ {#if item.href}
91
+ <a class:current href={item.href}>{item.label}</a>
92
+ {:else}
93
+ <button
94
+ class:current
95
+ class="item-button"
96
+ onclick={() => onToggle(item.id, !expanded)}
97
+ type="button"
98
+ >
99
+ {item.label}
100
+ </button>
101
+ {/if}
102
+
103
+ {#if hasChildren}
104
+ <button
105
+ aria-expanded={expanded}
106
+ aria-label={expanded
107
+ ? `Collapse ${item.label}`
108
+ : `Expand ${item.label}`}
109
+ class="toggle"
110
+ onclick={() => onToggle(item.id, !expanded)}
111
+ type="button"
112
+ >
113
+ <span class:expanded>&gt;</span>
114
+ </button>
115
+ {/if}
116
+ </div>
117
+
118
+ {#if hasChildren && expanded}
119
+ <SidenavTree
120
+ {activeItemIds}
121
+ {collapsedItemId}
122
+ {currentHref}
123
+ depth={depth + 1}
124
+ {expandedItemId}
125
+ items={children}
126
+ {onToggle}
127
+ {retainedExpandedItemId}
128
+ />
129
+ {/if}
130
+ {/if}
131
+ </li>
132
+ {/each}
133
+ </ul>
134
+
135
+ <style>
136
+ .sidenav-list {
137
+ display: grid;
138
+ gap: 2px;
139
+ margin: 0;
140
+ padding: 0;
141
+ list-style: none;
142
+ }
143
+
144
+ .sidenav-list:not(.root) {
145
+ margin-top: 3px;
146
+ }
147
+
148
+ li {
149
+ border-radius: 8px;
150
+ }
151
+
152
+ .item-node {
153
+ padding: 4px;
154
+ }
155
+
156
+ .active-branch {
157
+ background: var(--seblify-color-surface-hover, #f1f4f9);
158
+ }
159
+
160
+ .section-header {
161
+ margin: 14px 0 4px;
162
+ }
163
+
164
+ .root > .section-header {
165
+ margin-top: 22px;
166
+ }
167
+
168
+ .section-label {
169
+ padding: 0 10px;
170
+ color: var(--seblify-color-text-subtle, #667085);
171
+ font-size: 0.72rem;
172
+ font-weight: 900;
173
+ letter-spacing: 0;
174
+ text-transform: uppercase;
175
+ }
176
+
177
+ .item-row {
178
+ display: grid;
179
+ grid-template-columns: minmax(0, 1fr) auto;
180
+ gap: 8px;
181
+ align-items: center;
182
+ min-height: 34px;
183
+ }
184
+
185
+ .root > .item-node > .item-row {
186
+ font-size: 1rem;
187
+ }
188
+
189
+ .sidenav-list:not(.root) > .item-node > .item-row {
190
+ font-size: 0.875rem;
191
+ }
192
+
193
+ .sidenav-list:not(.root) a,
194
+ .sidenav-list:not(.root) .item-button {
195
+ font-weight: 400;
196
+ }
197
+
198
+ a,
199
+ .item-button {
200
+ min-width: 0;
201
+ border: 0;
202
+ border-radius: 6px;
203
+ padding: 8px 8px 8px calc(10px + var(--sidenav-indent));
204
+ overflow: hidden;
205
+ color: inherit;
206
+ background: transparent;
207
+ font: inherit;
208
+ font-weight: 750;
209
+ text-align: left;
210
+ text-decoration: none;
211
+ text-overflow: ellipsis;
212
+ white-space: nowrap;
213
+ }
214
+
215
+ .item-button {
216
+ cursor: pointer;
217
+ }
218
+
219
+ a:hover,
220
+ .item-button:hover,
221
+ .toggle:hover {
222
+ color: var(--seblify-color-text, #172033);
223
+ }
224
+
225
+ .current {
226
+ color: var(--seblify-color-text, #172033);
227
+ }
228
+
229
+ .root > .item-node > .item-row > .current {
230
+ font-weight: 750;
231
+ }
232
+
233
+ .sidenav-list:not(.root) .current {
234
+ font-weight: 400;
235
+ }
236
+
237
+ .active-branch > .item-row > a,
238
+ .active-branch > .item-row > .item-button {
239
+ color: var(--seblify-color-text, #172033);
240
+ }
241
+
242
+ .toggle {
243
+ display: grid;
244
+ width: 30px;
245
+ height: 30px;
246
+ place-items: center;
247
+ border: 0;
248
+ border-radius: 6px;
249
+ color: var(--seblify-color-text-subtle, #667085);
250
+ background: transparent;
251
+ font: inherit;
252
+ cursor: pointer;
253
+ }
254
+
255
+ .toggle span {
256
+ display: block;
257
+ font-size: 1.15rem;
258
+ line-height: 1;
259
+ transition: transform 120ms ease;
260
+ }
261
+
262
+ .toggle span.expanded {
263
+ transform: rotate(90deg);
264
+ }
265
+ </style>