tera-system-ui 0.1.70 → 0.1.72
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.
- package/dist/components/dialog/Dialog.d.ts +2 -0
- package/dist/components/dialog/Dialog.svelte +7 -1
- package/dist/components/dialog/dialog.scss +0 -184
- package/dist/components/side-navigation/SideNavigation.d.ts +38 -1
- package/dist/components/side-navigation/SideNavigation.js +28 -0
- package/dist/components/side-navigation/SideNavigation.svelte +44 -13
- package/dist/components/side-navigation/SideNavigationGroup.svelte +40 -0
- package/dist/components/side-navigation/SideNavigationGroup.svelte.d.ts +10 -0
- package/dist/components/side-navigation/index.d.ts +3 -2
- package/dist/components/side-navigation/index.js +2 -1
- package/dist/components/side-navigation/sidenav.scss +61 -0
- package/dist/hooks/closeOnBackNavigation.svelte.d.ts +19 -0
- package/dist/hooks/closeOnBackNavigation.svelte.js +40 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/llms/colors.md +18 -18
- package/dist/llms/dialog.md +1 -0
- package/dist/llms/side-navigation.md +27 -6
- package/package.json +1 -1
|
@@ -21,6 +21,8 @@ export type DialogProps = {
|
|
|
21
21
|
padding?: 'none' | undefined;
|
|
22
22
|
/** Whether clicking the backdrop closes the dialog. */
|
|
23
23
|
closeOnClickOutside?: boolean;
|
|
24
|
+
/** Whether pressing the browser Back button closes the dialog. Default true. */
|
|
25
|
+
closeOnNavigateBack?: boolean;
|
|
24
26
|
/** Whether to render a close (×) button in the header. */
|
|
25
27
|
closeButton?: boolean;
|
|
26
28
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script lang="ts">import { Dialog as DialogPrimitive } from 'bits-ui';
|
|
2
2
|
import IconX from '../icons/IconX.svelte';
|
|
3
3
|
import { cn } from '../../utils/index.js';
|
|
4
|
-
|
|
4
|
+
import { closeOnBackNavigation } from '../../hooks';
|
|
5
|
+
let { children, open = $bindable(false), ref = $bindable(null), closeOnClickOutside = true, closeOnNavigateBack = true, closeButton = true, size = 'sm', header, footer, class: className, position = 'center', padding, staticRender = false, preventScroll, onOpenChangeComplete, triggerRef, focusTriggerAfterClose = true, containerClass, headerClass, bodyClass, footerClass, ...props } = $props();
|
|
5
6
|
const sizeClass = {
|
|
6
7
|
xs: 'max-w-[min(24rem,calc(100vw-2rem))]',
|
|
7
8
|
sm: 'max-w-[min(30rem,calc(100vw-2rem))]',
|
|
@@ -20,6 +21,11 @@ function handleCloseAutoFocus(e) {
|
|
|
20
21
|
triggerRef.focus();
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
closeOnBackNavigation({
|
|
25
|
+
enabled: () => closeOnNavigateBack,
|
|
26
|
+
isOpen: () => open,
|
|
27
|
+
close: () => { open = false; },
|
|
28
|
+
});
|
|
23
29
|
</script>
|
|
24
30
|
|
|
25
31
|
<DialogPrimitive.Root bind:open onOpenChangeComplete={onOpenChangeComplete}>
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
@reference "tailwindcss/theme";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
.dialog-box {
|
|
5
|
-
max-height: min(calc(100svh - 1rem), 55rem);
|
|
6
|
-
overflow-y: auto;
|
|
7
|
-
overscroll-behavior: contain;
|
|
8
|
-
display: grid;
|
|
9
|
-
grid-template-rows: auto 1fr auto;
|
|
10
|
-
width: 100%;
|
|
11
|
-
background: var(--color-surface);
|
|
12
|
-
border-radius: var(--border-radius-container);
|
|
13
|
-
position: relative;
|
|
14
|
-
top: 0;
|
|
15
|
-
left: 0;
|
|
16
|
-
bottom: 0;
|
|
17
|
-
main {
|
|
18
|
-
overflow: auto;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
dialog, dialog:modal {
|
|
23
|
-
max-height: 100svh;
|
|
24
|
-
width: 100%;
|
|
25
|
-
height: fit-content;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
dialog {
|
|
29
|
-
&[size="xs"] {
|
|
30
|
-
width: 24rem;
|
|
31
|
-
max-width: min(calc(100vw - 1rem), 24rem);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
&[size="sm"] {
|
|
35
|
-
width: 30rem;
|
|
36
|
-
max-width: min(calc(100vw - 1rem), 30rem);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&[size="md"] {
|
|
40
|
-
width: 40rem;
|
|
41
|
-
max-width: min(calc(100vw - 1rem), 40rem);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&[size="lg"] {
|
|
45
|
-
width: 50rem;
|
|
46
|
-
max-width: min(calc(100vw - 1rem), 50rem);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&[size="full"] {
|
|
50
|
-
width: 100vw;
|
|
51
|
-
max-width: 100vw;
|
|
52
|
-
border-radius: 0;
|
|
53
|
-
}
|
|
54
|
-
&[size="auto"] {
|
|
55
|
-
width: fit-content;
|
|
56
|
-
max-width: 100vw;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
dialog[open] {
|
|
62
|
-
display: inline-flex;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
dialog {
|
|
66
|
-
--transition-duration: 0.25s;
|
|
67
|
-
--transition-easing: var(--ease-spring);
|
|
68
|
-
transform: translateY(0px);
|
|
69
|
-
margin: auto;
|
|
70
|
-
box-shadow: var(--shadow-lg);
|
|
71
|
-
border-radius: var(--border-radius-container);
|
|
72
|
-
|
|
73
|
-
&[data-position="top"] {
|
|
74
|
-
position: fixed;
|
|
75
|
-
top: 0;
|
|
76
|
-
left: 50%;
|
|
77
|
-
transform: translateX(-50%) translateY(0.5rem);
|
|
78
|
-
margin: 0;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
&, &::backdrop {
|
|
82
|
-
transition: opacity var(--transition-duration) var(--transition-easing),
|
|
83
|
-
transform var(--transition-duration) var(--transition-easing),
|
|
84
|
-
display var(--transition-duration) allow-discrete,
|
|
85
|
-
overlay var(--transition-duration) allow-discrete,
|
|
86
|
-
box-shadow var(--transition-duration) var(--transition-easing);
|
|
87
|
-
|
|
88
|
-
opacity: 0;
|
|
89
|
-
|
|
90
|
-
will-change: transform, opacity, box-shadow;
|
|
91
|
-
backface-visibility: hidden;
|
|
92
|
-
-webkit-backface-visibility: hidden;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
&[open] {
|
|
97
|
-
animation: dialog-enter var(--transition-duration) var(--transition-easing);
|
|
98
|
-
opacity: 1;
|
|
99
|
-
box-shadow: var(--shadow-lg);
|
|
100
|
-
&::backdrop {
|
|
101
|
-
opacity: 1;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
&[data-position="top"] {
|
|
105
|
-
animation: slide-in-top var(--transition-duration) var(--transition-easing);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
&:not([open]) {
|
|
110
|
-
animation: dialog-exit var(--transition-duration) var(--ease-ui);
|
|
111
|
-
opacity: 0;
|
|
112
|
-
box-shadow: var(--shadow-sm);
|
|
113
|
-
&::backdrop {
|
|
114
|
-
opacity: 0;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
&[data-position="top"] {
|
|
118
|
-
animation: slide-out-top var(--transition-duration) var(--ease-ui);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@keyframes dialog-enter {
|
|
125
|
-
from {
|
|
126
|
-
transform: scale(0.95);
|
|
127
|
-
opacity: 0;
|
|
128
|
-
}
|
|
129
|
-
to {
|
|
130
|
-
transform: scale(1);
|
|
131
|
-
opacity: 1;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
@keyframes dialog-exit {
|
|
136
|
-
from {
|
|
137
|
-
transform: scale(1);
|
|
138
|
-
opacity: 1;
|
|
139
|
-
}
|
|
140
|
-
to {
|
|
141
|
-
transform: scale(0.95);
|
|
142
|
-
opacity: 0;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
@keyframes slide-in-top {
|
|
147
|
-
from {
|
|
148
|
-
top: 0;
|
|
149
|
-
left: 50%;
|
|
150
|
-
transform: translateX(-50%) translateY(-40px);
|
|
151
|
-
opacity: 0;
|
|
152
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
153
|
-
}
|
|
154
|
-
to {
|
|
155
|
-
top: 0;
|
|
156
|
-
left: 50%;
|
|
157
|
-
transform: translateX(-50%) translateY(0.5rem);
|
|
158
|
-
opacity: 1;
|
|
159
|
-
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.24), 0 8px 16px rgba(0, 0, 0, 0.12);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
@keyframes slide-out-top {
|
|
164
|
-
from {
|
|
165
|
-
top: 0;
|
|
166
|
-
left: 50%;
|
|
167
|
-
transform: translateX(-50%) translateY(0.5rem);
|
|
168
|
-
opacity: 1;
|
|
169
|
-
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.24), 0 8px 16px rgba(0, 0, 0, 0.12);
|
|
170
|
-
}
|
|
171
|
-
to {
|
|
172
|
-
top: 0;
|
|
173
|
-
left: 50%;
|
|
174
|
-
transform: translateX(-50%) translateY(-40px);
|
|
175
|
-
opacity: 0;
|
|
176
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
dialog::backdrop {
|
|
181
|
-
backdrop-filter: blur(0.2rem);
|
|
182
|
-
background: --alpha(var(--color-neutral-token-13) / 20%);
|
|
183
|
-
transition: opacity var(--transition-duration) ease-out;
|
|
184
|
-
}
|
|
@@ -1,20 +1,55 @@
|
|
|
1
1
|
import { type VariantProps } from "tailwind-variants";
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
/**
|
|
5
|
+
* App-shell sidebar navigation. Use `SideNavigationLayout` as the outermost wrapper of a page: it renders the fixed sidebar plus a content region for your `Header`, the page body, and optional bottom controls. `SideNavigation` is the bar itself and is normally driven through the layout. Wrap the app in `TeraUiContext` so `sideNavHref` (active route) and `language` are supplied globally, or pass them as props directly.
|
|
6
|
+
*
|
|
7
|
+
* The `items` prop is a single flat array that may MIX two shapes in any order: (1) a link item `{ href, title, icon?, exactHref? }` — a single navigable entry, where `exactHref` (falling back to `href`) is compared to the active route to apply the `selected` highlight; and (2) a group `{ id, title, items, defaultExpanded? }` — a collapsible section whose `items` are link items. A group renders a compact, fixed-height header (title + chevron, no icon) that expands/collapses its children; in the compact icon-only rail the header collapses to a thin divider so groups stay separated without large gaps, while keeping a stable height so items don't shift. Group children render with their own icons.
|
|
8
|
+
*
|
|
9
|
+
* Group expand/collapse state is PERSISTED to localStorage under the key `side-nav-group-state` (a JSON map of `{ [groupId]: boolean }`), keyed by the group's `id` — so `id` must be stable and unique. `defaultExpanded` only sets the initial state the first time a group `id` is seen (before any saved value exists). A group that contains the active route is always force-expanded on mount, regardless of saved state.
|
|
10
|
+
*
|
|
11
|
+
* If `items` is omitted, a built-in default navigation (calculator / converter / docs) is rendered. The sidebar's own expand vs. compact (icon-only) state is toggled by calling the exported `toggleSideNavigation()` (e.g. from a hamburger button) and is persisted at the xl breakpoint under localStorage key `side-nav-xl-state`; on smaller widths the bar is icon-only and temporarily expands on hover.
|
|
12
|
+
*
|
|
13
|
+
* Example: `<SideNavigationLayout items={[{ href: '/home', title: 'Home', icon: IconHome }, { id: 'tools', title: 'Tools', defaultExpanded: true, items: [{ href: '/calc', exactHref: '/calc', title: 'Calculator', icon: IconCalc }] }]}> <Header onHamburgerClick={toggleSideNavigation}>...</Header> <div>Page content</div> </SideNavigationLayout>`
|
|
14
|
+
*/
|
|
4
15
|
export declare const styles: import("tailwind-variants").TVReturnType<{}, undefined, "", {}, undefined, import("tailwind-variants").TVReturnType<{}, undefined, "", unknown, unknown, undefined>>;
|
|
5
16
|
type SideNavigationVariants = VariantProps<typeof styles>;
|
|
17
|
+
/** A single navigable link in the sidebar. */
|
|
6
18
|
export type SideNavigationItem = {
|
|
19
|
+
/** Destination URL for the link. */
|
|
7
20
|
href: string;
|
|
21
|
+
/** Route used for active-state matching; falls back to `href` when omitted. */
|
|
8
22
|
exactHref?: string;
|
|
23
|
+
/** Icon component (e.g. a Tabler icon) rendered before the title. */
|
|
9
24
|
icon?: any;
|
|
25
|
+
/** Visible label. */
|
|
10
26
|
title: string;
|
|
11
27
|
};
|
|
28
|
+
/** A collapsible section grouping several link items under a header. */
|
|
29
|
+
export type SideNavigationGroup = {
|
|
30
|
+
/** Stable, unique key — used to persist this group's expanded state in localStorage. */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Header label (the group header shows title + chevron only, no icon). */
|
|
33
|
+
title: string;
|
|
34
|
+
/** Initial expanded state the first time the group is seen (before any saved value). */
|
|
35
|
+
defaultExpanded?: boolean;
|
|
36
|
+
/** Child links shown when the group is expanded. */
|
|
37
|
+
items: SideNavigationItem[];
|
|
38
|
+
};
|
|
39
|
+
/** An entry in the `items` array: either a flat link item or a collapsible group. */
|
|
40
|
+
export type SideNavigationEntry = SideNavigationItem | SideNavigationGroup;
|
|
41
|
+
/** Type guard: true when an entry is a {@link SideNavigationGroup} (has an `items` array). */
|
|
42
|
+
export declare function isSideNavigationGroup(entry: SideNavigationEntry): entry is SideNavigationGroup;
|
|
12
43
|
export interface SideNavigationProps extends HTMLAttributes<HTMLElement>, SideNavigationVariants {
|
|
13
44
|
children?: Snippet;
|
|
14
45
|
class?: string;
|
|
46
|
+
/** Current active route, used to highlight the matching item. Usually supplied via TeraUiContext. */
|
|
15
47
|
sideNavHref?: string;
|
|
48
|
+
/** Active language code; localizes the built-in default nav links. Usually supplied via TeraUiContext. */
|
|
16
49
|
language?: string;
|
|
17
|
-
items
|
|
50
|
+
/** Navigation entries: a flat array mixing link items ({ href, title, icon?, exactHref? }) and collapsible groups ({ id, title, items, defaultExpanded? }). Omit to render the built-in default nav. */
|
|
51
|
+
items?: SideNavigationEntry[];
|
|
52
|
+
/** Snippet rendered pinned at the bottom of the sidebar (e.g. a theme toggle). */
|
|
18
53
|
bottomChildren?: Snippet;
|
|
19
54
|
ref?: HTMLElement | null;
|
|
20
55
|
}
|
|
@@ -24,6 +59,8 @@ export declare const SCREEN_BREAK_POINTS: {
|
|
|
24
59
|
lg: number;
|
|
25
60
|
xl: number;
|
|
26
61
|
};
|
|
62
|
+
export declare function loadGroupStates(): Record<string, boolean>;
|
|
63
|
+
export declare function saveGroupStates(states: Record<string, boolean>): void;
|
|
27
64
|
export declare function saveXlSideNavState(state: string | undefined): void;
|
|
28
65
|
export declare function loadXlSideNavState(): string | undefined;
|
|
29
66
|
export declare function restoreSideNavStateForXl(): void;
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import { tv } from "tailwind-variants";
|
|
2
|
+
/**
|
|
3
|
+
* App-shell sidebar navigation. Use `SideNavigationLayout` as the outermost wrapper of a page: it renders the fixed sidebar plus a content region for your `Header`, the page body, and optional bottom controls. `SideNavigation` is the bar itself and is normally driven through the layout. Wrap the app in `TeraUiContext` so `sideNavHref` (active route) and `language` are supplied globally, or pass them as props directly.
|
|
4
|
+
*
|
|
5
|
+
* The `items` prop is a single flat array that may MIX two shapes in any order: (1) a link item `{ href, title, icon?, exactHref? }` — a single navigable entry, where `exactHref` (falling back to `href`) is compared to the active route to apply the `selected` highlight; and (2) a group `{ id, title, items, defaultExpanded? }` — a collapsible section whose `items` are link items. A group renders a compact, fixed-height header (title + chevron, no icon) that expands/collapses its children; in the compact icon-only rail the header collapses to a thin divider so groups stay separated without large gaps, while keeping a stable height so items don't shift. Group children render with their own icons.
|
|
6
|
+
*
|
|
7
|
+
* Group expand/collapse state is PERSISTED to localStorage under the key `side-nav-group-state` (a JSON map of `{ [groupId]: boolean }`), keyed by the group's `id` — so `id` must be stable and unique. `defaultExpanded` only sets the initial state the first time a group `id` is seen (before any saved value exists). A group that contains the active route is always force-expanded on mount, regardless of saved state.
|
|
8
|
+
*
|
|
9
|
+
* If `items` is omitted, a built-in default navigation (calculator / converter / docs) is rendered. The sidebar's own expand vs. compact (icon-only) state is toggled by calling the exported `toggleSideNavigation()` (e.g. from a hamburger button) and is persisted at the xl breakpoint under localStorage key `side-nav-xl-state`; on smaller widths the bar is icon-only and temporarily expands on hover.
|
|
10
|
+
*
|
|
11
|
+
* Example: `<SideNavigationLayout items={[{ href: '/home', title: 'Home', icon: IconHome }, { id: 'tools', title: 'Tools', defaultExpanded: true, items: [{ href: '/calc', exactHref: '/calc', title: 'Calculator', icon: IconCalc }] }]}> <Header onHamburgerClick={toggleSideNavigation}>...</Header> <div>Page content</div> </SideNavigationLayout>`
|
|
12
|
+
*/
|
|
2
13
|
export const styles = tv({
|
|
3
14
|
base: '',
|
|
4
15
|
variants: {},
|
|
5
16
|
compoundVariants: [],
|
|
6
17
|
defaultVariants: {},
|
|
7
18
|
});
|
|
19
|
+
/** Type guard: true when an entry is a {@link SideNavigationGroup} (has an `items` array). */
|
|
20
|
+
export function isSideNavigationGroup(entry) {
|
|
21
|
+
return Array.isArray(entry.items);
|
|
22
|
+
}
|
|
8
23
|
export const SCREEN_BREAK_POINTS = {
|
|
9
24
|
sm: 640,
|
|
10
25
|
md: 768,
|
|
@@ -12,6 +27,19 @@ export const SCREEN_BREAK_POINTS = {
|
|
|
12
27
|
xl: 1280,
|
|
13
28
|
};
|
|
14
29
|
const SIDE_NAV_XL_STATE_KEY = 'side-nav-xl-state';
|
|
30
|
+
const SIDE_NAV_GROUP_STATE_KEY = 'side-nav-group-state';
|
|
31
|
+
export function loadGroupStates() {
|
|
32
|
+
try {
|
|
33
|
+
const raw = localStorage.getItem(SIDE_NAV_GROUP_STATE_KEY);
|
|
34
|
+
return raw ? JSON.parse(raw) : {};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function saveGroupStates(states) {
|
|
41
|
+
localStorage.setItem(SIDE_NAV_GROUP_STATE_KEY, JSON.stringify(states));
|
|
42
|
+
}
|
|
15
43
|
export function saveXlSideNavState(state) {
|
|
16
44
|
if (state === undefined) {
|
|
17
45
|
localStorage.removeItem(SIDE_NAV_XL_STATE_KEY);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script lang="ts">import { onMount } from "svelte";
|
|
2
|
-
import { mainLayout, SCREEN_BREAK_POINTS, setSideNavState, restoreSideNavStateForXl, toggleSideNavigation } from "./SideNavigation";
|
|
2
|
+
import { mainLayout, SCREEN_BREAK_POINTS, setSideNavState, restoreSideNavStateForXl, isSideNavigationGroup, loadGroupStates, saveGroupStates, toggleSideNavigation } from "./SideNavigation";
|
|
3
3
|
import { clickOutside } from "../../actions/clickOutside";
|
|
4
4
|
import { Button } from "../button";
|
|
5
5
|
import { IconCoinConvert, IconHamburger } from "../icons";
|
|
6
6
|
import { BrandLogo } from "../brand-logo";
|
|
7
7
|
import SideNavItem from "./SideNavigationItem.svelte";
|
|
8
|
+
import SideNavGroup from "./SideNavigationGroup.svelte";
|
|
8
9
|
import IconBook from "../icons/IconBook.svelte";
|
|
9
10
|
import IconTransform from "../icons/IconTransform.svelte";
|
|
10
11
|
import IconCalculator from "../icons/IconCalculator.svelte";
|
|
@@ -66,13 +67,32 @@ const NAV_ITEM = $derived(items ?? [
|
|
|
66
67
|
title: m.text_calces_documentation(),
|
|
67
68
|
},
|
|
68
69
|
]);
|
|
69
|
-
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
function isSelected(item) {
|
|
71
|
+
return (item.exactHref ?? item.href) === resolvedSideNavHref;
|
|
72
|
+
}
|
|
73
|
+
let groupStates = $state({});
|
|
74
|
+
function toggleGroup(id) {
|
|
75
|
+
groupStates[id] = !groupStates[id];
|
|
76
|
+
saveGroupStates(groupStates);
|
|
77
|
+
}
|
|
74
78
|
onMount(() => {
|
|
75
79
|
restoreSideNavStateForXl();
|
|
80
|
+
const saved = loadGroupStates();
|
|
81
|
+
for (const entry of NAV_ITEM) {
|
|
82
|
+
if (!isSideNavigationGroup(entry))
|
|
83
|
+
continue;
|
|
84
|
+
const hasActiveChild = entry.items.some(isSelected);
|
|
85
|
+
if (entry.id in saved) {
|
|
86
|
+
groupStates[entry.id] = saved[entry.id];
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
groupStates[entry.id] = entry.defaultExpanded ?? false;
|
|
90
|
+
}
|
|
91
|
+
// Force-expand the group containing the active route.
|
|
92
|
+
if (hasActiveChild) {
|
|
93
|
+
groupStates[entry.id] = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
76
96
|
});
|
|
77
97
|
</script>
|
|
78
98
|
|
|
@@ -99,13 +119,24 @@ onMount(() => {
|
|
|
99
119
|
handleHover(false)
|
|
100
120
|
}}
|
|
101
121
|
>
|
|
102
|
-
{#each NAV_ITEM as
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
{#each NAV_ITEM as entry (isSideNavigationGroup(entry) ? entry.id : entry.href)}
|
|
123
|
+
{#if isSideNavigationGroup(entry)}
|
|
124
|
+
<SideNavGroup
|
|
125
|
+
group={entry}
|
|
126
|
+
expanded={groupStates[entry.id]}
|
|
127
|
+
onToggle={() => toggleGroup(entry.id)}
|
|
128
|
+
{isSelected}
|
|
129
|
+
/>
|
|
130
|
+
{:else}
|
|
131
|
+
<SideNavItem href={entry.href} class={isSelected(entry) ? 'selected' : ''}>
|
|
132
|
+
{#snippet icon()}
|
|
133
|
+
{#if entry.icon}
|
|
134
|
+
<entry.icon/>
|
|
135
|
+
{/if}
|
|
136
|
+
{/snippet}
|
|
137
|
+
{entry.title}
|
|
138
|
+
</SideNavItem>
|
|
139
|
+
{/if}
|
|
109
140
|
{/each}
|
|
110
141
|
</ul>
|
|
111
142
|
{@render bottomChildren?.()}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">import { slide } from "svelte/transition";
|
|
2
|
+
import { IconChevronDown } from "../icons";
|
|
3
|
+
import SideNavItem from "./SideNavigationItem.svelte";
|
|
4
|
+
let { group, expanded = false, onToggle, isSelected, } = $props();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<li class="side-nav_group">
|
|
8
|
+
<button
|
|
9
|
+
type="button"
|
|
10
|
+
class="side-nav_item side-nav_group-header"
|
|
11
|
+
aria-expanded={expanded}
|
|
12
|
+
onclick={onToggle}
|
|
13
|
+
>
|
|
14
|
+
<span class="side-nav_group-divider" aria-hidden="true"></span>
|
|
15
|
+
<div class="sm-hidden z-10 truncate side-nav_group-title">
|
|
16
|
+
{group.title}
|
|
17
|
+
</div>
|
|
18
|
+
<div class="sm-hidden z-10 side-nav_group-chevron" data-expanded={expanded}>
|
|
19
|
+
<IconChevronDown/>
|
|
20
|
+
</div>
|
|
21
|
+
</button>
|
|
22
|
+
|
|
23
|
+
{#if expanded}
|
|
24
|
+
<ul class="side-nav_group-items" transition:slide={{duration: 200}}>
|
|
25
|
+
{#each group.items as item (item.href)}
|
|
26
|
+
<SideNavItem
|
|
27
|
+
href={item.href}
|
|
28
|
+
class={`side-nav_group-item ${isSelected(item) ? 'selected' : ''}`}
|
|
29
|
+
>
|
|
30
|
+
{#snippet icon()}
|
|
31
|
+
{#if item.icon}
|
|
32
|
+
<item.icon/>
|
|
33
|
+
{/if}
|
|
34
|
+
{/snippet}
|
|
35
|
+
{item.title}
|
|
36
|
+
</SideNavItem>
|
|
37
|
+
{/each}
|
|
38
|
+
</ul>
|
|
39
|
+
{/if}
|
|
40
|
+
</li>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SideNavigationGroup, SideNavigationItem } from "./SideNavigation";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
group: SideNavigationGroup;
|
|
4
|
+
expanded?: boolean;
|
|
5
|
+
onToggle: () => void;
|
|
6
|
+
isSelected: (item: SideNavigationItem) => boolean;
|
|
7
|
+
};
|
|
8
|
+
declare const SideNavigationGroup: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type SideNavigationGroup = ReturnType<typeof SideNavigationGroup>;
|
|
10
|
+
export default SideNavigationGroup;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as SideNavigation } from './SideNavigation.svelte';
|
|
2
2
|
export { default as SideNavigationLayout } from './SideNavigationLayout.svelte';
|
|
3
|
-
export {
|
|
4
|
-
export
|
|
3
|
+
export { default as SideNavigationGroup } from './SideNavigationGroup.svelte';
|
|
4
|
+
export { toggleSideNavigation, isSideNavigationGroup } from './SideNavigation';
|
|
5
|
+
export type { SideNavigationItem, SideNavigationGroup as SideNavigationGroupItem, SideNavigationEntry, SideNavigationProps } from "./SideNavigation";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as SideNavigation } from './SideNavigation.svelte';
|
|
2
2
|
export { default as SideNavigationLayout } from './SideNavigationLayout.svelte';
|
|
3
|
-
export {
|
|
3
|
+
export { default as SideNavigationGroup } from './SideNavigationGroup.svelte';
|
|
4
|
+
export { toggleSideNavigation, isSideNavigationGroup } from './SideNavigation';
|
|
@@ -163,3 +163,64 @@
|
|
|
163
163
|
background-color: var(--color-surface-hover);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
.side-nav_group {
|
|
168
|
+
.side-nav_group-header {
|
|
169
|
+
position: relative;
|
|
170
|
+
display: flex;
|
|
171
|
+
gap: 0.5rem;
|
|
172
|
+
align-items: center;
|
|
173
|
+
width: 100%;
|
|
174
|
+
/* Fixed compact height: keeps child items from shifting up/down when the
|
|
175
|
+
panel collapses/expands, and tightens the gap between groups in the rail. */
|
|
176
|
+
height: 2rem;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
padding-block: 0;
|
|
179
|
+
padding-inline: --spacing(3);
|
|
180
|
+
font-weight: var(--font-weight-normal);
|
|
181
|
+
color: var(--color-text-secondary);
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
background: transparent;
|
|
184
|
+
border: none;
|
|
185
|
+
text-align: start;
|
|
186
|
+
|
|
187
|
+
&:hover {
|
|
188
|
+
background-color: var(--color-surface-hover);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* In the compact (icon-only) panel the title + chevron fade out and this line
|
|
193
|
+
fades in — so a group header reads as a thin divider between item clusters.
|
|
194
|
+
It is the exact inverse of `.sm-hidden`, sharing the same container-query
|
|
195
|
+
threshold so the two always cross-fade. */
|
|
196
|
+
.side-nav_group-divider {
|
|
197
|
+
position: absolute;
|
|
198
|
+
left: --spacing(2);
|
|
199
|
+
right: --spacing(2);
|
|
200
|
+
top: 50%;
|
|
201
|
+
transform: translateY(-50%);
|
|
202
|
+
height: 1px;
|
|
203
|
+
background: var(--color-border-default);
|
|
204
|
+
opacity: 1;
|
|
205
|
+
transition: opacity var(--sidebar-transition-duration);
|
|
206
|
+
pointer-events: none;
|
|
207
|
+
|
|
208
|
+
@container (min-width: 3rem) {
|
|
209
|
+
opacity: 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.side-nav_group-title {
|
|
214
|
+
flex: 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.side-nav_group-chevron {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
transition: rotate var(--sidebar-transition-duration) var(--ease-ui);
|
|
221
|
+
|
|
222
|
+
&[data-expanded="true"] {
|
|
223
|
+
rotate: 180deg;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closes an overlay (Dialog, Drawer, …) when the browser Back button is pressed,
|
|
3
|
+
* without ever navigating the real page away.
|
|
4
|
+
*
|
|
5
|
+
* While the overlay is open a *dummy* history entry (same URL) is pushed. Pressing
|
|
6
|
+
* Back pops that entry and closes the overlay; any other close path (Esc, click-outside,
|
|
7
|
+
* close button, programmatic `open = false`) silently removes the dummy entry again.
|
|
8
|
+
*
|
|
9
|
+
* Must live in a `.svelte.ts` module so it can use `$effect`, and must be called from a
|
|
10
|
+
* component init scope.
|
|
11
|
+
*/
|
|
12
|
+
export declare function closeOnBackNavigation(opts: {
|
|
13
|
+
/** Whether the behavior is enabled. */
|
|
14
|
+
enabled: () => boolean;
|
|
15
|
+
/** Current open state of the overlay. */
|
|
16
|
+
isOpen: () => boolean;
|
|
17
|
+
/** Close the overlay (e.g. set `open = false`). */
|
|
18
|
+
close: () => void;
|
|
19
|
+
}): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closes an overlay (Dialog, Drawer, …) when the browser Back button is pressed,
|
|
3
|
+
* without ever navigating the real page away.
|
|
4
|
+
*
|
|
5
|
+
* While the overlay is open a *dummy* history entry (same URL) is pushed. Pressing
|
|
6
|
+
* Back pops that entry and closes the overlay; any other close path (Esc, click-outside,
|
|
7
|
+
* close button, programmatic `open = false`) silently removes the dummy entry again.
|
|
8
|
+
*
|
|
9
|
+
* Must live in a `.svelte.ts` module so it can use `$effect`, and must be called from a
|
|
10
|
+
* component init scope.
|
|
11
|
+
*/
|
|
12
|
+
let counter = 0; // module scope, monotonic per page load
|
|
13
|
+
export function closeOnBackNavigation(opts) {
|
|
14
|
+
const id = `tera-overlay-${++counter}`;
|
|
15
|
+
let poppedByBack = false; // non-reactive guard
|
|
16
|
+
function handlePopState() {
|
|
17
|
+
// Our entry was popped only if the current top state is no longer ours.
|
|
18
|
+
if (opts.isOpen() && history.state?.__teraOverlayId !== id) {
|
|
19
|
+
poppedByBack = true;
|
|
20
|
+
opts.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!opts.enabled() || !opts.isOpen())
|
|
25
|
+
return;
|
|
26
|
+
// Opened → push a dummy entry (same URL); preserve any existing state keys
|
|
27
|
+
// (e.g. SvelteKit's own routing state).
|
|
28
|
+
history.pushState({ ...history.state, __teraOverlayId: id }, '');
|
|
29
|
+
window.addEventListener('popstate', handlePopState);
|
|
30
|
+
return () => {
|
|
31
|
+
window.removeEventListener('popstate', handlePopState);
|
|
32
|
+
if (poppedByBack) {
|
|
33
|
+
poppedByBack = false; // Back already removed the entry
|
|
34
|
+
}
|
|
35
|
+
else if (history.state?.__teraOverlayId === id) {
|
|
36
|
+
history.back(); // Esc / click-outside / × / programmatic → drop our entry
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { closeOnBackNavigation } from './closeOnBackNavigation.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { closeOnBackNavigation } from './closeOnBackNavigation.svelte';
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { FeatureCard, StatBlock, PricingCard, TestimonialCard } from './componen
|
|
|
23
23
|
export { Popover } from './components/popover/index.js';
|
|
24
24
|
export { PopoverResponsive } from './components/popover-responsive/index.js';
|
|
25
25
|
export { Select } from './components/select/index.js';
|
|
26
|
-
export { SideNavigation, SideNavigationLayout,
|
|
26
|
+
export { SideNavigation, SideNavigationLayout, SideNavigationGroup } from './components/side-navigation/index.js';
|
|
27
27
|
export { Skeleton } from './components/skeleton/index.js';
|
|
28
28
|
export { Slider } from './components/slider/index.js';
|
|
29
29
|
export { Spinner } from './components/spinner/index.js';
|
|
@@ -55,7 +55,7 @@ export type { LightDarkToggleProps } from './components/light-dark-toggle/index.
|
|
|
55
55
|
export type { PopoverProps } from './components/popover/index.js';
|
|
56
56
|
export type { PopoverResponsiveProps } from './components/popover-responsive/index.js';
|
|
57
57
|
export type { SelectProps, SelectOption } from './components/select/index.js';
|
|
58
|
-
export type { SideNavigationItem, SideNavigationProps } from './components/side-navigation/index.js';
|
|
58
|
+
export type { SideNavigationItem, SideNavigationGroup, SideNavigationEntry, SideNavigationProps } from './components/side-navigation/index.js';
|
|
59
59
|
export type { SliderProps } from './components/slider/index.js';
|
|
60
60
|
export type { StarRatingProps } from './components/star-rating/index.js';
|
|
61
61
|
export type { SwitchProps } from './components/switch/index.js';
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { FeatureCard, StatBlock, PricingCard, TestimonialCard } from './componen
|
|
|
23
23
|
export { Popover } from './components/popover/index.js';
|
|
24
24
|
export { PopoverResponsive } from './components/popover-responsive/index.js';
|
|
25
25
|
export { Select } from './components/select/index.js';
|
|
26
|
-
export { SideNavigation, SideNavigationLayout,
|
|
26
|
+
export { SideNavigation, SideNavigationLayout, SideNavigationGroup } from './components/side-navigation/index.js';
|
|
27
27
|
export { Skeleton } from './components/skeleton/index.js';
|
|
28
28
|
export { Slider } from './components/slider/index.js';
|
|
29
29
|
export { Spinner } from './components/spinner/index.js';
|
package/dist/llms/colors.md
CHANGED
|
@@ -23,11 +23,11 @@ Always use semantic tokens in components — never primitive shades.
|
|
|
23
23
|
|
|
24
24
|
Tailwind utility CSS variable Primitive Role
|
|
25
25
|
─────────────────────────────────────────────────────────────────────────
|
|
26
|
-
bg-surface --color-surface #
|
|
27
|
-
bg-surface-raised --color-surface-raised #
|
|
28
|
-
bg-surface-sunken --color-surface-sunken #
|
|
29
|
-
bg-surface-hover --color-surface-hover #
|
|
30
|
-
bg-surface-control --color-surface-control #
|
|
26
|
+
bg-surface --color-surface #090909 Page / app background
|
|
27
|
+
bg-surface-raised --color-surface-raised #141414 Cards, panels
|
|
28
|
+
bg-surface-sunken --color-surface-sunken #202020 Inputs (filled), disabled areas
|
|
29
|
+
bg-surface-hover --color-surface-hover #303030 Hover / pressed neutral bg
|
|
30
|
+
bg-surface-control --color-surface-control #090909 Interactive controls (button, input outlined, checkbox, select, slider)
|
|
31
31
|
Light: same as page (white). Dark: elevated gray — sits above both page and dialogs.
|
|
32
32
|
Use this instead of bg-surface for any interactive control background.
|
|
33
33
|
|
|
@@ -35,17 +35,17 @@ Always use semantic tokens in components — never primitive shades.
|
|
|
35
35
|
|
|
36
36
|
Tailwind utility CSS variable Primitive Role
|
|
37
37
|
─────────────────────────────────────────────────────────────────────────
|
|
38
|
-
border-border-default --color-border-default #
|
|
39
|
-
border-border-strong --color-border-strong #
|
|
38
|
+
border-border-default --color-border-default #464646 Default border
|
|
39
|
+
border-border-strong --color-border-strong #5F5F5F Focus ring, separator
|
|
40
40
|
|
|
41
41
|
## Semantic Tokens — Text
|
|
42
42
|
|
|
43
43
|
Tailwind utility CSS variable Primitive Role
|
|
44
44
|
─────────────────────────────────────────────────────────────────────────
|
|
45
|
-
text-text-primary --color-text-primary #
|
|
46
|
-
text-text-secondary --color-text-secondary #
|
|
45
|
+
text-text-primary --color-text-primary #FFFFFF Body text, headings
|
|
46
|
+
text-text-secondary --color-text-secondary #BFBFBF Supporting / label text
|
|
47
47
|
text-text-tertiary --color-text-tertiary #8C8C8C Placeholders, hints
|
|
48
|
-
text-text-disabled --color-text-disabled #
|
|
48
|
+
text-text-disabled --color-text-disabled #5F5F5F Disabled labels
|
|
49
49
|
|
|
50
50
|
## Semantic Tokens — Interactive (primary action)
|
|
51
51
|
|
|
@@ -102,15 +102,15 @@ Always use semantic tokens in components — never primitive shades.
|
|
|
102
102
|
|
|
103
103
|
### Neutral scale (inverts in dark mode)
|
|
104
104
|
|
|
105
|
-
neutral-token-1 = #
|
|
106
|
-
neutral-token-2 = #
|
|
107
|
-
neutral-token-3 = #
|
|
108
|
-
neutral-token-4 = #
|
|
109
|
-
neutral-token-5 = #
|
|
110
|
-
neutral-token-6 = #
|
|
105
|
+
neutral-token-1 = #090909 (white — page background)
|
|
106
|
+
neutral-token-2 = #141414
|
|
107
|
+
neutral-token-3 = #202020 (input / disabled bg)
|
|
108
|
+
neutral-token-4 = #303030 (hover state bg)
|
|
109
|
+
neutral-token-5 = #464646 (default border)
|
|
110
|
+
neutral-token-6 = #5F5F5F (focus border / disabled text)
|
|
111
111
|
neutral-token-7 = #8C8C8C (placeholder / hint)
|
|
112
|
-
neutral-token-8 = #
|
|
113
|
-
neutral-token-13 = #
|
|
112
|
+
neutral-token-8 = #BFBFBF (secondary text)
|
|
113
|
+
neutral-token-13 = #FFFFFF (primary text)
|
|
114
114
|
|
|
115
115
|
### Primary (brand blue — changes per theme)
|
|
116
116
|
|
package/dist/llms/dialog.md
CHANGED
|
@@ -24,6 +24,7 @@ Built on BitsUI Dialog primitive.
|
|
|
24
24
|
position 'top' | 'center' optional Vertical alignment of the dialog on screen.
|
|
25
25
|
padding 'none' optional Set to 'none' to remove internal padding.
|
|
26
26
|
closeOnClickOutside boolean optional Whether clicking the backdrop closes the dialog.
|
|
27
|
+
closeOnNavigateBack boolean optional Whether pressing the browser Back button closes the dialog. Default true.
|
|
27
28
|
closeButton boolean optional Whether to render a close (×) button in the header.
|
|
28
29
|
staticRender boolean optional When true the dialog DOM is kept alive after close — children preserve internal state between open/close cycles.
|
|
29
30
|
preventScroll boolean optional Prevent the page from scrolling while the dialog is open.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# SideNavigation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
App-shell sidebar navigation with collapsible item groups (state persisted to localStorage). Parts: SideNavigationLayout (wrapper), SideNavigation (the bar), SideNavigationGroup, SideNavigationItem. Pass an `items` array mixing link items and groups.
|
|
4
|
+
|
|
5
|
+
App-shell sidebar navigation. Use `SideNavigationLayout` as the outermost wrapper of a page: it renders the fixed sidebar plus a content region for your `Header`, the page body, and optional bottom controls. `SideNavigation` is the bar itself and is normally driven through the layout. Wrap the app in `TeraUiContext` so `sideNavHref` (active route) and `language` are supplied globally, or pass them as props directly. The `items` prop is a single flat array that may MIX two shapes in any order: (1) a link item `{ href, title, icon?, exactHref? }` — a single navigable entry, where `exactHref` (falling back to `href`) is compared to the active route to apply the `selected` highlight; and (2) a group `{ id, title, items, defaultExpanded? }` — a collapsible section whose `items` are link items. A group renders a compact, fixed-height header (title + chevron, no icon) that expands/collapses its children; in the compact icon-only rail the header collapses to a thin divider so groups stay separated without large gaps, while keeping a stable height so items don't shift. Group children render with their own icons. Group expand/collapse state is PERSISTED to localStorage under the key `side-nav-group-state` (a JSON map of `{ [groupId]: boolean }`), keyed by the group's `id` — so `id` must be stable and unique. `defaultExpanded` only sets the initial state the first time a group `id` is seen (before any saved value exists). A group that contains the active route is always force-expanded on mount, regardless of saved state. If `items` is omitted, a built-in default navigation (calculator / converter / docs) is rendered. The sidebar's own expand vs. compact (icon-only) state is toggled by calling the exported `toggleSideNavigation()` (e.g. from a hamburger button) and is persisted at the xl breakpoint under localStorage key `side-nav-xl-state`; on smaller widths the bar is icon-only and temporarily expands on hover. Example: `<SideNavigationLayout items={[{ href: '/home', title: 'Home', icon: IconHome }, { id: 'tools', title: 'Tools', defaultExpanded: true, items: [{ href: '/calc', exactHref: '/calc', title: 'Calculator', icon: IconCalc }] }]}> <Header onHamburgerClick={toggleSideNavigation}>...</Header> <div>Page content</div> </SideNavigationLayout>`
|
|
4
6
|
|
|
5
7
|
## Import
|
|
6
8
|
|
|
7
|
-
import { SideNavigation } from 'tera-system-ui';
|
|
9
|
+
import { SideNavigation, SideNavigationGroup } from 'tera-system-ui';
|
|
8
10
|
import { SideNavigation } from 'tera-system-ui/side-navigation'; // subpath
|
|
9
11
|
|
|
10
12
|
## SideNavigation
|
|
@@ -15,10 +17,10 @@ Sidebar navigation. Parts: SideNavigation, SideNavigationItem, SideNavigationLay
|
|
|
15
17
|
──────────────────────────────────────────────────────────────────────────
|
|
16
18
|
children Snippet optional
|
|
17
19
|
class string optional
|
|
18
|
-
sideNavHref string optional
|
|
19
|
-
language string optional
|
|
20
|
-
items
|
|
21
|
-
bottomChildren Snippet optional
|
|
20
|
+
sideNavHref string optional Current active route, used to highlight the matching item. Usually supplied via TeraUiContext.
|
|
21
|
+
language string optional Active language code; localizes the built-in default nav links. Usually supplied via TeraUiContext.
|
|
22
|
+
items SideNavigationEntry[] optional Navigation entries: a flat array mixing link items ({ href, title, icon?, exactHref? }) and collapsible groups ({ id, title, items, defaultExpanded? }). Omit to render the built-in default nav.
|
|
23
|
+
bottomChildren Snippet optional Snippet rendered pinned at the bottom of the sidebar (e.g. a theme toggle).
|
|
22
24
|
ref HTMLElement optional (bindable)
|
|
23
25
|
|
|
24
26
|
### Usage
|
|
@@ -28,3 +30,22 @@ Sidebar navigation. Parts: SideNavigation, SideNavigationItem, SideNavigationLay
|
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
32
|
<SideNavigation>Content</SideNavigation>
|
|
33
|
+
|
|
34
|
+
## SideNavigationGroup
|
|
35
|
+
|
|
36
|
+
### Props
|
|
37
|
+
|
|
38
|
+
Name Type Required
|
|
39
|
+
──────────────────────────────────────────────────────────────────────────
|
|
40
|
+
group SideNavigationGroup required
|
|
41
|
+
expanded boolean optional
|
|
42
|
+
onToggle () => void required
|
|
43
|
+
isSelected (item: SideNavigationItem) => boolean required
|
|
44
|
+
|
|
45
|
+
### Usage
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
import { SideNavigationGroup } from 'tera-system-ui';
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<SideNavigationGroup />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tera-system-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "npm run customPrepublish && npm run generate-index && npm run generate-llms && vite build && npm run package && npm run copy-docs && npm run copy-llms && npm run postpublish",
|