terminoir 0.18.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/css/cards.css +11 -6
- package/dist/components/CalendarGrid.svelte +9 -2
- package/dist/components/CalendarGrid.svelte.d.ts +2 -0
- package/dist/components/CardGrid.svelte +2 -1
- package/dist/components/CardGrid.svelte.d.ts +2 -1
- package/dist/components/Page.svelte +5 -1
- package/dist/components/Page.svelte.d.ts +2 -0
- package/dist/components/Tabs.svelte +60 -3
- package/dist/components/Tabs.svelte.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ Import from **`terminoir`**:
|
|
|
124
124
|
| `Input`, `Textarea`, `Select`, `Checkbox`, `Switch` | `hideLabel` (screen-reader only), `hint`, `error`, `quiet` (borderless until hover/focus, for inline property editing); `Input` `width` for toolbar fields |
|
|
125
125
|
| `Chip`, `Badge` | Status tones; `Chip` `color` dot, `onclick`, removable `onremove`, on/off toggle `pressed` (filters) |
|
|
126
126
|
| `StatusChip` | On/off status: green dot + `activeLabel`, or muted `inactiveLabel` (defaults Connected / Not connected) |
|
|
127
|
-
| `Card`, `CardGrid` | Accent tint, badge, chips, progress, meta, hover lift; `CardGrid columns={2\|3\|4}`
|
|
127
|
+
| `Card`, `CardGrid` | Accent tint, badge, chips, progress, meta, hover lift; `CardGrid columns={2\|3\|4}` caps the column count; columns stay at least `--terminoir-card-grid-min` (20rem) wide, so a narrow grid drops to fewer. Also for `Panel`s |
|
|
128
128
|
| `Panel` | Glass panel surface |
|
|
129
129
|
| `Dialog` | Modal with focus trap-ish behavior + Escape |
|
|
130
130
|
| `Popover`, `Tooltip` | Lightweight overlays |
|
|
@@ -132,7 +132,7 @@ Import from **`terminoir`**:
|
|
|
132
132
|
| `Drawer` | Side sheet with title, close button and optional footer |
|
|
133
133
|
| `CommandPalette` | ⌘/Ctrl+K (`hotkey`), grouped rows with icons and hints; `bind:query` + `filter={false}` + `loading` for async search; `footer` snippet |
|
|
134
134
|
| `Toaster` + `toast()` | Action / undo via `action: { label, onClick }` |
|
|
135
|
-
| `Tabs`, `SegmentedControl` | Tab panels (unique ids per instance); compact radio-group toggle |
|
|
135
|
+
| `Tabs`, `SegmentedControl` | Tab panels (unique ids per instance, arrow keys, optional `icon` per tab, `orientation="vertical"` for a side column that becomes a row on phones); compact radio-group toggle |
|
|
136
136
|
| `AppShell` | `header` + `sidebar` snippets; mobile drawer; `fitViewport` (main scrolls, for canvases), `flush`, `headerMode="mobile"`; `toggleSidebar()` / `closeMobileDrawer()` via `bind:this` |
|
|
137
137
|
| `AppShellNavLink` | `href?`, `active`, nav row with active highlight |
|
|
138
138
|
| `SettingsPage`, `SettingsSection` | Settings layout — intro + stacked glass section cards |
|
|
@@ -148,7 +148,7 @@ Building blocks so apps compose screens without CSS of their own. See the demo's
|
|
|
148
148
|
|
|
149
149
|
| Component | Notes |
|
|
150
150
|
|-----------|--------|
|
|
151
|
-
| `Page`, `PageHeader`, `Section` | Readable page column (`narrow`/`default`/`wide`/`full`, `fill` for canvases); title + meta + actions; titled section with actions |
|
|
151
|
+
| `Page`, `PageHeader`, `Section` | Readable page column (`narrow`/`default`/`wide`/`full`, `fill` for canvases, `fullHeight` to fill the stage height with padding); title + meta + actions; titled section with actions |
|
|
152
152
|
| `PanelHeader` | Heading inside a `Panel`: `logo` (the image unframed; initials in a round avatar until `src`), `icon` or `leading` mark, title, description, `actions` (stacks under on phones) |
|
|
153
153
|
| `Stack`, `Inline`, `Spacer`, `Centered` | Vertical / horizontal layout with `--space-*` gaps (`Inline fill` splits evenly, `collapse` stacks on phones); fill space; full-viewport centred column |
|
|
154
154
|
| `Text`, `Prose`, `Disclosure` | Tone/size/weight text; long-form typography; collapsible details |
|
|
@@ -163,7 +163,7 @@ Building blocks so apps compose screens without CSS of their own. See the demo's
|
|
|
163
163
|
| `TitleInput`, `FileButton`, `RadioCards` | Borderless heading input; button that opens a file picker; card-style radio group |
|
|
164
164
|
| `OptionsEditor` | Notion-style option list for select/status values: drag to reorder, click a name to rename, colour and delete from each row's menu, type to add; `ChipSelect` takes `oncreate` to add one from its search |
|
|
165
165
|
| `EditableText` | A name you click to rename in place (Enter or click away saves, Escape cancels); `PageHeader`, `ListItem` and `PropertyRow` take `onrename` for the same |
|
|
166
|
-
| `CalendarGrid` | Week (scrolling hours) and month grids; `CalendarEvent` with `muted`/`color`; helpers `calendarRange`, `shiftCalendar`, `eventsOn` |
|
|
166
|
+
| `CalendarGrid` | Week (scrolling hours) and month grids; `fill` grows to the remaining height (e.g. in a `fullHeight` Page); `CalendarEvent` with `muted`/`color`; helpers `calendarRange`, `shiftCalendar`, `eventsOn` |
|
|
167
167
|
| `CanvasStage`, `DocumentViewer` | Full-bleed canvas host with floating overlay and side panel; paged viewer with toolbar and highlight marks |
|
|
168
168
|
| `AccentSwatches` | `bind:value` accent hex from `ACCENT_PALETTE` |
|
|
169
169
|
| `GroupedList`, `SegmentBar` | Items in labelled groups with a colour dot and count, "Show n more" past `limit`, `collapsed` groups behind a toggle (e.g. finished items); a thin bar of proportional coloured segments |
|
package/css/cards.css
CHANGED
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
width: 100%;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.terminoir-card-grid--cols-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
.terminoir-card-grid[class*='terminoir-card-grid--cols-'] {
|
|
13
|
+
/* N columns at most, each at least --terminoir-card-grid-min wide: drops to fewer columns when the grid
|
|
14
|
+
itself is narrow (a side column, a laptop), not only on phones. */
|
|
15
|
+
--terminoir-card-grid-min: 20rem;
|
|
16
|
+
grid-template-columns: repeat(
|
|
17
|
+
auto-fill,
|
|
18
|
+
minmax(min(100%, max(var(--terminoir-card-grid-min), calc((100% - (var(--terminoir-card-grid-cols) - 1) * var(--space-4)) / var(--terminoir-card-grid-cols)))), 1fr)
|
|
19
|
+
);
|
|
18
20
|
}
|
|
21
|
+
.terminoir-card-grid--cols-2 { --terminoir-card-grid-cols: 2; }
|
|
22
|
+
.terminoir-card-grid--cols-3 { --terminoir-card-grid-cols: 3; }
|
|
23
|
+
.terminoir-card-grid--cols-4 { --terminoir-card-grid-cols: 4; }
|
|
19
24
|
|
|
20
25
|
.terminoir-card {
|
|
21
26
|
position: relative;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
anchor,
|
|
22
22
|
events,
|
|
23
23
|
onEventClick,
|
|
24
|
+
fill = false,
|
|
24
25
|
class: className = '',
|
|
25
26
|
}: {
|
|
26
27
|
mode?: CalendarMode;
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
anchor: Date;
|
|
29
30
|
events: CalendarEvent[];
|
|
30
31
|
onEventClick?: (event: CalendarEvent) => void;
|
|
32
|
+
/** Grow to the remaining height of a flex column (e.g. a `fullHeight` Page) instead of a fixed height. */
|
|
33
|
+
fill?: boolean;
|
|
31
34
|
class?: string;
|
|
32
35
|
} = $props();
|
|
33
36
|
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
{/snippet}
|
|
70
73
|
|
|
71
74
|
{#if mode === 'week'}
|
|
72
|
-
<div class={cn('terminoir-cal terminoir-cal--week', className)}>
|
|
75
|
+
<div class={cn('terminoir-cal terminoir-cal--week', fill && 'terminoir-cal--fill', className)}>
|
|
73
76
|
<div class="terminoir-cal__week-head">
|
|
74
77
|
<div></div>
|
|
75
78
|
{#each weekDays as day (day.getTime())}
|
|
@@ -109,7 +112,7 @@
|
|
|
109
112
|
</div>
|
|
110
113
|
</div>
|
|
111
114
|
{:else}
|
|
112
|
-
<div class={cn('terminoir-cal terminoir-cal--month', className)}>
|
|
115
|
+
<div class={cn('terminoir-cal terminoir-cal--month', fill && 'terminoir-cal--fill', className)}>
|
|
113
116
|
{#each weekDays as day (day.getTime())}
|
|
114
117
|
<div class="terminoir-cal__dow">{day.toLocaleDateString(undefined, { weekday: 'short' })}</div>
|
|
115
118
|
{/each}
|
|
@@ -217,6 +220,10 @@
|
|
|
217
220
|
}
|
|
218
221
|
.terminoir-cal__event:disabled { cursor: default; }
|
|
219
222
|
.terminoir-cal__event--muted { opacity: 0.6; background: transparent; }
|
|
223
|
+
/* Fill: take the parent's free height; week hours scroll inside, month rows stretch. */
|
|
224
|
+
.terminoir-cal--fill { flex: 1 1 0; }
|
|
225
|
+
.terminoir-cal--week.terminoir-cal--fill { height: auto; }
|
|
226
|
+
.terminoir-cal--month.terminoir-cal--fill { grid-template-rows: auto; grid-auto-rows: 1fr; }
|
|
220
227
|
.terminoir-cal__time { color: var(--muted-foreground); flex-shrink: 0; }
|
|
221
228
|
.terminoir-cal__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
222
229
|
.terminoir-cal--month { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
@@ -5,6 +5,8 @@ type $$ComponentProps = {
|
|
|
5
5
|
anchor: Date;
|
|
6
6
|
events: CalendarEvent[];
|
|
7
7
|
onEventClick?: (event: CalendarEvent) => void;
|
|
8
|
+
/** Grow to the remaining height of a flex column (e.g. a `fullHeight` Page) instead of a fixed height. */
|
|
9
|
+
fill?: boolean;
|
|
8
10
|
class?: string;
|
|
9
11
|
};
|
|
10
12
|
declare const CalendarGrid: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
...rest
|
|
10
10
|
}: {
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* At most this many columns, e.g. 2 for a pair of settings panels. Columns stay at least
|
|
13
|
+
* `--terminoir-card-grid-min` (20rem) wide, so a narrow grid (a phone, beside a side column) shows fewer.
|
|
13
14
|
* Omit to fit as many 280px columns as the width allows.
|
|
14
15
|
*/
|
|
15
16
|
columns?: 2 | 3 | 4;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* At most this many columns, e.g. 2 for a pair of settings panels. Columns stay at least
|
|
5
|
+
* `--terminoir-card-grid-min` (20rem) wide, so a narrow grid (a phone, beside a side column) shows fewer.
|
|
5
6
|
* Omit to fit as many 280px columns as the width allows.
|
|
6
7
|
*/
|
|
7
8
|
columns?: 2 | 3 | 4;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
let {
|
|
6
6
|
width = 'default',
|
|
7
7
|
fill = false,
|
|
8
|
+
fullHeight = false,
|
|
8
9
|
class: className = '',
|
|
9
10
|
children,
|
|
10
11
|
...rest
|
|
@@ -13,12 +14,14 @@
|
|
|
13
14
|
width?: 'narrow' | 'default' | 'wide' | 'full';
|
|
14
15
|
/** Edge-to-edge and full height with no padding, for canvases (graphs, viewers, calendars). */
|
|
15
16
|
fill?: boolean;
|
|
17
|
+
/** At least the stage's height (keeps padding), so a growing child such as `CalendarGrid fill` takes the rest. */
|
|
18
|
+
fullHeight?: boolean;
|
|
16
19
|
class?: string;
|
|
17
20
|
children?: Snippet;
|
|
18
21
|
} & Record<string, unknown> = $props();
|
|
19
22
|
</script>
|
|
20
23
|
|
|
21
|
-
<div class={cn('terminoir-page', `terminoir-page--${width}`, fill && 'terminoir-page--fill', className)} {...rest}>
|
|
24
|
+
<div class={cn('terminoir-page', `terminoir-page--${width}`, fill && 'terminoir-page--fill', fullHeight && 'terminoir-page--full-height', className)} {...rest}>
|
|
22
25
|
{@render children?.()}
|
|
23
26
|
</div>
|
|
24
27
|
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
.terminoir-page--default { max-width: 56rem; }
|
|
38
41
|
.terminoir-page--wide { max-width: 80rem; }
|
|
39
42
|
.terminoir-page--full { max-width: none; }
|
|
43
|
+
.terminoir-page--full-height { min-height: 100%; }
|
|
40
44
|
.terminoir-page--fill {
|
|
41
45
|
max-width: none;
|
|
42
46
|
height: 100%;
|
|
@@ -4,6 +4,8 @@ type $$ComponentProps = {
|
|
|
4
4
|
width?: 'narrow' | 'default' | 'wide' | 'full';
|
|
5
5
|
/** Edge-to-edge and full height with no padding, for canvases (graphs, viewers, calendars). */
|
|
6
6
|
fill?: boolean;
|
|
7
|
+
/** At least the stage's height (keeps padding), so a growing child such as `CalendarGrid fill` takes the rest. */
|
|
8
|
+
fullHeight?: boolean;
|
|
7
9
|
class?: string;
|
|
8
10
|
children?: Snippet;
|
|
9
11
|
} & Record<string, unknown>;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { cn } from '../utils/cn.js';
|
|
4
|
+
import NavIcon from './NavIcon.svelte';
|
|
4
5
|
|
|
5
6
|
let {
|
|
6
7
|
tabs = [],
|
|
7
8
|
value = $bindable(''),
|
|
9
|
+
orientation = 'horizontal',
|
|
8
10
|
class: className = '',
|
|
9
11
|
children,
|
|
10
12
|
}: {
|
|
11
|
-
|
|
13
|
+
/** `icon` is a Font Awesome name, as for `NavIcon`. */
|
|
14
|
+
tabs: Array<{ id: string; label: string; icon?: string }>;
|
|
12
15
|
value?: string;
|
|
16
|
+
/** `vertical`: the tabs stack in a column beside the panel (a settings sidebar); a scrolling row on phones. */
|
|
17
|
+
orientation?: 'horizontal' | 'vertical';
|
|
13
18
|
class?: string;
|
|
14
19
|
children?: Snippet;
|
|
15
20
|
} = $props();
|
|
@@ -19,10 +24,27 @@
|
|
|
19
24
|
$effect.pre(() => {
|
|
20
25
|
if (!value && tabs[0]) value = tabs[0].id;
|
|
21
26
|
});
|
|
27
|
+
|
|
28
|
+
const PREVIOUS = new Set(['ArrowLeft', 'ArrowUp']);
|
|
29
|
+
const NEXT = new Set(['ArrowRight', 'ArrowDown']);
|
|
30
|
+
|
|
31
|
+
/** Arrow keys move between tabs (either axis, so it works for both orientations), Home/End jump to the ends. */
|
|
32
|
+
function onkeydown(event: KeyboardEvent) {
|
|
33
|
+
const index = tabs.findIndex((tab) => tab.id === value);
|
|
34
|
+
let next = -1;
|
|
35
|
+
if (PREVIOUS.has(event.key)) next = (index - 1 + tabs.length) % tabs.length;
|
|
36
|
+
else if (NEXT.has(event.key)) next = (index + 1) % tabs.length;
|
|
37
|
+
else if (event.key === 'Home') next = 0;
|
|
38
|
+
else if (event.key === 'End') next = tabs.length - 1;
|
|
39
|
+
if (next < 0) return;
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
value = tabs[next].id;
|
|
42
|
+
(event.currentTarget as HTMLElement).querySelector<HTMLElement>(`#${CSS.escape(`${uid}-tab-${value}`)}`)?.focus();
|
|
43
|
+
}
|
|
22
44
|
</script>
|
|
23
45
|
|
|
24
|
-
<div class={cn('terminoir-tabs', className)}>
|
|
25
|
-
<div role="tablist" aria-orientation=
|
|
46
|
+
<div class={cn('terminoir-tabs', orientation === 'vertical' && 'terminoir-tabs--vertical', className)}>
|
|
47
|
+
<div role="tablist" aria-orientation={orientation} class="terminoir-tabs__list glass-chrome" tabindex="-1" {onkeydown}>
|
|
26
48
|
{#each tabs as tab (tab.id)}
|
|
27
49
|
<button
|
|
28
50
|
type="button"
|
|
@@ -30,9 +52,11 @@
|
|
|
30
52
|
id={`${uid}-tab-${tab.id}`}
|
|
31
53
|
aria-selected={value === tab.id}
|
|
32
54
|
aria-controls={`${uid}-panel-${tab.id}`}
|
|
55
|
+
tabindex={value === tab.id ? 0 : -1}
|
|
33
56
|
class="terminoir-tabs__trigger terminoir-focusable"
|
|
34
57
|
onclick={() => (value = tab.id)}
|
|
35
58
|
>
|
|
59
|
+
{#if tab.icon}<NavIcon name={tab.icon} />{/if}
|
|
36
60
|
{tab.label}
|
|
37
61
|
</button>
|
|
38
62
|
{/each}
|
|
@@ -65,6 +89,9 @@
|
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
.terminoir-tabs__trigger {
|
|
92
|
+
display: inline-flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: var(--space-2);
|
|
68
95
|
flex-shrink: 0;
|
|
69
96
|
white-space: nowrap;
|
|
70
97
|
border: 0;
|
|
@@ -85,4 +112,34 @@
|
|
|
85
112
|
.terminoir-tabs__panel {
|
|
86
113
|
padding-top: var(--space-3);
|
|
87
114
|
}
|
|
115
|
+
|
|
116
|
+
/* Vertical: a column of tabs beside the panel, kept in view while the panel scrolls. */
|
|
117
|
+
@media (min-width: 701px) {
|
|
118
|
+
.terminoir-tabs--vertical {
|
|
119
|
+
display: grid;
|
|
120
|
+
grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
|
|
121
|
+
gap: var(--space-5);
|
|
122
|
+
align-items: start;
|
|
123
|
+
}
|
|
124
|
+
.terminoir-tabs--vertical > .terminoir-tabs__list {
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
position: sticky;
|
|
128
|
+
top: var(--space-3);
|
|
129
|
+
overflow: visible;
|
|
130
|
+
}
|
|
131
|
+
.terminoir-tabs--vertical .terminoir-tabs__trigger {
|
|
132
|
+
width: 100%;
|
|
133
|
+
text-align: left;
|
|
134
|
+
}
|
|
135
|
+
.terminoir-tabs--vertical .terminoir-tabs__trigger[aria-selected='true'] {
|
|
136
|
+
background: color-mix(in srgb, var(--primary) 14%, transparent);
|
|
137
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 28%, var(--border));
|
|
138
|
+
}
|
|
139
|
+
.terminoir-tabs--vertical > .terminoir-tabs__panel {
|
|
140
|
+
grid-column: 2;
|
|
141
|
+
grid-row: 1;
|
|
142
|
+
padding-top: 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
88
145
|
</style>
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
+
/** `icon` is a Font Awesome name, as for `NavIcon`. */
|
|
3
4
|
tabs: Array<{
|
|
4
5
|
id: string;
|
|
5
6
|
label: string;
|
|
7
|
+
icon?: string;
|
|
6
8
|
}>;
|
|
7
9
|
value?: string;
|
|
10
|
+
/** `vertical`: the tabs stack in a column beside the panel (a settings sidebar); a scrolling row on phones. */
|
|
11
|
+
orientation?: 'horizontal' | 'vertical';
|
|
8
12
|
class?: string;
|
|
9
13
|
children?: Snippet;
|
|
10
14
|
};
|