@streamscloud/kit 0.46.0 → 0.47.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/dist/ui/document-tabs/cmp.document-tabs.svelte +245 -0
- package/dist/ui/document-tabs/cmp.document-tabs.svelte.d.ts +80 -0
- package/dist/ui/document-tabs/document-tab.svelte +210 -0
- package/dist/ui/document-tabs/document-tab.svelte.d.ts +34 -0
- package/dist/ui/document-tabs/document-tabs-localization.d.ts +6 -0
- package/dist/ui/document-tabs/document-tabs-localization.js +33 -0
- package/dist/ui/document-tabs/index.d.ts +3 -0
- package/dist/ui/document-tabs/index.js +2 -0
- package/dist/ui/document-tabs/types.d.ts +20 -0
- package/dist/ui/document-tabs/types.js +1 -0
- package/dist/ui/page-layout/cmp.page-panel-sidebars.svelte +29 -3
- package/dist/ui/page-layout/cmp.page-panel-sidebars.svelte.d.ts +8 -0
- package/dist/ui/page-layout/cmp.page-panel.svelte +66 -42
- package/dist/ui/tabs/cmp.tabs.svelte +6 -14
- package/dist/ui/tabs/cmp.tabs.svelte.d.ts +5 -11
- package/dist/ui/tabs/tab.svelte +14 -75
- package/dist/ui/tabs/tab.svelte.d.ts +0 -1
- package/dist/ui/tabs/types.d.ts +1 -1
- package/package.json +5 -1
- package/dist/ui/tabs/tabs-localization.d.ts +0 -3
- package/dist/ui/tabs/tabs-localization.js +0 -12
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
<script lang="ts" generics="T">import { horizontalWheelScroll } from '../../core/actions';
|
|
2
|
+
import { Icon } from '../icon';
|
|
3
|
+
import DocumentTabItem from './document-tab.svelte';
|
|
4
|
+
import { DocumentTabsLocalization } from './document-tabs-localization';
|
|
5
|
+
import IconChevronLeft from '@fluentui/svg-icons/icons/chevron_left_20_regular.svg?raw';
|
|
6
|
+
import IconChevronRight from '@fluentui/svg-icons/icons/chevron_right_20_regular.svg?raw';
|
|
7
|
+
const { value, tabs, overflow = 'visible', compare, on } = $props();
|
|
8
|
+
const localization = new DocumentTabsLocalization();
|
|
9
|
+
const SCROLL_STEP = 160;
|
|
10
|
+
let trackNode = $state(null);
|
|
11
|
+
let canScrollBack = $state(false);
|
|
12
|
+
let canScrollForward = $state(false);
|
|
13
|
+
let scrollSyncArmed = false;
|
|
14
|
+
const isActive = (tab) => (compare ? compare(value, tab.value) : value === tab.value);
|
|
15
|
+
const isClosable = (tab) => !!on?.close && tab.closable !== false && !tab.disabled;
|
|
16
|
+
const activeIndex = $derived(tabs.findIndex((tab) => isActive(tab)));
|
|
17
|
+
const tabButtons = () => Array.from(trackNode?.querySelectorAll('[role="tab"]') ?? []);
|
|
18
|
+
const updateScrollState = (node) => {
|
|
19
|
+
if (overflow !== 'scroll') {
|
|
20
|
+
canScrollBack = false;
|
|
21
|
+
canScrollForward = false;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const { scrollLeft, scrollWidth, clientWidth } = node;
|
|
25
|
+
canScrollBack = scrollLeft > 0;
|
|
26
|
+
canScrollForward = scrollLeft < scrollWidth - clientWidth - 1;
|
|
27
|
+
};
|
|
28
|
+
const trackMounted = (node) => {
|
|
29
|
+
trackNode = node;
|
|
30
|
+
const observer = new ResizeObserver(() => updateScrollState(node));
|
|
31
|
+
observer.observe(node);
|
|
32
|
+
updateScrollState(node);
|
|
33
|
+
return {
|
|
34
|
+
destroy: () => observer.disconnect()
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const scrollBack = () => {
|
|
38
|
+
trackNode?.scrollBy({ left: -SCROLL_STEP, behavior: 'smooth' });
|
|
39
|
+
};
|
|
40
|
+
const scrollForward = () => {
|
|
41
|
+
trackNode?.scrollBy({ left: SCROLL_STEP, behavior: 'smooth' });
|
|
42
|
+
};
|
|
43
|
+
const stepFocus = (buttons, from, step) => {
|
|
44
|
+
const count = buttons.length;
|
|
45
|
+
for (let offset = 1; offset <= count; offset++) {
|
|
46
|
+
const candidate = buttons[(((from + step * offset) % count) + count) % count];
|
|
47
|
+
if (!candidate.disabled) {
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
const handleKeydown = (event) => {
|
|
54
|
+
const buttons = tabButtons();
|
|
55
|
+
const current = buttons.indexOf(document.activeElement);
|
|
56
|
+
if (current === -1) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
60
|
+
const tab = tabs[current];
|
|
61
|
+
if (tab && isClosable(tab)) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
on?.close?.(tab.value);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let target;
|
|
68
|
+
switch (event.key) {
|
|
69
|
+
case 'ArrowRight':
|
|
70
|
+
target = stepFocus(buttons, current, 1);
|
|
71
|
+
break;
|
|
72
|
+
case 'ArrowLeft':
|
|
73
|
+
target = stepFocus(buttons, current, -1);
|
|
74
|
+
break;
|
|
75
|
+
case 'Home':
|
|
76
|
+
target = stepFocus(buttons, -1, 1);
|
|
77
|
+
break;
|
|
78
|
+
case 'End':
|
|
79
|
+
target = stepFocus(buttons, buttons.length, -1);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!target) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
target.focus();
|
|
89
|
+
target.click();
|
|
90
|
+
};
|
|
91
|
+
$effect(() => {
|
|
92
|
+
const index = activeIndex;
|
|
93
|
+
if (!trackNode || index < 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!scrollSyncArmed) {
|
|
97
|
+
scrollSyncArmed = true;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
tabButtons()[index]?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
|
|
101
|
+
});
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<div class="document-tabs">
|
|
105
|
+
{#if canScrollBack}
|
|
106
|
+
<button type="button" class="document-tabs__scroll-btn document-tabs__scroll-btn--back" onclick={scrollBack} aria-label={localization.scrollBack}>
|
|
107
|
+
<Icon src={IconChevronLeft} size="sm" />
|
|
108
|
+
</button>
|
|
109
|
+
{/if}
|
|
110
|
+
<div
|
|
111
|
+
class="document-tabs__track"
|
|
112
|
+
class:document-tabs__track--scrollable={overflow === 'scroll'}
|
|
113
|
+
class:document-tabs__track--mask-back={canScrollBack && !canScrollForward}
|
|
114
|
+
class:document-tabs__track--mask-forward={canScrollForward && !canScrollBack}
|
|
115
|
+
class:document-tabs__track--mask-both={canScrollBack && canScrollForward}
|
|
116
|
+
role="tablist"
|
|
117
|
+
use:trackMounted
|
|
118
|
+
use:horizontalWheelScroll={{ isolateScroll: overflow === 'scroll' }}
|
|
119
|
+
onscroll={(event) => updateScrollState(event.currentTarget)}>
|
|
120
|
+
{#each tabs as tab (tab.value)}
|
|
121
|
+
<DocumentTabItem tab={tab} active={isActive(tab)} closable={isClosable(tab)} on={{ select: on?.change, close: on?.close, keydown: handleKeydown }} />
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
124
|
+
{#if canScrollForward}
|
|
125
|
+
<button type="button" class="document-tabs__scroll-btn document-tabs__scroll-btn--forward" onclick={scrollForward} aria-label={localization.scrollForward}>
|
|
126
|
+
<Icon src={IconChevronRight} size="sm" />
|
|
127
|
+
</button>
|
|
128
|
+
{/if}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!--
|
|
132
|
+
@component
|
|
133
|
+
DocumentTabs — browser-style tabs for an editor: the active tab is taller than its neighbours and
|
|
134
|
+
sits flush on the container's bottom rule, so it reads as continuous with the panel below while the
|
|
135
|
+
inactive ones stay behind that line. Each tab carries an optional icon, an unsaved-changes dot, a
|
|
136
|
+
`tone` for a tab of a different kind (a live preview), and its own `closable` opt-out. A closable or
|
|
137
|
+
dirty tab reserves the trailing slot permanently: the dot sits there at rest, the close button takes
|
|
138
|
+
its place on hover, and a tab's width never depends on which of them is showing. Arrow keys / Home /
|
|
139
|
+
End move between tabs (roving tabindex), Delete closes the focused one.
|
|
140
|
+
|
|
141
|
+
`overflow="scroll"` gives the strip its own scroll container with edge fades, chevrons and
|
|
142
|
+
wheel-to-scroll. Leave it `'visible'` where the container already scrolls the strip — two nested
|
|
143
|
+
scrollers fight each other.
|
|
144
|
+
|
|
145
|
+
The active tab dips `--sc-kit--document-tabs--baseline-overlap` below the strip to cover the bottom
|
|
146
|
+
rule of the bar it sits in. A container that clips (any `overflow` other than `visible`) has to
|
|
147
|
+
leave that last pixel inside its own clip region, or the overlap is cut off.
|
|
148
|
+
|
|
149
|
+
### CSS Custom Properties
|
|
150
|
+
| Property | Description | Default |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `--sc-kit--document-tabs--height` | Inactive tab height | `29px` |
|
|
153
|
+
| `--sc-kit--document-tabs--height--active` | Active tab height | `32px` |
|
|
154
|
+
| `--sc-kit--document-tabs--padding-inline` | Tab horizontal padding — the same in both states, so activating a tab shifts nothing sideways | `10px` |
|
|
155
|
+
| `--sc-kit--document-tabs--min-width` | Minimum tab width | `112px` |
|
|
156
|
+
| `--sc-kit--document-tabs--max-width` | Maximum tab width — the label ellipsizes past it | `200px` |
|
|
157
|
+
| `--sc-kit--document-tabs--gap` | Gap between tabs | `1px` |
|
|
158
|
+
| `--sc-kit--document-tabs--tab--gap` | Gap between a tab's icon, label and trailing slot | `8px` |
|
|
159
|
+
| `--sc-kit--document-tabs--radius` | Top corner radius | `--sc-kit--radius--sm` |
|
|
160
|
+
| `--sc-kit--document-tabs--border-width` | Tab border width | `1px` |
|
|
161
|
+
| `--sc-kit--document-tabs--border-color` | Tab border color | `--sc-kit--color--border` |
|
|
162
|
+
| `--sc-kit--document-tabs--baseline-overlap` | How far the active tab dips below the strip to cover its container's bottom rule | `1px` |
|
|
163
|
+
| `--sc-kit--document-tabs--background` | Inactive tab background | `--sc-kit--color--bg--app` |
|
|
164
|
+
| `--sc-kit--document-tabs--background--active` | Active tab background — matches the pane below | `--sc-kit--color--bg--panel` |
|
|
165
|
+
| `--sc-kit--document-tabs--background--hover` | Inactive tab hover background | `--sc-kit--color--bg--hover` |
|
|
166
|
+
| `--sc-kit--document-tabs--color` | Inactive tab label color | `--sc-kit--color--text--secondary` |
|
|
167
|
+
| `--sc-kit--document-tabs--color--active` | Active tab label color | `--sc-kit--color--text--primary` |
|
|
168
|
+
| `--sc-kit--document-tabs--color--hover` | Inactive tab hover label color | `--sc-kit--color--text--primary` |
|
|
169
|
+
| `--sc-kit--document-tabs--color--disabled` | Disabled tab label color | `--sc-kit--color--text--muted` |
|
|
170
|
+
| `--sc-kit--document-tabs--font-size` | Label font size | `--sc-kit--font-size--sm` |
|
|
171
|
+
| `--sc-kit--document-tabs--font-weight` | Label font weight | `--sc-kit--font-weight--regular` |
|
|
172
|
+
| `--sc-kit--document-tabs--scroll-btn--background` | Fade behind the overflow chevrons | `--sc-kit--color--bg--panel` |
|
|
173
|
+
-->
|
|
174
|
+
|
|
175
|
+
<style>.document-tabs {
|
|
176
|
+
--_document-tabs--gap: var(--sc-kit--document-tabs--gap, 1px);
|
|
177
|
+
--_document-tabs--baseline-overlap: var(--sc-kit--document-tabs--baseline-overlap, 1px);
|
|
178
|
+
--_document-tabs--scroll-btn-background: var(--sc-kit--document-tabs--scroll-btn--background, var(--sc-kit--color--bg--panel));
|
|
179
|
+
position: relative;
|
|
180
|
+
display: flex;
|
|
181
|
+
align-self: stretch;
|
|
182
|
+
align-items: flex-end;
|
|
183
|
+
min-inline-size: 0;
|
|
184
|
+
margin-block-end: calc(-1 * var(--_document-tabs--baseline-overlap));
|
|
185
|
+
}
|
|
186
|
+
.document-tabs:hover {
|
|
187
|
+
--_scroll-btn-opacity: 1;
|
|
188
|
+
}
|
|
189
|
+
.document-tabs__track {
|
|
190
|
+
--_mask-size: 1.5rem;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: flex-end;
|
|
193
|
+
gap: var(--_document-tabs--gap);
|
|
194
|
+
min-inline-size: 0;
|
|
195
|
+
}
|
|
196
|
+
.document-tabs__track--scrollable {
|
|
197
|
+
overflow-x: auto;
|
|
198
|
+
scrollbar-width: none;
|
|
199
|
+
}
|
|
200
|
+
.document-tabs__track--scrollable::-webkit-scrollbar {
|
|
201
|
+
display: none;
|
|
202
|
+
}
|
|
203
|
+
.document-tabs__track--mask-back {
|
|
204
|
+
mask-image: linear-gradient(to right, transparent 0%, black var(--_mask-size), black 100%);
|
|
205
|
+
}
|
|
206
|
+
.document-tabs__track--mask-forward {
|
|
207
|
+
mask-image: linear-gradient(to right, black 0%, black calc(100% - var(--_mask-size)), transparent 100%);
|
|
208
|
+
}
|
|
209
|
+
.document-tabs__track--mask-both {
|
|
210
|
+
mask-image: linear-gradient(to right, transparent 0%, black var(--_mask-size), black calc(100% - var(--_mask-size)), transparent 100%);
|
|
211
|
+
}
|
|
212
|
+
.document-tabs__scroll-btn {
|
|
213
|
+
position: absolute;
|
|
214
|
+
inset-block: 0;
|
|
215
|
+
z-index: 1;
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
padding-inline: 0.25rem;
|
|
220
|
+
background: transparent;
|
|
221
|
+
backdrop-filter: blur(2px);
|
|
222
|
+
border: none;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
color: var(--sc-kit--color--text--secondary);
|
|
225
|
+
opacity: var(--_scroll-btn-opacity, 0);
|
|
226
|
+
transition: opacity var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
227
|
+
}
|
|
228
|
+
.document-tabs__scroll-btn:hover {
|
|
229
|
+
color: var(--sc-kit--color--text--primary);
|
|
230
|
+
}
|
|
231
|
+
.document-tabs__scroll-btn:focus-visible {
|
|
232
|
+
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
233
|
+
outline-offset: -2px;
|
|
234
|
+
opacity: 1;
|
|
235
|
+
}
|
|
236
|
+
.document-tabs__scroll-btn--back {
|
|
237
|
+
inset-inline-start: 0;
|
|
238
|
+
padding-inline-end: 0.625rem;
|
|
239
|
+
background: linear-gradient(to right, var(--_document-tabs--scroll-btn-background) 60%, transparent);
|
|
240
|
+
}
|
|
241
|
+
.document-tabs__scroll-btn--forward {
|
|
242
|
+
inset-inline-end: 0;
|
|
243
|
+
padding-inline-start: 0.625rem;
|
|
244
|
+
background: linear-gradient(to left, var(--_document-tabs--scroll-btn-background) 60%, transparent);
|
|
245
|
+
}</style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { DocumentTab, DocumentTabsOverflow } from './types';
|
|
2
|
+
declare function $$render<T>(): {
|
|
3
|
+
props: {
|
|
4
|
+
value: T;
|
|
5
|
+
/** `NoInfer<T>` ensures `T` narrows from `value` only; inline tab literals type-check against the value's T without widening — no `as const` needed at callsites. */
|
|
6
|
+
tabs: DocumentTab<NoInfer<T>>[];
|
|
7
|
+
/** `'scroll'` makes the strip its own scroll container with edge fades and chevrons. Leave it `'visible'` when the container already scrolls the strip. @default 'visible' */
|
|
8
|
+
overflow?: DocumentTabsOverflow;
|
|
9
|
+
/** Equality for object T values. @default reference equality (`===`) */
|
|
10
|
+
compare?: (a: T, b: T) => boolean;
|
|
11
|
+
on?: {
|
|
12
|
+
change?: (value: T) => void;
|
|
13
|
+
/** Providing this renders a close button on every tab that doesn't opt out with `closable: false`. */
|
|
14
|
+
close?: (value: T) => void;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
exports: {};
|
|
18
|
+
bindings: "";
|
|
19
|
+
slots: {};
|
|
20
|
+
events: {};
|
|
21
|
+
};
|
|
22
|
+
declare class __sveltets_Render<T> {
|
|
23
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
24
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
25
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
26
|
+
bindings(): "";
|
|
27
|
+
exports(): {};
|
|
28
|
+
}
|
|
29
|
+
interface $$IsomorphicComponent {
|
|
30
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
31
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
32
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
33
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
34
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* DocumentTabs — browser-style tabs for an editor: the active tab is taller than its neighbours and
|
|
38
|
+
* sits flush on the container's bottom rule, so it reads as continuous with the panel below while the
|
|
39
|
+
* inactive ones stay behind that line. Each tab carries an optional icon, an unsaved-changes dot, a
|
|
40
|
+
* `tone` for a tab of a different kind (a live preview), and its own `closable` opt-out. A closable or
|
|
41
|
+
* dirty tab reserves the trailing slot permanently: the dot sits there at rest, the close button takes
|
|
42
|
+
* its place on hover, and a tab's width never depends on which of them is showing. Arrow keys / Home /
|
|
43
|
+
* End move between tabs (roving tabindex), Delete closes the focused one.
|
|
44
|
+
*
|
|
45
|
+
* `overflow="scroll"` gives the strip its own scroll container with edge fades, chevrons and
|
|
46
|
+
* wheel-to-scroll. Leave it `'visible'` where the container already scrolls the strip — two nested
|
|
47
|
+
* scrollers fight each other.
|
|
48
|
+
*
|
|
49
|
+
* The active tab dips `--sc-kit--document-tabs--baseline-overlap` below the strip to cover the bottom
|
|
50
|
+
* rule of the bar it sits in. A container that clips (any `overflow` other than `visible`) has to
|
|
51
|
+
* leave that last pixel inside its own clip region, or the overlap is cut off.
|
|
52
|
+
*
|
|
53
|
+
* ### CSS Custom Properties
|
|
54
|
+
* | Property | Description | Default |
|
|
55
|
+
* |---|---|---|
|
|
56
|
+
* | `--sc-kit--document-tabs--height` | Inactive tab height | `29px` |
|
|
57
|
+
* | `--sc-kit--document-tabs--height--active` | Active tab height | `32px` |
|
|
58
|
+
* | `--sc-kit--document-tabs--padding-inline` | Tab horizontal padding — the same in both states, so activating a tab shifts nothing sideways | `10px` |
|
|
59
|
+
* | `--sc-kit--document-tabs--min-width` | Minimum tab width | `112px` |
|
|
60
|
+
* | `--sc-kit--document-tabs--max-width` | Maximum tab width — the label ellipsizes past it | `200px` |
|
|
61
|
+
* | `--sc-kit--document-tabs--gap` | Gap between tabs | `1px` |
|
|
62
|
+
* | `--sc-kit--document-tabs--tab--gap` | Gap between a tab's icon, label and trailing slot | `8px` |
|
|
63
|
+
* | `--sc-kit--document-tabs--radius` | Top corner radius | `--sc-kit--radius--sm` |
|
|
64
|
+
* | `--sc-kit--document-tabs--border-width` | Tab border width | `1px` |
|
|
65
|
+
* | `--sc-kit--document-tabs--border-color` | Tab border color | `--sc-kit--color--border` |
|
|
66
|
+
* | `--sc-kit--document-tabs--baseline-overlap` | How far the active tab dips below the strip to cover its container's bottom rule | `1px` |
|
|
67
|
+
* | `--sc-kit--document-tabs--background` | Inactive tab background | `--sc-kit--color--bg--app` |
|
|
68
|
+
* | `--sc-kit--document-tabs--background--active` | Active tab background — matches the pane below | `--sc-kit--color--bg--panel` |
|
|
69
|
+
* | `--sc-kit--document-tabs--background--hover` | Inactive tab hover background | `--sc-kit--color--bg--hover` |
|
|
70
|
+
* | `--sc-kit--document-tabs--color` | Inactive tab label color | `--sc-kit--color--text--secondary` |
|
|
71
|
+
* | `--sc-kit--document-tabs--color--active` | Active tab label color | `--sc-kit--color--text--primary` |
|
|
72
|
+
* | `--sc-kit--document-tabs--color--hover` | Inactive tab hover label color | `--sc-kit--color--text--primary` |
|
|
73
|
+
* | `--sc-kit--document-tabs--color--disabled` | Disabled tab label color | `--sc-kit--color--text--muted` |
|
|
74
|
+
* | `--sc-kit--document-tabs--font-size` | Label font size | `--sc-kit--font-size--sm` |
|
|
75
|
+
* | `--sc-kit--document-tabs--font-weight` | Label font weight | `--sc-kit--font-weight--regular` |
|
|
76
|
+
* | `--sc-kit--document-tabs--scroll-btn--background` | Fade behind the overflow chevrons | `--sc-kit--color--bg--panel` |
|
|
77
|
+
*/
|
|
78
|
+
declare const Cmp: $$IsomorphicComponent;
|
|
79
|
+
type Cmp<T> = InstanceType<typeof Cmp<T>>;
|
|
80
|
+
export default Cmp;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<script lang="ts" generics="T">import { IconSlot } from '../icon';
|
|
2
|
+
import { DocumentTabsLocalization } from './document-tabs-localization';
|
|
3
|
+
import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
|
|
4
|
+
const { tab, active, closable, on } = $props();
|
|
5
|
+
const localization = new DocumentTabsLocalization();
|
|
6
|
+
const ariaLabel = $derived(tab.dirty ? `${tab.label} — ${localization.unsavedChanges}` : undefined);
|
|
7
|
+
const handleClick = () => {
|
|
8
|
+
if (tab.disabled || active) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
on?.select?.(tab.value);
|
|
12
|
+
};
|
|
13
|
+
const handleClose = () => {
|
|
14
|
+
on?.close?.(tab.value);
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<div
|
|
19
|
+
class="document-tab"
|
|
20
|
+
class:document-tab--active={active}
|
|
21
|
+
class:document-tab--disabled={tab.disabled}
|
|
22
|
+
class:document-tab--hoverable={!active && !tab.disabled}
|
|
23
|
+
class:document-tab--dirty={tab.dirty}
|
|
24
|
+
class:document-tab--closable={closable}
|
|
25
|
+
class:document-tab--tone-gray={tab.tone === 'gray'}
|
|
26
|
+
class:document-tab--tone-blue={tab.tone === 'blue'}
|
|
27
|
+
class:document-tab--tone-green={tab.tone === 'green'}
|
|
28
|
+
class:document-tab--tone-red={tab.tone === 'red'}
|
|
29
|
+
class:document-tab--tone-orange={tab.tone === 'orange'}
|
|
30
|
+
class:document-tab--tone-teal={tab.tone === 'teal'}
|
|
31
|
+
role="presentation">
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
class="document-tab__button"
|
|
35
|
+
role="tab"
|
|
36
|
+
aria-selected={active}
|
|
37
|
+
aria-label={ariaLabel}
|
|
38
|
+
tabindex={active ? 0 : -1}
|
|
39
|
+
disabled={tab.disabled}
|
|
40
|
+
title={tab.tooltip}
|
|
41
|
+
onclick={handleClick}
|
|
42
|
+
onkeydown={on?.keydown}>
|
|
43
|
+
{#if tab.icon}
|
|
44
|
+
<span class="document-tab__icon"><IconSlot icon={tab.icon} fallbacks={{ size: 'sm' }} /></span>
|
|
45
|
+
{/if}
|
|
46
|
+
<span class="document-tab__label">{tab.label}</span>
|
|
47
|
+
</button>
|
|
48
|
+
{#if tab.dirty || closable}
|
|
49
|
+
<span class="document-tab__trailing">
|
|
50
|
+
<span class="document-tab__dot" class:document-tab__dot--hidden={!tab.dirty}></span>
|
|
51
|
+
{#if closable}
|
|
52
|
+
<button type="button" class="document-tab__close" tabindex={active ? 0 : -1} aria-label={localization.close} onclick={handleClose}>
|
|
53
|
+
<IconSlot icon={IconDismiss} fallbacks={{ size: 'sm' }} />
|
|
54
|
+
</button>
|
|
55
|
+
{/if}
|
|
56
|
+
</span>
|
|
57
|
+
{/if}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<style>.document-tab {
|
|
61
|
+
--_document-tab--height: var(--sc-kit--document-tabs--height, 1.8125rem);
|
|
62
|
+
--_document-tab--padding-inline: var(--sc-kit--document-tabs--padding-inline, 0.625rem);
|
|
63
|
+
--_document-tab--min-width: var(--sc-kit--document-tabs--min-width, 7rem);
|
|
64
|
+
--_document-tab--max-width: var(--sc-kit--document-tabs--max-width, 12.5rem);
|
|
65
|
+
--_document-tab--gap: var(--sc-kit--document-tabs--tab--gap, 0.5rem);
|
|
66
|
+
--_document-tab--radius: var(--sc-kit--document-tabs--radius, var(--sc-kit--radius--sm));
|
|
67
|
+
--_document-tab--border-width: var(--sc-kit--document-tabs--border-width, 1px);
|
|
68
|
+
--_document-tab--border-color: var(--sc-kit--document-tabs--border-color, var(--sc-kit--color--border));
|
|
69
|
+
--_document-tab--baseline-overlap: var(--sc-kit--document-tabs--baseline-overlap, 1px);
|
|
70
|
+
--_document-tab--background: var(--_document-tab--tone-background, var(--sc-kit--document-tabs--background, var(--sc-kit--color--bg--app)));
|
|
71
|
+
--_document-tab--background-hover: var(
|
|
72
|
+
--_document-tab--tone-background-hover,
|
|
73
|
+
var(--sc-kit--document-tabs--background--hover, var(--sc-kit--color--bg--hover))
|
|
74
|
+
);
|
|
75
|
+
--_document-tab--color: var(--sc-kit--document-tabs--color, var(--sc-kit--color--text--secondary));
|
|
76
|
+
--_document-tab--font-size: var(--sc-kit--document-tabs--font-size, var(--sc-kit--font-size--sm));
|
|
77
|
+
--_document-tab--font-weight: var(--sc-kit--document-tabs--font-weight, var(--sc-kit--font-weight--regular));
|
|
78
|
+
--_document-tab--cursor: pointer;
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: var(--_document-tab--gap);
|
|
82
|
+
block-size: var(--_document-tab--height);
|
|
83
|
+
min-inline-size: var(--_document-tab--min-width);
|
|
84
|
+
max-inline-size: var(--_document-tab--max-width);
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
padding-inline: var(--_document-tab--padding-inline);
|
|
87
|
+
margin-block-end: var(--_document-tab--baseline-overlap);
|
|
88
|
+
background: var(--_document-tab--background);
|
|
89
|
+
border: var(--_document-tab--border-width) solid var(--_document-tab--border-color);
|
|
90
|
+
border-block-end: 0;
|
|
91
|
+
border-start-start-radius: var(--_document-tab--radius);
|
|
92
|
+
border-start-end-radius: var(--_document-tab--radius);
|
|
93
|
+
color: var(--_document-tab--color);
|
|
94
|
+
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default), background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
95
|
+
}
|
|
96
|
+
.document-tab--tone-gray {
|
|
97
|
+
--_document-tab--tone-background: var(--sc-kit--tone--gray--bg);
|
|
98
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--gray--fg) 10%, var(--sc-kit--tone--gray--bg));
|
|
99
|
+
}
|
|
100
|
+
.document-tab--tone-blue {
|
|
101
|
+
--_document-tab--tone-background: var(--sc-kit--tone--blue--bg);
|
|
102
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--blue--fg) 10%, var(--sc-kit--tone--blue--bg));
|
|
103
|
+
}
|
|
104
|
+
.document-tab--tone-green {
|
|
105
|
+
--_document-tab--tone-background: var(--sc-kit--tone--green--bg);
|
|
106
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--green--fg) 10%, var(--sc-kit--tone--green--bg));
|
|
107
|
+
}
|
|
108
|
+
.document-tab--tone-red {
|
|
109
|
+
--_document-tab--tone-background: var(--sc-kit--tone--red--bg);
|
|
110
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--red--fg) 10%, var(--sc-kit--tone--red--bg));
|
|
111
|
+
}
|
|
112
|
+
.document-tab--tone-orange {
|
|
113
|
+
--_document-tab--tone-background: var(--sc-kit--tone--orange--bg);
|
|
114
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--orange--fg) 10%, var(--sc-kit--tone--orange--bg));
|
|
115
|
+
}
|
|
116
|
+
.document-tab--tone-teal {
|
|
117
|
+
--_document-tab--tone-background: var(--sc-kit--tone--teal--bg);
|
|
118
|
+
--_document-tab--tone-background-hover: color-mix(in srgb, var(--sc-kit--tone--teal--fg) 10%, var(--sc-kit--tone--teal--bg));
|
|
119
|
+
}
|
|
120
|
+
.document-tab--active {
|
|
121
|
+
--_document-tab--height: var(--sc-kit--document-tabs--height--active, 2rem);
|
|
122
|
+
--_document-tab--background: var(--_document-tab--tone-background, var(--sc-kit--document-tabs--background--active, var(--sc-kit--color--bg--panel)));
|
|
123
|
+
--_document-tab--color: var(--sc-kit--document-tabs--color--active, var(--sc-kit--color--text--primary));
|
|
124
|
+
margin-block-end: 0;
|
|
125
|
+
}
|
|
126
|
+
.document-tab--hoverable:hover {
|
|
127
|
+
--_document-tab--background: var(--_document-tab--background-hover);
|
|
128
|
+
--_document-tab--color: var(--sc-kit--document-tabs--color--hover, var(--sc-kit--color--text--primary));
|
|
129
|
+
}
|
|
130
|
+
.document-tab--disabled {
|
|
131
|
+
--_document-tab--color: var(--sc-kit--document-tabs--color--disabled, var(--sc-kit--color--text--muted));
|
|
132
|
+
--_document-tab--cursor: default;
|
|
133
|
+
}
|
|
134
|
+
.document-tab--closable:hover, .document-tab--closable:focus-within {
|
|
135
|
+
--_document-tab--dot-visibility: hidden;
|
|
136
|
+
--_document-tab--close-display: inline-flex;
|
|
137
|
+
}
|
|
138
|
+
.document-tab__button {
|
|
139
|
+
display: inline-flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: var(--_document-tab--gap);
|
|
142
|
+
flex: 1;
|
|
143
|
+
min-inline-size: 0;
|
|
144
|
+
block-size: 100%;
|
|
145
|
+
padding: 0;
|
|
146
|
+
background: none;
|
|
147
|
+
border: none;
|
|
148
|
+
color: inherit;
|
|
149
|
+
font-family: inherit;
|
|
150
|
+
font-size: var(--_document-tab--font-size);
|
|
151
|
+
font-weight: var(--_document-tab--font-weight);
|
|
152
|
+
line-height: var(--sc-kit--leading--tight);
|
|
153
|
+
cursor: var(--_document-tab--cursor);
|
|
154
|
+
}
|
|
155
|
+
.document-tab__button:focus-visible {
|
|
156
|
+
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
157
|
+
outline-offset: -2px;
|
|
158
|
+
}
|
|
159
|
+
.document-tab__icon {
|
|
160
|
+
display: inline-flex;
|
|
161
|
+
flex-shrink: 0;
|
|
162
|
+
}
|
|
163
|
+
.document-tab__label {
|
|
164
|
+
text-overflow: ellipsis;
|
|
165
|
+
max-width: 100%;
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
}
|
|
169
|
+
.document-tab__trailing {
|
|
170
|
+
position: relative;
|
|
171
|
+
display: inline-flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
flex-shrink: 0;
|
|
175
|
+
inline-size: 1.125rem;
|
|
176
|
+
block-size: 1.125rem;
|
|
177
|
+
}
|
|
178
|
+
.document-tab__dot {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
visibility: var(--_document-tab--dot-visibility, visible);
|
|
181
|
+
inline-size: 0.375rem;
|
|
182
|
+
block-size: 0.375rem;
|
|
183
|
+
background: currentcolor;
|
|
184
|
+
border-radius: 50%;
|
|
185
|
+
}
|
|
186
|
+
.document-tab__dot--hidden {
|
|
187
|
+
visibility: hidden;
|
|
188
|
+
}
|
|
189
|
+
.document-tab__close {
|
|
190
|
+
display: var(--_document-tab--close-display, none);
|
|
191
|
+
position: absolute;
|
|
192
|
+
inset: 0;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: center;
|
|
195
|
+
padding: 1px;
|
|
196
|
+
background: none;
|
|
197
|
+
border: none;
|
|
198
|
+
border-radius: var(--sc-kit--radius--sm);
|
|
199
|
+
color: var(--sc-kit--color--text--secondary);
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default), background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
202
|
+
}
|
|
203
|
+
.document-tab__close:hover {
|
|
204
|
+
background: var(--sc-kit--color--bg--hover);
|
|
205
|
+
color: var(--sc-kit--color--text--primary);
|
|
206
|
+
}
|
|
207
|
+
.document-tab__close:focus-visible {
|
|
208
|
+
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
209
|
+
outline-offset: 1px;
|
|
210
|
+
}</style>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DocumentTab } from './types';
|
|
2
|
+
declare function $$render<T>(): {
|
|
3
|
+
props: {
|
|
4
|
+
tab: DocumentTab<T>;
|
|
5
|
+
active: boolean;
|
|
6
|
+
closable: boolean;
|
|
7
|
+
on?: {
|
|
8
|
+
select?: (value: T) => void;
|
|
9
|
+
close?: (value: T) => void;
|
|
10
|
+
keydown?: (event: KeyboardEvent) => void;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
exports: {};
|
|
14
|
+
bindings: "";
|
|
15
|
+
slots: {};
|
|
16
|
+
events: {};
|
|
17
|
+
};
|
|
18
|
+
declare class __sveltets_Render<T> {
|
|
19
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
20
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
21
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
22
|
+
bindings(): "";
|
|
23
|
+
exports(): {};
|
|
24
|
+
}
|
|
25
|
+
interface $$IsomorphicComponent {
|
|
26
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
27
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
28
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
29
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
30
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
31
|
+
}
|
|
32
|
+
declare const DocumentTab: $$IsomorphicComponent;
|
|
33
|
+
type DocumentTab<T> = InstanceType<typeof DocumentTab<T>>;
|
|
34
|
+
export default DocumentTab;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AppLocale } from '../../core/locale';
|
|
2
|
+
const loc = {
|
|
3
|
+
close: {
|
|
4
|
+
en: 'Close tab',
|
|
5
|
+
no: 'Lukk fane'
|
|
6
|
+
},
|
|
7
|
+
unsavedChanges: {
|
|
8
|
+
en: 'unsaved changes',
|
|
9
|
+
no: 'ulagrede endringer'
|
|
10
|
+
},
|
|
11
|
+
scrollBack: {
|
|
12
|
+
en: 'Scroll back',
|
|
13
|
+
no: 'Rull tilbake'
|
|
14
|
+
},
|
|
15
|
+
scrollForward: {
|
|
16
|
+
en: 'Scroll forward',
|
|
17
|
+
no: 'Rull fremover'
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export class DocumentTabsLocalization {
|
|
21
|
+
get close() {
|
|
22
|
+
return loc.close[AppLocale.current];
|
|
23
|
+
}
|
|
24
|
+
get unsavedChanges() {
|
|
25
|
+
return loc.unsavedChanges[AppLocale.current];
|
|
26
|
+
}
|
|
27
|
+
get scrollBack() {
|
|
28
|
+
return loc.scrollBack[AppLocale.current];
|
|
29
|
+
}
|
|
30
|
+
get scrollForward() {
|
|
31
|
+
return loc.scrollForward[AppLocale.current];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IconProp } from '../icon';
|
|
2
|
+
export declare const DOCUMENT_TAB_TONES: readonly ["gray", "blue", "green", "red", "orange", "teal"];
|
|
3
|
+
export type DocumentTabTone = (typeof DOCUMENT_TAB_TONES)[number];
|
|
4
|
+
export type DocumentTabsOverflow = 'visible' | 'scroll';
|
|
5
|
+
export type DocumentTab<TValue = unknown> = {
|
|
6
|
+
value: TValue;
|
|
7
|
+
label: string;
|
|
8
|
+
/** Leading glyph — a file-type mark, typically. */
|
|
9
|
+
icon?: IconProp;
|
|
10
|
+
/** Unsaved changes: a dot in the trailing slot, which the close button takes over on hover. */
|
|
11
|
+
dirty?: boolean;
|
|
12
|
+
/** Set `false` to keep one tab open while its neighbours stay closable. @default true (whenever `on.close` is provided) */
|
|
13
|
+
closable?: boolean;
|
|
14
|
+
/** Tints the tab so it reads as a different kind of thing from the documents beside it — a live preview, a diff, a terminal. */
|
|
15
|
+
tone?: DocumentTabTone;
|
|
16
|
+
/** Full text for a label that truncates — the complete file path, typically. */
|
|
17
|
+
tooltip?: string;
|
|
18
|
+
/** Non-interactive — click does nothing. */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DOCUMENT_TAB_TONES = ['gray', 'blue', 'green', 'red', 'orange', 'teal'];
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
<script lang="ts">import { slideHorizontally, transitionDuration } from '../../core/transitions';
|
|
2
2
|
import PagePanel from './cmp.page-panel.svelte';
|
|
3
3
|
import { untrack } from 'svelte';
|
|
4
|
-
const { panelState, children, sidebarLeft, sidebarRight, sidebarLeftIsValid = true, sidebarRightIsValid = true, title, actionsLeft, actionsCenter, actionsRight, balanceHeaderCenter = false, toggle } = $props();
|
|
4
|
+
const { panelState, children, sidebarLeft, sidebarRight, sidebarLeftIsValid = true, sidebarRightIsValid = true, title, actionsLeft, actionsCenter, actionsRight, balanceHeaderCenter = false, alignActionsLeftToContent = false, toggle } = $props();
|
|
5
5
|
let leftPanel = $state(null);
|
|
6
6
|
let rightPanel = $state(null);
|
|
7
|
+
let sidebarLeftWidth = $state(0);
|
|
8
|
+
const sidebarLeftMounted = (node) => {
|
|
9
|
+
const measure = () => {
|
|
10
|
+
const divider = parseFloat(getComputedStyle(node).borderInlineEndWidth) || 0;
|
|
11
|
+
sidebarLeftWidth = Math.max(0, node.offsetWidth - divider);
|
|
12
|
+
};
|
|
13
|
+
const observer = new ResizeObserver(measure);
|
|
14
|
+
observer.observe(node);
|
|
15
|
+
measure();
|
|
16
|
+
return {
|
|
17
|
+
destroy: () => {
|
|
18
|
+
observer.disconnect();
|
|
19
|
+
sidebarLeftWidth = 0;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
7
23
|
$effect(() => {
|
|
8
24
|
const hasLeft = !!sidebarLeft;
|
|
9
25
|
untrack(() => {
|
|
@@ -69,10 +85,14 @@ $effect(() => {
|
|
|
69
85
|
actionsCenter={actionsCenter}
|
|
70
86
|
actionsRight={actionsRight}
|
|
71
87
|
balanceHeaderCenter={balanceHeaderCenter}
|
|
72
|
-
collapseButtons={collapseButtons}
|
|
88
|
+
collapseButtons={collapseButtons}
|
|
89
|
+
--_--page-panel--content-start="{alignActionsLeftToContent ? sidebarLeftWidth : 0}px">
|
|
73
90
|
<div class="page-panel-sidebars">
|
|
74
91
|
{#if sidebarLeft && !leftCollapsed}
|
|
75
|
-
<div
|
|
92
|
+
<div
|
|
93
|
+
class="page-panel-sidebars__sidebar page-panel-sidebars__sidebar--left"
|
|
94
|
+
use:sidebarLeftMounted
|
|
95
|
+
transition:slideHorizontally|local={{ duration: transitionDuration.slow }}>
|
|
76
96
|
{@render sidebarLeft()}
|
|
77
97
|
</div>
|
|
78
98
|
{/if}
|
|
@@ -97,6 +117,12 @@ Pass a `SidebarsToggle` instance as `toggle` to drive an external "collapse both
|
|
|
97
117
|
top-bar button) without `bind:this` or a local collapsed flag — read `toggleBoth` / `allCollapsed`
|
|
98
118
|
off that same instance.
|
|
99
119
|
|
|
120
|
+
`alignActionsLeftToContent` starts the header's scrolling left track at the content column rather
|
|
121
|
+
than right after the collapse button: whatever sits in `actionsLeft` begins exactly where the
|
|
122
|
+
content pane begins, and follows that edge as the sidebar resizes, collapses, or isn't there at all.
|
|
123
|
+
The track starts on the sidebar's divider rather than after it, so a bordered child lands on that
|
|
124
|
+
line instead of doubling it. The collapse button stays put outside that track.
|
|
125
|
+
|
|
100
126
|
### CSS Custom Properties
|
|
101
127
|
| Property | Description | Default |
|
|
102
128
|
|---|---|---|
|
|
@@ -14,6 +14,8 @@ type Props = {
|
|
|
14
14
|
actionsRight?: Snippet;
|
|
15
15
|
/** Lay the inner panel header out as a 3-column grid so `actionsCenter` is centered against the panel width. @default false */
|
|
16
16
|
balanceHeaderCenter?: boolean;
|
|
17
|
+
/** Start the header's left track at the content column instead of right after the collapse button, so `actionsLeft` begins where the content pane begins and follows the sidebar as it resizes or collapses. @default false */
|
|
18
|
+
alignActionsLeftToContent?: boolean;
|
|
17
19
|
/** Shared collapse target for an external "collapse both sidebars" control (e.g. `PageLayout`'s `editorControls.panelsToggle`). */
|
|
18
20
|
toggle?: SidebarsToggle;
|
|
19
21
|
};
|
|
@@ -26,6 +28,12 @@ type Props = {
|
|
|
26
28
|
* top-bar button) without `bind:this` or a local collapsed flag — read `toggleBoth` / `allCollapsed`
|
|
27
29
|
* off that same instance.
|
|
28
30
|
*
|
|
31
|
+
* `alignActionsLeftToContent` starts the header's scrolling left track at the content column rather
|
|
32
|
+
* than right after the collapse button: whatever sits in `actionsLeft` begins exactly where the
|
|
33
|
+
* content pane begins, and follows that edge as the sidebar resizes, collapses, or isn't there at all.
|
|
34
|
+
* The track starts on the sidebar's divider rather than after it, so a bordered child lands on that
|
|
35
|
+
* line instead of doubling it. The collapse button stays put outside that track.
|
|
36
|
+
*
|
|
29
37
|
* ### CSS Custom Properties
|
|
30
38
|
* | Property | Description | Default |
|
|
31
39
|
* |---|---|---|
|
|
@@ -145,44 +145,50 @@ $effect(() => {
|
|
|
145
145
|
{:else}
|
|
146
146
|
{#if balanceHeaderCenter || leftCollapseIds.length > 0 || title || actionsLeft}
|
|
147
147
|
<div class="page-panel__header-left" class:page-panel__header-left--balanced={balanceHeaderCenter}>
|
|
148
|
-
{#if
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<Icon src={IconChevronLeft} size="sm" />
|
|
155
|
-
</button>
|
|
148
|
+
{#if leftCollapseIds.length > 0}
|
|
149
|
+
<div class="page-panel__header-collapse">
|
|
150
|
+
{#each leftCollapseIds as cid (cid)}
|
|
151
|
+
<PanelCollapseButton panelState={panelState} panelId={cid} />
|
|
152
|
+
{/each}
|
|
153
|
+
</div>
|
|
156
154
|
{/if}
|
|
157
|
-
<div
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<PanelCollapseButton panelState={panelState} panelId={cid} />
|
|
167
|
-
{/each}
|
|
168
|
-
{#if typeof title === 'function'}
|
|
169
|
-
{@render title()}
|
|
170
|
-
{:else if title}
|
|
171
|
-
<span class="page-panel__title">{title}</span>
|
|
155
|
+
<div class="page-panel__header-track-wrap">
|
|
156
|
+
{#if canScrollHeaderLeftBack}
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
class="page-panel__header-scroll-btn page-panel__header-scroll-btn--back"
|
|
160
|
+
onclick={scrollHeaderLeftBack}
|
|
161
|
+
aria-label={localization.scrollBack}>
|
|
162
|
+
<Icon src={IconChevronLeft} size="sm" />
|
|
163
|
+
</button>
|
|
172
164
|
{/if}
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
<div
|
|
166
|
+
class="page-panel__header-left-track"
|
|
167
|
+
class:page-panel__header-left-track--mask-back={canScrollHeaderLeftBack && !canScrollHeaderLeftForward}
|
|
168
|
+
class:page-panel__header-left-track--mask-forward={canScrollHeaderLeftForward && !canScrollHeaderLeftBack}
|
|
169
|
+
class:page-panel__header-left-track--mask-both={canScrollHeaderLeftBack && canScrollHeaderLeftForward}
|
|
170
|
+
use:headerLeftTrackMounted
|
|
171
|
+
use:horizontalWheelScroll
|
|
172
|
+
onscroll={(e) => updateHeaderLeftScrollState(e.currentTarget)}>
|
|
173
|
+
{#if typeof title === 'function'}
|
|
174
|
+
{@render title()}
|
|
175
|
+
{:else if title}
|
|
176
|
+
<span class="page-panel__title">{title}</span>
|
|
177
|
+
{/if}
|
|
178
|
+
{#if actionsLeft}
|
|
179
|
+
{@render actionsLeft()}
|
|
180
|
+
{/if}
|
|
181
|
+
</div>
|
|
182
|
+
{#if canScrollHeaderLeftForward}
|
|
183
|
+
<button
|
|
184
|
+
type="button"
|
|
185
|
+
class="page-panel__header-scroll-btn page-panel__header-scroll-btn--forward"
|
|
186
|
+
onclick={scrollHeaderLeftForward}
|
|
187
|
+
aria-label={localization.scrollForward}>
|
|
188
|
+
<Icon src={IconChevronRight} size="sm" />
|
|
189
|
+
</button>
|
|
175
190
|
{/if}
|
|
176
191
|
</div>
|
|
177
|
-
{#if canScrollHeaderLeftForward}
|
|
178
|
-
<button
|
|
179
|
-
type="button"
|
|
180
|
-
class="page-panel__header-scroll-btn page-panel__header-scroll-btn--forward"
|
|
181
|
-
onclick={scrollHeaderLeftForward}
|
|
182
|
-
aria-label={localization.scrollForward}>
|
|
183
|
-
<Icon src={IconChevronRight} size="sm" />
|
|
184
|
-
</button>
|
|
185
|
-
{/if}
|
|
186
192
|
</div>
|
|
187
193
|
{/if}
|
|
188
194
|
{#if balanceHeaderCenter || actionsCenter}
|
|
@@ -274,6 +280,7 @@ to min / max; Enter / Space toggle collapsed.
|
|
|
274
280
|
--_page-panel--footer-justify: var(--sc-kit--page-panel--footer--justify, flex-end);
|
|
275
281
|
--_page-panel--header-background: var(--sc-kit--page-panel--header--background, var(--sc-kit--color--bg--panel));
|
|
276
282
|
--_page-panel--header-padding-inline: var(--sc-kit--page-panel--header--padding-inline, 1.5rem);
|
|
283
|
+
--_page-panel--content-start: var(--_--page-panel--content-start, 0px);
|
|
277
284
|
--_page-panel--title-font-size: var(--sc-kit--page-panel--title--font-size, 0.875rem);
|
|
278
285
|
--_page-panel--resizer-color: var(--sc-kit--page-panel--resizer-color, var(--sc-kit--color--border));
|
|
279
286
|
--_page-panel--height: var(--_--page-layout--panel--height, 100%);
|
|
@@ -334,7 +341,8 @@ to min / max; Enter / Space toggle collapsed.
|
|
|
334
341
|
font-size: var(--_page-panel--title-font-size);
|
|
335
342
|
}
|
|
336
343
|
.page-panel__header-left {
|
|
337
|
-
display:
|
|
344
|
+
display: grid;
|
|
345
|
+
grid-template-columns: minmax(min-content, max(0px, var(--_page-panel--content-start) - var(--_page-panel--header-padding-inline))) minmax(0, 1fr);
|
|
338
346
|
align-self: stretch;
|
|
339
347
|
align-items: center;
|
|
340
348
|
min-width: 0;
|
|
@@ -346,12 +354,29 @@ to min / max; Enter / Space toggle collapsed.
|
|
|
346
354
|
.page-panel__header-left:hover {
|
|
347
355
|
--_scroll-btn-opacity: 1;
|
|
348
356
|
}
|
|
357
|
+
.page-panel__header-collapse {
|
|
358
|
+
grid-column: 1;
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
padding-inline-end: 1rem;
|
|
362
|
+
}
|
|
363
|
+
.page-panel__header-track-wrap {
|
|
364
|
+
grid-column: 2;
|
|
365
|
+
position: relative;
|
|
366
|
+
display: flex;
|
|
367
|
+
align-self: stretch;
|
|
368
|
+
align-items: stretch;
|
|
369
|
+
min-width: 0;
|
|
370
|
+
}
|
|
349
371
|
.page-panel__header-left-track {
|
|
350
372
|
--_mask-size: 1.5rem;
|
|
351
373
|
display: flex;
|
|
374
|
+
align-self: stretch;
|
|
352
375
|
align-items: center;
|
|
353
376
|
gap: 1rem;
|
|
354
377
|
min-width: 0;
|
|
378
|
+
padding-block-end: 1px;
|
|
379
|
+
margin-block-end: -1px;
|
|
355
380
|
overflow-x: auto;
|
|
356
381
|
scrollbar-width: none;
|
|
357
382
|
}
|
|
@@ -369,13 +394,12 @@ to min / max; Enter / Space toggle collapsed.
|
|
|
369
394
|
}
|
|
370
395
|
.page-panel__header-scroll-btn {
|
|
371
396
|
position: absolute;
|
|
372
|
-
|
|
373
|
-
bottom: 0;
|
|
397
|
+
inset-block: 0;
|
|
374
398
|
z-index: 1;
|
|
375
399
|
display: flex;
|
|
376
400
|
align-items: center;
|
|
377
401
|
justify-content: center;
|
|
378
|
-
padding: 0
|
|
402
|
+
padding-inline: 0.25rem;
|
|
379
403
|
background: transparent;
|
|
380
404
|
backdrop-filter: blur(2px);
|
|
381
405
|
border: none;
|
|
@@ -388,13 +412,13 @@ to min / max; Enter / Space toggle collapsed.
|
|
|
388
412
|
color: var(--sc-kit--color--text--primary);
|
|
389
413
|
}
|
|
390
414
|
.page-panel__header-scroll-btn--back {
|
|
391
|
-
|
|
392
|
-
padding-
|
|
415
|
+
inset-inline-start: 0;
|
|
416
|
+
padding-inline-end: 0.625rem;
|
|
393
417
|
background: linear-gradient(to right, var(--_page-panel--header-background) 60%, transparent);
|
|
394
418
|
}
|
|
395
419
|
.page-panel__header-scroll-btn--forward {
|
|
396
|
-
|
|
397
|
-
padding-
|
|
420
|
+
inset-inline-end: 0;
|
|
421
|
+
padding-inline-start: 0.625rem;
|
|
398
422
|
background: linear-gradient(to left, var(--_page-panel--header-background) 60%, transparent);
|
|
399
423
|
}
|
|
400
424
|
.page-panel__header-center {
|
|
@@ -5,22 +5,21 @@ const isActive = (tab) => (compare ? compare(value, tab.value) : value === tab.v
|
|
|
5
5
|
|
|
6
6
|
<div class="tabs tabs--{variant}" role="tablist">
|
|
7
7
|
{#each tabs as tab, i (i)}
|
|
8
|
-
<TabItem tab={tab} variant={variant} size={size} active={isActive(tab)} on={{ select: on?.change
|
|
8
|
+
<TabItem tab={tab} variant={variant} size={size} active={isActive(tab)} on={{ select: on?.change }} />
|
|
9
9
|
{/each}
|
|
10
10
|
</div>
|
|
11
11
|
|
|
12
12
|
<!--
|
|
13
13
|
@component
|
|
14
14
|
Tabs — header strip for switching between content panels. Panel rendering is consumer-controlled
|
|
15
|
-
(switch on the active `value`).
|
|
15
|
+
(switch on the active `value`). Two visual variants:
|
|
16
16
|
|
|
17
17
|
- **`default`**: underline — a 2px accent bar travels under the active tab; the strip has a bottom border.
|
|
18
18
|
- **`pills`**: filled — the active tab lifts as a card on an inset tray.
|
|
19
|
-
- **`document`**: browser-style tabs — flush, rounded-top; the active tab background matches the canvas so it reads as continuous with the content below.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
Each tab carries an optional `disabled` flag (non-interactive). It's a `tablist` — for navigation
|
|
21
|
+
between panels, not single-choice form input. Closable tabs are an editor concern, not a panel
|
|
22
|
+
switcher's, so they are not part of this component.
|
|
24
23
|
|
|
25
24
|
### CSS Custom Properties
|
|
26
25
|
| Property | Description | Default |
|
|
@@ -32,16 +31,13 @@ form input — use `SegmentedControl` for the latter (it's a `radiogroup`; this
|
|
|
32
31
|
| `--sc-kit--tabs--radius` | Tab radius (pills) / tray radius | `--sc-kit--radius--sm` / `--sc-kit--radius--md` |
|
|
33
32
|
| `--sc-kit--tabs--color` | Default tab text color | `--sc-kit--color--text--secondary` |
|
|
34
33
|
| `--sc-kit--tabs--color--hover` | Hover text color | `--sc-kit--color--text--primary` |
|
|
35
|
-
| `--sc-kit--tabs--color--active` | Active tab text color | default: `--sc-kit--color--accent`, pills
|
|
34
|
+
| `--sc-kit--tabs--color--active` | Active tab text color | default: `--sc-kit--color--accent`, pills: `--sc-kit--color--text--primary` |
|
|
36
35
|
| `--sc-kit--tabs--color--disabled` | Disabled tab text color | `--sc-kit--color--text--muted` |
|
|
37
36
|
| `--sc-kit--tabs--accent` | Underline bar color (default variant) | `--sc-kit--color--accent` |
|
|
38
37
|
| `--sc-kit--tabs--border-color` | Bottom border color (default variant) | `--sc-kit--color--border` |
|
|
39
38
|
| `--sc-kit--tabs--pills--background` | Inset tray background (pills variant) | `--sc-kit--color--bg--active` |
|
|
40
39
|
| `--sc-kit--tabs--pills--background--active` | Active tab background (pills variant) | `--sc-kit--color--bg--panel` |
|
|
41
40
|
| `--sc-kit--tabs--pills--shadow--active` | Active tab lift shadow (pills variant) | `--sc-kit--shadow--sm` |
|
|
42
|
-
| `--sc-kit--tabs--document--background` | Inactive tab background (document variant) | `neutral-200` / `dark-500` |
|
|
43
|
-
| `--sc-kit--tabs--document--background--active` | Active tab background (document variant) — matches the canvas | `--sc-kit--color--bg--app` |
|
|
44
|
-
| `--sc-kit--tabs--document--background--hover` | Inactive tab hover background (document variant) | `--sc-kit--color--bg--hover` |
|
|
45
41
|
-->
|
|
46
42
|
|
|
47
43
|
<style>.tabs {
|
|
@@ -60,8 +56,4 @@ form input — use `SegmentedControl` for the latter (it's a `radiogroup`; this
|
|
|
60
56
|
padding: 0.25rem;
|
|
61
57
|
background: var(--_tabs--pills-background);
|
|
62
58
|
border-radius: var(--sc-kit--radius--md);
|
|
63
|
-
}
|
|
64
|
-
.tabs--document {
|
|
65
|
-
gap: 0;
|
|
66
|
-
align-items: flex-end;
|
|
67
59
|
}</style>
|
|
@@ -12,8 +12,6 @@ declare function $$render<T>(): {
|
|
|
12
12
|
compare?: (a: T, b: T) => boolean;
|
|
13
13
|
on?: {
|
|
14
14
|
change?: (value: T) => void;
|
|
15
|
-
/** Providing this renders a close affordance on each non-disabled tab (works in any variant). */
|
|
16
|
-
close?: (value: T) => void;
|
|
17
15
|
};
|
|
18
16
|
};
|
|
19
17
|
exports: {};
|
|
@@ -37,15 +35,14 @@ interface $$IsomorphicComponent {
|
|
|
37
35
|
}
|
|
38
36
|
/**
|
|
39
37
|
* Tabs — header strip for switching between content panels. Panel rendering is consumer-controlled
|
|
40
|
-
* (switch on the active `value`).
|
|
38
|
+
* (switch on the active `value`). Two visual variants:
|
|
41
39
|
*
|
|
42
40
|
* - **`default`**: underline — a 2px accent bar travels under the active tab; the strip has a bottom border.
|
|
43
41
|
* - **`pills`**: filled — the active tab lifts as a card on an inset tray.
|
|
44
|
-
* - **`document`**: browser-style tabs — flush, rounded-top; the active tab background matches the canvas so it reads as continuous with the content below.
|
|
45
42
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
43
|
+
* Each tab carries an optional `disabled` flag (non-interactive). It's a `tablist` — for navigation
|
|
44
|
+
* between panels, not single-choice form input. Closable tabs are an editor concern, not a panel
|
|
45
|
+
* switcher's, so they are not part of this component.
|
|
49
46
|
*
|
|
50
47
|
* ### CSS Custom Properties
|
|
51
48
|
* | Property | Description | Default |
|
|
@@ -57,16 +54,13 @@ interface $$IsomorphicComponent {
|
|
|
57
54
|
* | `--sc-kit--tabs--radius` | Tab radius (pills) / tray radius | `--sc-kit--radius--sm` / `--sc-kit--radius--md` |
|
|
58
55
|
* | `--sc-kit--tabs--color` | Default tab text color | `--sc-kit--color--text--secondary` |
|
|
59
56
|
* | `--sc-kit--tabs--color--hover` | Hover text color | `--sc-kit--color--text--primary` |
|
|
60
|
-
* | `--sc-kit--tabs--color--active` | Active tab text color | default: `--sc-kit--color--accent`, pills
|
|
57
|
+
* | `--sc-kit--tabs--color--active` | Active tab text color | default: `--sc-kit--color--accent`, pills: `--sc-kit--color--text--primary` |
|
|
61
58
|
* | `--sc-kit--tabs--color--disabled` | Disabled tab text color | `--sc-kit--color--text--muted` |
|
|
62
59
|
* | `--sc-kit--tabs--accent` | Underline bar color (default variant) | `--sc-kit--color--accent` |
|
|
63
60
|
* | `--sc-kit--tabs--border-color` | Bottom border color (default variant) | `--sc-kit--color--border` |
|
|
64
61
|
* | `--sc-kit--tabs--pills--background` | Inset tray background (pills variant) | `--sc-kit--color--bg--active` |
|
|
65
62
|
* | `--sc-kit--tabs--pills--background--active` | Active tab background (pills variant) | `--sc-kit--color--bg--panel` |
|
|
66
63
|
* | `--sc-kit--tabs--pills--shadow--active` | Active tab lift shadow (pills variant) | `--sc-kit--shadow--sm` |
|
|
67
|
-
* | `--sc-kit--tabs--document--background` | Inactive tab background (document variant) | `neutral-200` / `dark-500` |
|
|
68
|
-
* | `--sc-kit--tabs--document--background--active` | Active tab background (document variant) — matches the canvas | `--sc-kit--color--bg--app` |
|
|
69
|
-
* | `--sc-kit--tabs--document--background--hover` | Inactive tab hover background (document variant) | `--sc-kit--color--bg--hover` |
|
|
70
64
|
*/
|
|
71
65
|
declare const Cmp: $$IsomorphicComponent;
|
|
72
66
|
type Cmp<T> = InstanceType<typeof Cmp<T>>;
|
package/dist/ui/tabs/tab.svelte
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
|
-
<script lang="ts" generics="T">
|
|
2
|
-
import { TabsLocalization } from './tabs-localization';
|
|
3
|
-
import IconDismiss from '@fluentui/svg-icons/icons/dismiss_20_regular.svg?raw';
|
|
4
|
-
const { tab, variant, size, active, on } = $props();
|
|
5
|
-
const localization = new TabsLocalization();
|
|
6
|
-
const closable = $derived(!!on?.close && !tab.disabled);
|
|
1
|
+
<script lang="ts" generics="T">const { tab, variant, size, active, on } = $props();
|
|
7
2
|
const handleClick = () => {
|
|
8
3
|
if (tab.disabled || active) {
|
|
9
4
|
return;
|
|
10
5
|
}
|
|
11
6
|
on?.select?.(tab.value);
|
|
12
7
|
};
|
|
13
|
-
|
|
14
|
-
event.stopPropagation();
|
|
15
|
-
on?.close?.(tab.value);
|
|
16
|
-
};
|
|
17
|
-
const handleCloseKeydown = (event) => {
|
|
18
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
19
|
-
event.preventDefault();
|
|
20
|
-
event.stopPropagation();
|
|
21
|
-
on?.close?.(tab.value);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
8
|
+
export {};
|
|
24
9
|
</script>
|
|
25
10
|
|
|
26
11
|
<button
|
|
@@ -30,25 +15,18 @@ const handleCloseKeydown = (event) => {
|
|
|
30
15
|
class:tab--hoverable={!active && !tab.disabled}
|
|
31
16
|
class:tab--default-active={variant === 'default' && active}
|
|
32
17
|
class:tab--pills-active={variant === 'pills' && active}
|
|
33
|
-
class:tab--document-active={variant === 'document' && active}
|
|
34
|
-
class:tab--document-hoverable={variant === 'document' && !active && !tab.disabled}
|
|
35
18
|
role="tab"
|
|
36
19
|
aria-selected={active}
|
|
37
20
|
disabled={tab.disabled}
|
|
38
21
|
onclick={handleClick}>
|
|
39
22
|
<span class="tab__label">{tab.label}</span>
|
|
40
|
-
{#if closable}
|
|
41
|
-
<span class="tab__close" role="button" tabindex="0" aria-label={localization.close} onclick={handleClose} onkeydown={handleCloseKeydown}>
|
|
42
|
-
<IconSlot icon={IconDismiss} fallbacks={{ size: 'sm' }} />
|
|
43
|
-
</span>
|
|
44
|
-
{/if}
|
|
45
23
|
</button>
|
|
46
24
|
|
|
47
25
|
<style>.tab {
|
|
48
|
-
--_tab--height: var(--sc-kit--tabs--height,
|
|
49
|
-
--_tab--font-size: var(--sc-kit--tabs--font-size, var(--
|
|
50
|
-
--_tab--font-weight: var(--sc-kit--tabs--font-weight, var(--
|
|
51
|
-
--_tab--padding-inline: var(--sc-kit--tabs--padding-inline,
|
|
26
|
+
--_tab--height: var(--sc-kit--tabs--height, var(--_tab--size-height));
|
|
27
|
+
--_tab--font-size: var(--sc-kit--tabs--font-size, var(--_tab--size-font-size));
|
|
28
|
+
--_tab--font-weight: var(--sc-kit--tabs--font-weight, var(--_tab--size-font-weight));
|
|
29
|
+
--_tab--padding-inline: var(--sc-kit--tabs--padding-inline, var(--_tab--size-padding-inline));
|
|
52
30
|
--_tab--radius: var(--sc-kit--tabs--radius, var(--sc-kit--radius--sm));
|
|
53
31
|
--_tab--color: var(--sc-kit--tabs--color, var(--sc-kit--color--text--secondary));
|
|
54
32
|
--_tab--color-hover: var(--sc-kit--tabs--color--hover, var(--sc-kit--color--text--primary));
|
|
@@ -57,9 +35,6 @@ const handleCloseKeydown = (event) => {
|
|
|
57
35
|
--_tab--accent: var(--sc-kit--tabs--accent, var(--sc-kit--color--accent));
|
|
58
36
|
--_tab--pills-background-active: var(--sc-kit--tabs--pills--background--active, var(--sc-kit--color--bg--panel));
|
|
59
37
|
--_tab--pills-shadow-active: var(--sc-kit--tabs--pills--shadow--active, var(--sc-kit--shadow--sm));
|
|
60
|
-
--_tab--document-background: var(--sc-kit--tabs--document--background, light-dark(#e5e7eb, #272727));
|
|
61
|
-
--_tab--document-background-active: var(--sc-kit--tabs--document--background--active, var(--sc-kit--color--bg--app));
|
|
62
|
-
--_tab--document-background-hover: var(--sc-kit--tabs--document--background--hover, var(--sc-kit--color--bg--hover));
|
|
63
38
|
position: relative;
|
|
64
39
|
display: inline-flex;
|
|
65
40
|
align-items: center;
|
|
@@ -80,16 +55,16 @@ const handleCloseKeydown = (event) => {
|
|
|
80
55
|
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default), background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
81
56
|
}
|
|
82
57
|
.tab--sm {
|
|
83
|
-
--
|
|
84
|
-
--
|
|
85
|
-
--
|
|
86
|
-
--
|
|
58
|
+
--_tab--size-height: 1.75rem;
|
|
59
|
+
--_tab--size-font-size: var(--sc-kit--font-size--sm);
|
|
60
|
+
--_tab--size-font-weight: var(--sc-kit--font-weight--regular);
|
|
61
|
+
--_tab--size-padding-inline: 0.625rem;
|
|
87
62
|
}
|
|
88
63
|
.tab--md {
|
|
89
|
-
--
|
|
90
|
-
--
|
|
91
|
-
--
|
|
92
|
-
--
|
|
64
|
+
--_tab--size-height: 2.25rem;
|
|
65
|
+
--_tab--size-font-size: var(--sc-kit--font-size--md);
|
|
66
|
+
--_tab--size-font-weight: var(--sc-kit--font-weight--medium);
|
|
67
|
+
--_tab--size-padding-inline: 0.875rem;
|
|
93
68
|
}
|
|
94
69
|
.tab--hoverable:hover {
|
|
95
70
|
color: var(--_tab--color-hover);
|
|
@@ -123,23 +98,6 @@ const handleCloseKeydown = (event) => {
|
|
|
123
98
|
background: var(--_tab--pills-background-active);
|
|
124
99
|
box-shadow: var(--_tab--pills-shadow-active);
|
|
125
100
|
}
|
|
126
|
-
.tab--document {
|
|
127
|
-
--_tab--color-active-default: var(--sc-kit--color--text--primary);
|
|
128
|
-
--_tab--height: 2rem;
|
|
129
|
-
--_tab--padding-inline: 0.75rem;
|
|
130
|
-
justify-content: flex-start;
|
|
131
|
-
gap: 0.5rem;
|
|
132
|
-
min-width: 7rem;
|
|
133
|
-
background: var(--_tab--document-background);
|
|
134
|
-
border-radius: 0.25rem 0.5rem 0 0;
|
|
135
|
-
}
|
|
136
|
-
.tab--document-hoverable:hover {
|
|
137
|
-
background: var(--_tab--document-background-hover);
|
|
138
|
-
}
|
|
139
|
-
.tab--document-active {
|
|
140
|
-
color: var(--_tab--color-active);
|
|
141
|
-
background: var(--_tab--document-background-active);
|
|
142
|
-
}
|
|
143
101
|
.tab:focus-visible {
|
|
144
102
|
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
145
103
|
outline-offset: 2px;
|
|
@@ -147,23 +105,4 @@ const handleCloseKeydown = (event) => {
|
|
|
147
105
|
.tab__label {
|
|
148
106
|
display: inline-block;
|
|
149
107
|
max-width: 100%;
|
|
150
|
-
}
|
|
151
|
-
.tab__close {
|
|
152
|
-
display: inline-flex;
|
|
153
|
-
align-items: center;
|
|
154
|
-
justify-content: center;
|
|
155
|
-
flex-shrink: 0;
|
|
156
|
-
margin-left: auto;
|
|
157
|
-
padding: 0.125rem;
|
|
158
|
-
color: var(--sc-kit--color--text--secondary);
|
|
159
|
-
border-radius: var(--sc-kit--radius--sm);
|
|
160
|
-
cursor: pointer;
|
|
161
|
-
transition: color var(--sc-kit--duration--base) var(--sc-kit--ease--default), background-color var(--sc-kit--duration--base) var(--sc-kit--ease--default);
|
|
162
|
-
}
|
|
163
|
-
.tab__close:hover {
|
|
164
|
-
color: var(--sc-kit--color--text--primary);
|
|
165
|
-
}
|
|
166
|
-
.tab__close:focus-visible {
|
|
167
|
-
outline: 2px solid var(--sc-kit--color--border--focus);
|
|
168
|
-
outline-offset: 2px;
|
|
169
108
|
}</style>
|
package/dist/ui/tabs/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamscloud/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"author": "StreamsCloud",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -163,6 +163,10 @@
|
|
|
163
163
|
"types": "./dist/ui/divider/index.d.ts",
|
|
164
164
|
"svelte": "./dist/ui/divider/index.js"
|
|
165
165
|
},
|
|
166
|
+
"./ui/document-tabs": {
|
|
167
|
+
"types": "./dist/ui/document-tabs/index.d.ts",
|
|
168
|
+
"svelte": "./dist/ui/document-tabs/index.js"
|
|
169
|
+
},
|
|
166
170
|
"./ui/drag-and-drop": {
|
|
167
171
|
"types": "./dist/ui/drag-and-drop/index.d.ts",
|
|
168
172
|
"svelte": "./dist/ui/drag-and-drop/index.js"
|