tera-system-ui 0.1.69 → 0.1.71

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.
@@ -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
- let { children, open = $bindable(false), ref = $bindable(null), closeOnClickOutside = 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();
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
- }
@@ -24,6 +24,9 @@ export declare const SCREEN_BREAK_POINTS: {
24
24
  lg: number;
25
25
  xl: number;
26
26
  };
27
+ export declare function saveXlSideNavState(state: string | undefined): void;
28
+ export declare function loadXlSideNavState(): string | undefined;
29
+ export declare function restoreSideNavStateForXl(): void;
27
30
  export declare const mainLayout: () => HTMLElement | null;
28
31
  export declare const getSideNavState: () => string | undefined;
29
32
  export declare const setSideNavState: (state: any) => void;
@@ -11,6 +11,25 @@ export const SCREEN_BREAK_POINTS = {
11
11
  lg: 1024,
12
12
  xl: 1280,
13
13
  };
14
+ const SIDE_NAV_XL_STATE_KEY = 'side-nav-xl-state';
15
+ export function saveXlSideNavState(state) {
16
+ if (state === undefined) {
17
+ localStorage.removeItem(SIDE_NAV_XL_STATE_KEY);
18
+ }
19
+ else {
20
+ localStorage.setItem(SIDE_NAV_XL_STATE_KEY, state);
21
+ }
22
+ }
23
+ export function loadXlSideNavState() {
24
+ return localStorage.getItem(SIDE_NAV_XL_STATE_KEY) ?? undefined;
25
+ }
26
+ export function restoreSideNavStateForXl() {
27
+ const screenWidth = getScreenWidth();
28
+ if (screenWidth >= SCREEN_BREAK_POINTS.xl) {
29
+ const saved = loadXlSideNavState();
30
+ setSideNavState(saved);
31
+ }
32
+ }
14
33
  export const mainLayout = () => document?.getElementById('side-nav_main-layout');
15
34
  export const getSideNavState = () => {
16
35
  return mainLayout()?.getAttribute('data-side-nav-state') || undefined;
@@ -38,6 +57,7 @@ export function toggleSideNavigation() {
38
57
  }
39
58
  else if (screenWidth >= SCREEN_BREAK_POINTS.xl) {
40
59
  state = currentSideNavState === undefined ? "compact" : undefined;
60
+ saveXlSideNavState(state);
41
61
  }
42
62
  setSideNavState(state);
43
63
  }
@@ -1,4 +1,5 @@
1
- <script lang="ts">import { SCREEN_BREAK_POINTS, mainLayout, setSideNavState, toggleSideNavigation, getSideNavState } from "./SideNavigation";
1
+ <script lang="ts">import { onMount } from "svelte";
2
+ import { mainLayout, SCREEN_BREAK_POINTS, setSideNavState, restoreSideNavStateForXl, toggleSideNavigation } from "./SideNavigation";
2
3
  import { clickOutside } from "../../actions/clickOutside";
3
4
  import { Button } from "../button";
4
5
  import { IconCoinConvert, IconHamburger } from "../icons";
@@ -70,6 +71,9 @@ let selectedIndex = (() => {
70
71
  return (item.exactHref ?? item.href) === resolvedSideNavHref;
71
72
  });
72
73
  })();
74
+ onMount(() => {
75
+ restoreSideNavStateForXl();
76
+ });
73
77
  </script>
74
78
 
75
79
  <nav class="side-nav_main-side-bar grid grid-rows-[auto_1fr_auto]"
@@ -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';
@@ -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 #FFFFFF Page / app background
27
- bg-surface-raised --color-surface-raised #FCFCFC Cards, panels
28
- bg-surface-sunken --color-surface-sunken #F5F5F5 Inputs (filled), disabled areas
29
- bg-surface-hover --color-surface-hover #F0F0F0 Hover / pressed neutral bg
30
- bg-surface-control --color-surface-control #FFFFFF Interactive controls (button, input outlined, checkbox, select, slider)
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 #D9D9D9 Default border
39
- border-border-strong --color-border-strong #BFBFBF Focus ring, separator
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 #000000 Body text, headings
46
- text-text-secondary --color-text-secondary #595959 Supporting / label text
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 #BFBFBF Disabled labels
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 = #FFFFFF (white — page background)
106
- neutral-token-2 = #FCFCFC
107
- neutral-token-3 = #F5F5F5 (input / disabled bg)
108
- neutral-token-4 = #F0F0F0 (hover state bg)
109
- neutral-token-5 = #D9D9D9 (default border)
110
- neutral-token-6 = #BFBFBF (focus border / disabled text)
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 = #595959 (secondary text)
113
- neutral-token-13 = #000000 (primary text)
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
 
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tera-system-ui",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
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",