terminoir 0.17.0 → 0.18.1

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/README.md CHANGED
@@ -157,6 +157,7 @@ Building blocks so apps compose screens without CSS of their own. See the demo's
157
157
  | `List`, `ListItem`, `DescriptionList`, `FormRow` | Rows with leading/trailing slots; key–value pairs; label-left form rows |
158
158
  | `SearchPicker` | Debounced async combobox (`search(q)`), keyboard navigation; grows to fill a row after chips |
159
159
  | `MarkdownEditor` | Notion-style editor that reads and writes markdown (Milkdown): `/` block menu, block handle (`+` and drag to move), checklists, markdown shortcuts, undo. `linkSearch` enables `[[` links written as `[[id\|label]]`; `onlinkopen` gets the clicked target. Outside `value` changes apply while it is not focused; `onchange` fires only after a user edit |
160
+ | `AsideLayout` | Main column with a collapsible right-hand panel (e.g. a record's properties); remembers open/closed, stacks inline under the header on phones |
160
161
  | `PropertyRow` | Compact icon + label row for inline-edited values (record properties); hover highlights the row |
161
162
  | `ChipSelect`, `DateField` | Value shown as a coloured `Chip` that opens a grouped option list (`ChipSelectOption`, `ChipSelectGroup`); date shown as "Today" / "Tomorrow" / weekday / "Nov 12" (`dayLabel`), `deadline` tints past and today, opens the native picker, Backspace clears |
162
163
  | `TitleInput`, `FileButton`, `RadioCards` | Borderless heading input; button that opens a file picker; card-style radio group |
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../utils/cn.js';
4
+ import { readStorage, writeStorage } from '../utils/storage.js';
5
+ import NavIcon from './NavIcon.svelte';
6
+
7
+ let {
8
+ label,
9
+ appId = 'app',
10
+ storageKey = 'aside-open',
11
+ class: className = '',
12
+ header,
13
+ aside,
14
+ children,
15
+ }: {
16
+ /** Names the side panel, e.g. "Properties"; shown as its heading and on the toggle. */
17
+ label: string;
18
+ appId?: string;
19
+ /** Where the open/closed choice is remembered; give each kind of screen its own key. */
20
+ storageKey?: string;
21
+ class?: string;
22
+ /** Top of the main column (toolbar, title). On phones it stays above the panel. */
23
+ header?: Snippet;
24
+ /** Panel content: a right-hand column on wide screens, inline under `header` on phones. */
25
+ aside?: Snippet;
26
+ children?: Snippet;
27
+ } = $props();
28
+
29
+ let open = $state(true);
30
+
31
+ $effect.pre(() => {
32
+ open = readStorage(appId, storageKey, 'true') !== 'false';
33
+ });
34
+
35
+ function toggle() {
36
+ open = !open;
37
+ writeStorage(appId, storageKey, String(open));
38
+ }
39
+ </script>
40
+
41
+ <div class={cn('terminoir-aside-layout', !open && 'terminoir-aside-layout--collapsed', className)}>
42
+ <div class="terminoir-aside-layout__header">
43
+ {@render header?.()}
44
+ </div>
45
+ <aside class="terminoir-aside-layout__aside" aria-label={label}>
46
+ <div class="terminoir-aside-layout__aside-top">
47
+ <span class="terminoir-aside-layout__label">{label}</span>
48
+ <button
49
+ type="button"
50
+ class="terminoir-btn terminoir-btn--sm terminoir-btn--ghost terminoir-focusable glass-interactive terminoir-aside-layout__toggle"
51
+ aria-label={open ? `Hide ${label.toLowerCase()}` : `Show ${label.toLowerCase()}`}
52
+ aria-expanded={open}
53
+ title={open ? `Hide ${label.toLowerCase()}` : `Show ${label.toLowerCase()}`}
54
+ onclick={toggle}
55
+ >
56
+ <NavIcon name={open ? 'angles-right' : 'angles-left'} />
57
+ </button>
58
+ </div>
59
+ <div class="terminoir-aside-layout__aside-body">
60
+ {@render aside?.()}
61
+ </div>
62
+ </aside>
63
+ <div class="terminoir-aside-layout__main">
64
+ {@render children?.()}
65
+ </div>
66
+ </div>
67
+
68
+ <style>
69
+ .terminoir-aside-layout {
70
+ --terminoir-aside-width: 20rem;
71
+ display: grid;
72
+ grid-template-columns: minmax(0, 1fr) var(--terminoir-aside-width);
73
+ grid-template-rows: auto 1fr;
74
+ grid-template-areas:
75
+ 'header aside'
76
+ 'main aside';
77
+ /* Wide enough for a MarkdownEditor block handle, which flips to the right of the text when the left is tight. */
78
+ column-gap: calc(var(--space-12) + var(--space-2));
79
+ row-gap: var(--space-4);
80
+ min-width: 0;
81
+ }
82
+ .terminoir-aside-layout--collapsed {
83
+ grid-template-columns: minmax(0, 1fr) auto;
84
+ }
85
+ .terminoir-aside-layout__header,
86
+ .terminoir-aside-layout__main {
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: var(--space-4);
90
+ min-width: 0;
91
+ }
92
+ .terminoir-aside-layout__header { grid-area: header; }
93
+ .terminoir-aside-layout__main { grid-area: main; }
94
+ .terminoir-aside-layout__aside {
95
+ grid-area: aside;
96
+ align-self: start;
97
+ position: sticky;
98
+ top: var(--space-4);
99
+ max-height: calc(100dvh - var(--space-8));
100
+ overflow-y: auto;
101
+ min-width: 0;
102
+ padding-left: var(--space-4);
103
+ border-left: 1px solid var(--border);
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: var(--space-2);
107
+ }
108
+ .terminoir-aside-layout__aside-top {
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: space-between;
112
+ gap: var(--space-2);
113
+ }
114
+ .terminoir-aside-layout__label {
115
+ font-size: 0.75rem;
116
+ font-weight: 600;
117
+ color: var(--muted-foreground);
118
+ }
119
+ .terminoir-aside-layout--collapsed .terminoir-aside-layout__aside {
120
+ padding-left: var(--space-2);
121
+ }
122
+ .terminoir-aside-layout--collapsed .terminoir-aside-layout__label,
123
+ .terminoir-aside-layout--collapsed .terminoir-aside-layout__aside-body {
124
+ display: none;
125
+ }
126
+
127
+ /* Phones: one column, the panel inline under the header and always open. */
128
+ @media (max-width: 768px) {
129
+ .terminoir-aside-layout,
130
+ .terminoir-aside-layout--collapsed {
131
+ grid-template-columns: minmax(0, 1fr);
132
+ grid-template-rows: auto;
133
+ grid-template-areas: 'header' 'aside' 'main';
134
+ }
135
+ .terminoir-aside-layout__aside,
136
+ .terminoir-aside-layout--collapsed .terminoir-aside-layout__aside {
137
+ position: static;
138
+ max-height: none;
139
+ overflow: visible;
140
+ padding-left: 0;
141
+ border-left: 0;
142
+ }
143
+ .terminoir-aside-layout__aside-top { display: none; }
144
+ .terminoir-aside-layout--collapsed .terminoir-aside-layout__aside-body { display: block; }
145
+ }
146
+ </style>
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ /** Names the side panel, e.g. "Properties"; shown as its heading and on the toggle. */
4
+ label: string;
5
+ appId?: string;
6
+ /** Where the open/closed choice is remembered; give each kind of screen its own key. */
7
+ storageKey?: string;
8
+ class?: string;
9
+ /** Top of the main column (toolbar, title). On phones it stays above the panel. */
10
+ header?: Snippet;
11
+ /** Panel content: a right-hand column on wide screens, inline under `header` on phones. */
12
+ aside?: Snippet;
13
+ children?: Snippet;
14
+ };
15
+ declare const AsideLayout: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type AsideLayout = ReturnType<typeof AsideLayout>;
17
+ export default AsideLayout;
@@ -22,7 +22,7 @@
22
22
  type MenuEntry = { id: string; label: string; icon?: string; iconColor?: string; description?: string; choose: () => void };
23
23
 
24
24
  const LINK_SEARCH_DEBOUNCE_MS = 120;
25
- /** How far left of the text a dragged block can still be dropped (the handle's gutter), and above or below it. */
25
+ /** How far beside the text a dragged block can still be dropped (the handle's gutter; it flips right when the left is tight), and above or below it. */
26
26
  const DROP_MARGIN_PX = 72;
27
27
 
28
28
  let {
@@ -178,7 +178,7 @@
178
178
  const rect = root!.getBoundingClientRect();
179
179
  return (
180
180
  event.clientX >= rect.left - DROP_MARGIN_PX &&
181
- event.clientX <= rect.right &&
181
+ event.clientX <= rect.right + DROP_MARGIN_PX &&
182
182
  event.clientY >= rect.top - DROP_MARGIN_PX &&
183
183
  event.clientY <= rect.bottom + DROP_MARGIN_PX
184
184
  );
package/dist/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export { default as Toaster } from './components/Toaster.svelte';
43
43
  export { default as Tooltip } from './components/Tooltip.svelte';
44
44
  export { default as Page } from './components/Page.svelte';
45
45
  export { default as PageHeader } from './components/PageHeader.svelte';
46
+ export { default as AsideLayout } from './components/AsideLayout.svelte';
46
47
  export { default as PanelHeader } from './components/PanelHeader.svelte';
47
48
  export { default as Section } from './components/Section.svelte';
48
49
  export { default as Stack } from './components/Stack.svelte';
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ export { default as Tooltip } from './components/Tooltip.svelte';
42
42
  // Layout
43
43
  export { default as Page } from './components/Page.svelte';
44
44
  export { default as PageHeader } from './components/PageHeader.svelte';
45
+ export { default as AsideLayout } from './components/AsideLayout.svelte';
45
46
  export { default as PanelHeader } from './components/PanelHeader.svelte';
46
47
  export { default as Section } from './components/Section.svelte';
47
48
  export { default as Stack } from './components/Stack.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminoir",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Terminoir — Liquid Glass design tokens and Svelte 5 UI components.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",