svelte-fluentui 1.0.0-rc07 → 1.0.0-rc15
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 +544 -0
- package/dist/actions/portal.d.ts +16 -0
- package/dist/actions/portal.js +24 -0
- package/dist/assets/styles/components.scss +13 -0
- package/dist/assets/styles/fluent-blazor-compat.scss +13 -9
- package/dist/assets/styles/theme.scss +14 -0
- package/dist/components/Autocomplete.svelte +945 -909
- package/dist/components/Autocomplete.svelte.d.ts +1 -0
- package/dist/components/Calendar.svelte +22 -5
- package/dist/components/Calendar.svelte.d.ts +6 -0
- package/dist/components/Combobox.svelte +300 -259
- package/dist/components/Combobox.svelte.d.ts +1 -0
- package/dist/components/ContextMenu.svelte +592 -0
- package/dist/components/ContextMenu.svelte.d.ts +30 -0
- package/dist/components/DatePicker.svelte +93 -30
- package/dist/components/DatePicker.svelte.d.ts +15 -1
- package/dist/components/Dialog.svelte +213 -37
- package/dist/components/Dialog.svelte.d.ts +18 -0
- package/dist/components/GridCellEditor.svelte +1005 -1015
- package/dist/components/MenuButton.svelte +312 -0
- package/dist/components/MenuButton.svelte.d.ts +61 -0
- package/dist/components/PositioningRegion.svelte +13 -4
- package/dist/components/PositioningRegion.svelte.d.ts +3 -0
- package/dist/components/QuickGrid.svelte +552 -70
- package/dist/components/QuickGrid.svelte.d.ts +99 -9
- package/dist/components/Select.svelte +21 -0
- package/dist/components/SiteSettings.svelte +1 -1
- package/dist/components/Slider.svelte +101 -7
- package/dist/components/Slider.svelte.d.ts +22 -2
- package/dist/components/Tab.svelte +68 -80
- package/dist/components/Tab.svelte.d.ts +2 -3
- package/dist/components/Tabs.svelte +1226 -55
- package/dist/components/Tabs.svelte.d.ts +56 -1
- package/dist/components/TextField.svelte +17 -2
- package/dist/components/TextField.svelte.d.ts +2 -0
- package/dist/components/Textarea.svelte +1 -1
- package/dist/components/TimePicker.svelte +124 -30
- package/dist/components/TimePicker.svelte.d.ts +18 -1
- package/dist/components/Tooltip.svelte +435 -31
- package/dist/components/Tooltip.svelte.d.ts +8 -4
- package/dist/components/layout/Grid.svelte +20 -2
- package/dist/components/layout/Grid.svelte.d.ts +2 -0
- package/dist/components/layout/TopNav.svelte +3 -3
- package/dist/fluent-ui/calendar/fluent-calendar-extended.d.ts +1 -1
- package/dist/fluent-ui/calendar/fluent-calendar-extended.js +5 -2
- package/dist/global.d.ts +11 -0
- package/dist/global.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/main.css +29 -12
- package/dist/main.css.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/dist/vite/vite-plugin-fluentui-icons.js +4 -6
- package/package.json +8 -5
package/README.md
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# Svelte FluentUI
|
|
2
|
+
|
|
3
|
+
A comprehensive Svelte wrapper library for Microsoft FluentUI web components (v2.6.x), providing a seamless way to use FluentUI components in Svelte applications.
|
|
4
|
+
|
|
5
|
+
## What's New in v1.0.0-rc15
|
|
6
|
+
|
|
7
|
+
- **`QuickGrid` tree mode** — pass `treePathMember="path"` and mark one column with `isTree: true` to render hierarchical data with indentation + expand/collapse. Supports PostgreSQL ltree (`"1.2.3"`), POSIX (`"/1/2/3"`), and Windows (`"C:\foo\bar"`) path styles with auto-detection. Optional `treeLevelMember` / `treeParentMember` skip path parsing when those values are pre-computed in the database. `treeDataSorted` skips internal sort. `expandedPaths` is `$bindable`. `defaultExpandDepth` is interpreted relative to the dataset's shallowest level so subtree views always show their roots. Filter is ancestor-aware. Optional `treeDoubleClickBehavior="toggle"` lets users smash the row instead of aiming at the chevron
|
|
8
|
+
- **`QuickGrid` per-column `filter` callback** — `(filterValue: string, row: T) => boolean | null` replaces the built-in substring match for one column. Use for numeric ranges, date ranges, regex, multi-field search. Returning `null` signals "input is syntactically incomplete" — the grid then ignores the filter (all rows pass) AND adds a `.invalid` class to the input so the user gets a red border instead of an empty grid
|
|
9
|
+
- **`QuickGrid` grid-level `onfilterchange`** — `(filters: Record<string, string>) => void` for server-side filtering. When set, internal filtering is fully bypassed; the grid still renders the inputs, fires this on every keystroke, and trusts the caller to update `items`
|
|
10
|
+
- **`QuickGrid` `idMember` prop** — stable row identity. Drafts and invalid-cell markers now key by this value (or `treePathMember` in tree mode, or displayed-row index as a last-resort fallback that triggers a one-shot console warning) so they survive pagination, filter, sort, and tree expand/collapse re-orderings
|
|
11
|
+
- **`QuickGrid` `columnMinWidth` prop** — default `min-width` for any column without its own. Stops content-sized columns from collapsing too far when paired with `fillerColumn`
|
|
12
|
+
- **`QuickGrid` `fillerColumn` actually absorbs leftover space now** — was `width: auto` (a no-op for absorption), now `width: 100%`. Affects every demo using `fillerColumn`, not just tree
|
|
13
|
+
- **BREAKING — `QuickGrid` `invalidCells` shape: `rowIndex` → `rowKey`** — bindable `invalidCells: CellValidationState[]` now carries `rowKey: string` instead of `rowIndex: number`, matching the new stable-id keying. Migration: callers binding `invalidCells` need to read `c.rowKey` instead of `c.rowIndex`. The `onvalidationerror` callback's `detail.rowIndex` is unchanged
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- 🎨 **Complete FluentUI Component Set** - Wraps all major FluentUI web components
|
|
18
|
+
- 🔧 **TypeScript Support** - Full type definitions for all components
|
|
19
|
+
- 📱 **Responsive Design** - Built-in responsive layout components (incl. responsive `Tabs` with scroll/wrap/menu modes)
|
|
20
|
+
- 🎯 **Svelte 5 Compatible** - Works with the latest Svelte features
|
|
21
|
+
- 🎨 **SCSS & Tailwind CSS** - Flexible styling options
|
|
22
|
+
- 📦 **Tree-shakeable** - Import only what you need
|
|
23
|
+
- ✨ **FluentUI Blazor Inspired** - Advanced components like Calendar, DatePicker, TimePicker, InputFile, Autocomplete
|
|
24
|
+
- 💾 **Navigation Persistence** - Sidebar menu state persists across page reloads with localStorage
|
|
25
|
+
- 🎯 **Active Route Highlighting** - Current page automatically highlighted in navigation
|
|
26
|
+
- 🔔 **Toast Notifications** - Programmatic toast service with multiple positions and auto-dismiss
|
|
27
|
+
- 🗂 **Centralized z-index scale + portaled overlays** - Dialogs, popovers and tooltips render through `document.body` so they escape ancestor stacking contexts; every overlay uses a shared `--fluent-z-*` token scale with baked-in fallbacks
|
|
28
|
+
- 🪟 **`portal` action** - Utility action exported for consumer-built overlays that need to escape parent stacking contexts
|
|
29
|
+
- 🔌 **Global runtime API** - `window.components["svelte-fluentui"].version()` exposed at runtime for introspection (mirrors the pattern used by sister packages)
|
|
30
|
+
|
|
31
|
+
## Highlights
|
|
32
|
+
|
|
33
|
+
- **Unified form label styling** - TextField, Select, Autocomplete, Radio, RadioGroup and Textarea all share a single canonical `.fluent-label` class so labels look identical across every field
|
|
34
|
+
- **Autocomplete height parity** - The custom Autocomplete now matches FluentUI text field / select height (32px) using the same `--base-height-multiplier × --design-unit` tokens
|
|
35
|
+
- **Ctrl+Space in Autocomplete** - Press `Ctrl+Space` (or `⌘+Space` on Mac) to force-open the dropdown with all available options
|
|
36
|
+
- **Dialog** - Full Fluent-style layout: `title` / `header` slot, right-aligned footer, `primaryAction` / `secondaryAction` shorthand, or custom `footer` snippet. Portaled to `<body>` so it escapes ancestor stacking contexts
|
|
37
|
+
- **DatePicker & TimePicker** - Calendar/clock popups anchored correctly to their inputs, natural-width popups (no more stretch-to-input-width), outside-click + `Escape` dismissal, `minDate`/`maxDate` / `minTime`/`maxTime`, `firstDayOfWeek`, `disabledDateFunc`, `autoClose`, bindable `open`
|
|
38
|
+
- **Calendar** - `firstDayOfWeek` override, `selectableDates` allow-list, `onPickerMonthChange` callback
|
|
39
|
+
- **Tabs** - Pure-Svelte implementation (no more shadow-DOM wrestling) with animated full-tab-width active indicator. `responsive="scroll" | "wrap" | "menu"` — `scroll` auto-shows `‹ ›` arrow buttons on overflow; `menu` collapses overflow into a `⋯` dropdown that swaps the picked tab into the strip on select, with ellipsis truncation on the borderline tab. Full keyboard nav, vertical orientation with `stripWidth` sidebar sizing + auto-ellipsis labels, swipe-to-navigate on the panels (opt out via `swipe={false}`), per-tab `title` tooltips, and `justify` for equal-width stretching
|
|
40
|
+
- **MenuButton** - Button that opens a dropdown menu on click instead of a one-off `onclick`. Takes `items: MenuButtonItem[]` (label, icon, disabled, visible, danger, dividerBefore, onclick), forwards button props, exposes bindable `open`. Positioned with Floating UI — auto-flips when near viewport edges, shifts to stay in bounds, and caps height with internal scroll when tight. `position="bottom" | "top"` controls the preferred side
|
|
41
|
+
- **ContextMenu** - Standalone right-click menu that opens at the cursor. Wrap any element with `<ContextMenu items={…}>…</ContextMenu>` and right-click inside opens a Floating-UI-positioned menu. Supports **inline expandable sections** (`children` + `expandable: true`, with a rotating chevron) for sidebar-style nav menus *and* **side-opening submenus** (nested `children`) with hover-intent delays, auto-flip away from the viewport edge, and arbitrary nesting. `offsetMenuX` / `offsetMenuY` push the menu away from the cursor so the click position doesn't land on the first item. Closes on scroll, click-outside, or Escape (Escape pops submenus one level at a time)
|
|
42
|
+
- **Slider** - Proper wrapper with `bind:value`, `min`/`max`/`step`, `orientation`, `onchange`/`oninput`, labels
|
|
43
|
+
- **Navigation Persistence** - Sidebar menu state automatically saved to localStorage and restored on page reload
|
|
44
|
+
- **Active Route Highlighting** - Current page is automatically highlighted in the navigation menu
|
|
45
|
+
- **Toast Service** - Programmatic notifications with `toast.success()`, `toast.error()`, etc. - 6 positions, progress bars, auto-dismiss
|
|
46
|
+
- **InputFile** - Drag-and-drop file upload with validation and progress tracking
|
|
47
|
+
- **Autocomplete** - Multiple selection with tag/chip display, async search with AbortSignal, initial options, Ctrl+Space to show all
|
|
48
|
+
- **QuickGrid** - Advanced data grid with sorting, filtering, pagination, editable rows, row toolbar, context menu, tree mode with ltree-style paths, per-column custom filter predicates, and stable row identity via `idMember`
|
|
49
|
+
- **Three-State Checkbox** - Checkbox with indeterminate state support
|
|
50
|
+
- **Responsive Layout** - Complete layout system with Grid, Stack, and responsive components
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install svelte-fluentui
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick Start
|
|
59
|
+
|
|
60
|
+
```svelte
|
|
61
|
+
<script>
|
|
62
|
+
import { Button, TextField, Card } from 'svelte-fluentui'
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<Card>
|
|
66
|
+
<TextField placeholder="Enter your name" />
|
|
67
|
+
<Button appearance="accent">Submit</Button>
|
|
68
|
+
</Card>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Available Components
|
|
72
|
+
|
|
73
|
+
### Form Controls
|
|
74
|
+
- `Button` - Various button styles and appearances
|
|
75
|
+
- `TextField` - Text input with validation
|
|
76
|
+
- `NumberField` - Numeric input control
|
|
77
|
+
- `Textarea` - Multi-line text input
|
|
78
|
+
- `Checkbox` - Checkbox input with three-state support
|
|
79
|
+
- `Radio` / `RadioGroup` - Radio button controls
|
|
80
|
+
- `Switch` - Toggle switch
|
|
81
|
+
- `Select` - Dropdown selection
|
|
82
|
+
- `Combobox` - Searchable dropdown
|
|
83
|
+
- `Autocomplete` - Multiple selection with tags/chips (inspired by FluentUI Blazor)
|
|
84
|
+
- `Slider` - Range slider control
|
|
85
|
+
- `Search` - Search input field
|
|
86
|
+
- `DatePicker` - Date selection with calendar popup (inspired by FluentUI Blazor)
|
|
87
|
+
- `TimePicker` - Time selection with hour/minute/second picker (inspired by FluentUI Blazor)
|
|
88
|
+
- `InputFile` - File upload with drag-drop and progress tracking (inspired by FluentUI Blazor)
|
|
89
|
+
|
|
90
|
+
### Data Display
|
|
91
|
+
- `DataGrid` / `DataGridRow` / `DataGridCell` - Data table components
|
|
92
|
+
- `QuickGrid` - Advanced data grid with sorting, filtering, pagination, inline editing, tree mode, and custom filter predicates
|
|
93
|
+
- `Card` - Content container
|
|
94
|
+
- `Badge` - Status indicators
|
|
95
|
+
- `ProgressBar` - Progress indication
|
|
96
|
+
- `Tooltip` - Contextual information
|
|
97
|
+
- `Calendar` - Date picker and calendar
|
|
98
|
+
- `Paginator` - Pagination control
|
|
99
|
+
|
|
100
|
+
### Navigation
|
|
101
|
+
- `Tabs` / `Tab` / `TabPanel` - Tab navigation
|
|
102
|
+
- `Breadcrumb` / `BreadcrumbItem` - Breadcrumb navigation
|
|
103
|
+
- `Menu` / `MenuItem` - Context menus
|
|
104
|
+
- `AppBar` / `AppBarItem` - Application bar
|
|
105
|
+
- `NavMenu` / `NavItem` / `NavLink` - Navigation components
|
|
106
|
+
- `Anchor` - Link component
|
|
107
|
+
|
|
108
|
+
### Layout
|
|
109
|
+
- `Stack` - Flexible layout container
|
|
110
|
+
- `Layout` - Page layout wrapper
|
|
111
|
+
- `Header` / `Footer` / `BodyContent` - Layout sections
|
|
112
|
+
- `Spacer` - Spacing utility
|
|
113
|
+
- `Divider` - Visual separator
|
|
114
|
+
|
|
115
|
+
### Feedback
|
|
116
|
+
- `Dialog` - Modal dialogs
|
|
117
|
+
- `Toast` - Declarative notification messages
|
|
118
|
+
- `ToastContainer` + `toast` - Programmatic toast service (success, error, warning, info)
|
|
119
|
+
- `Accordion` / `AccordionItem` - Collapsible content
|
|
120
|
+
|
|
121
|
+
### Utilities
|
|
122
|
+
- `Listbox` / `Option` - List selection
|
|
123
|
+
- `Tree` / `TreeItem` - Hierarchical data
|
|
124
|
+
- `Toolbar` - Action toolbars
|
|
125
|
+
|
|
126
|
+
## Usage Examples
|
|
127
|
+
|
|
128
|
+
### Form with Validation
|
|
129
|
+
```svelte
|
|
130
|
+
<script>
|
|
131
|
+
import { TextField, Button, Stack } from 'svelte-fluentui'
|
|
132
|
+
|
|
133
|
+
let email = ''
|
|
134
|
+
let password = ''
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<Stack orientation="vertical" gap="16">
|
|
138
|
+
<TextField
|
|
139
|
+
bind:value={email}
|
|
140
|
+
type="email"
|
|
141
|
+
placeholder="Enter email"
|
|
142
|
+
required
|
|
143
|
+
/>
|
|
144
|
+
<TextField
|
|
145
|
+
bind:value={password}
|
|
146
|
+
type="password"
|
|
147
|
+
placeholder="Enter password"
|
|
148
|
+
required
|
|
149
|
+
/>
|
|
150
|
+
<Button appearance="accent">Sign In</Button>
|
|
151
|
+
</Stack>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Data Grid
|
|
155
|
+
```svelte
|
|
156
|
+
<script>
|
|
157
|
+
import { DataGrid, DataGridRow, DataGridCell } from 'svelte-fluentui'
|
|
158
|
+
|
|
159
|
+
const users = [
|
|
160
|
+
{ name: 'Alice', email: 'alice@example.com', role: 'Admin' },
|
|
161
|
+
{ name: 'Bob', email: 'bob@example.com', role: 'User' }
|
|
162
|
+
]
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<DataGrid>
|
|
166
|
+
{#each users as user}
|
|
167
|
+
<DataGridRow>
|
|
168
|
+
<DataGridCell>{user.name}</DataGridCell>
|
|
169
|
+
<DataGridCell>{user.email}</DataGridCell>
|
|
170
|
+
<DataGridCell>{user.role}</DataGridCell>
|
|
171
|
+
</DataGridRow>
|
|
172
|
+
{/each}
|
|
173
|
+
</DataGrid>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### QuickGrid with Sorting & Filtering
|
|
177
|
+
```svelte
|
|
178
|
+
<script lang="ts">
|
|
179
|
+
import { QuickGrid } from 'svelte-fluentui'
|
|
180
|
+
|
|
181
|
+
type User = {
|
|
182
|
+
id: number
|
|
183
|
+
name: string
|
|
184
|
+
email: string
|
|
185
|
+
role: string
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const users: User[] = [
|
|
189
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' },
|
|
190
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'User' },
|
|
191
|
+
{ id: 3, name: 'Carol', email: 'carol@example.com', role: 'User' }
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
const columns = [
|
|
195
|
+
{ field: 'id', title: 'ID', width: '80px', sortable: true },
|
|
196
|
+
{ field: 'name', title: 'Name', sortable: true, filterable: true },
|
|
197
|
+
{ field: 'email', title: 'Email', filterable: true },
|
|
198
|
+
{ field: 'role', title: 'Role', sortable: true }
|
|
199
|
+
]
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<QuickGrid
|
|
203
|
+
items={users}
|
|
204
|
+
{columns}
|
|
205
|
+
sortable
|
|
206
|
+
filterable
|
|
207
|
+
pageable
|
|
208
|
+
pageSize={10}
|
|
209
|
+
/>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Navigation Layout with Persistence
|
|
213
|
+
```svelte
|
|
214
|
+
<script>
|
|
215
|
+
import { Layout, Header, NavMenu, NavGroup, NavLinkItem, BodyContent } from 'svelte-fluentui'
|
|
216
|
+
import { page } from '$app/stores'
|
|
217
|
+
|
|
218
|
+
// Check if a link is active based on current route
|
|
219
|
+
function isActive(href: string): boolean {
|
|
220
|
+
if (!href) return false
|
|
221
|
+
if (href === "/" && $page.url.pathname === "/") return true
|
|
222
|
+
if (href !== "/" && $page.url.pathname.startsWith(href)) return true
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<Layout>
|
|
228
|
+
<Header slot="header">
|
|
229
|
+
<h1>My App</h1>
|
|
230
|
+
</Header>
|
|
231
|
+
|
|
232
|
+
<NavMenu slot="navigation">
|
|
233
|
+
<NavGroup title="Main Menu">
|
|
234
|
+
{#snippet linkText()}
|
|
235
|
+
Main Menu
|
|
236
|
+
{/snippet}
|
|
237
|
+
|
|
238
|
+
<NavLinkItem href="/" class={isActive("/") ? "active" : ""}>
|
|
239
|
+
Home
|
|
240
|
+
</NavLinkItem>
|
|
241
|
+
<NavLinkItem href="/about" class={isActive("/about") ? "active" : ""}>
|
|
242
|
+
About
|
|
243
|
+
</NavLinkItem>
|
|
244
|
+
<NavLinkItem href="/contact" class={isActive("/contact") ? "active" : ""}>
|
|
245
|
+
Contact
|
|
246
|
+
</NavLinkItem>
|
|
247
|
+
</NavGroup>
|
|
248
|
+
</NavMenu>
|
|
249
|
+
|
|
250
|
+
<BodyContent>
|
|
251
|
+
<!-- Main content here -->
|
|
252
|
+
</BodyContent>
|
|
253
|
+
</Layout>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Date and Time Pickers
|
|
257
|
+
```svelte
|
|
258
|
+
<script>
|
|
259
|
+
import { DatePicker, TimePicker } from 'svelte-fluentui'
|
|
260
|
+
|
|
261
|
+
let selectedDate = $state<Date | null>(new Date())
|
|
262
|
+
let selectedTime = $state<string | null>("14:30")
|
|
263
|
+
|
|
264
|
+
// Deny weekends
|
|
265
|
+
const disabledDateFunc = (d: Date) => d.getDay() === 0 || d.getDay() === 6
|
|
266
|
+
</script>
|
|
267
|
+
|
|
268
|
+
<DatePicker
|
|
269
|
+
bind:value={selectedDate}
|
|
270
|
+
label="Select date"
|
|
271
|
+
placeholder="Choose a date"
|
|
272
|
+
firstDayOfWeek={1}
|
|
273
|
+
{disabledDateFunc}
|
|
274
|
+
autoClose={true}
|
|
275
|
+
/>
|
|
276
|
+
|
|
277
|
+
<TimePicker
|
|
278
|
+
bind:value={selectedTime}
|
|
279
|
+
label="Select time"
|
|
280
|
+
useAmPm={false}
|
|
281
|
+
showSeconds={false}
|
|
282
|
+
minTime="08:00"
|
|
283
|
+
maxTime="18:00"
|
|
284
|
+
autoClose={true}
|
|
285
|
+
/>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Dialog with Actions
|
|
289
|
+
```svelte
|
|
290
|
+
<script>
|
|
291
|
+
import { Dialog, Button } from 'svelte-fluentui'
|
|
292
|
+
|
|
293
|
+
let open = $state(false)
|
|
294
|
+
let name = $state("")
|
|
295
|
+
</script>
|
|
296
|
+
|
|
297
|
+
<Button onclick={() => open = true}>Open Dialog</Button>
|
|
298
|
+
|
|
299
|
+
<Dialog
|
|
300
|
+
bind:visible={open}
|
|
301
|
+
title="Rename item"
|
|
302
|
+
modal
|
|
303
|
+
primaryAction={{
|
|
304
|
+
label: "Save",
|
|
305
|
+
onClick: async () => {
|
|
306
|
+
if (!name.trim()) return false // keep open
|
|
307
|
+
await saveName(name)
|
|
308
|
+
// auto-closes when onClick resolves to anything but `false`
|
|
309
|
+
}
|
|
310
|
+
}}
|
|
311
|
+
secondaryAction={{
|
|
312
|
+
label: "Cancel"
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
<label>
|
|
316
|
+
Name
|
|
317
|
+
<input bind:value={name} />
|
|
318
|
+
</label>
|
|
319
|
+
</Dialog>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Responsive Tabs
|
|
323
|
+
```svelte
|
|
324
|
+
<script>
|
|
325
|
+
import { Tabs, Tab } from 'svelte-fluentui'
|
|
326
|
+
</script>
|
|
327
|
+
|
|
328
|
+
<!-- Default: tabs scroll horizontally when too wide -->
|
|
329
|
+
<Tabs activeId="overview">
|
|
330
|
+
{#snippet childContent()}
|
|
331
|
+
<Tab id="overview" label="Overview" />
|
|
332
|
+
<Tab id="members" label="Members" />
|
|
333
|
+
<Tab id="activity" label="Activity" />
|
|
334
|
+
<Tab id="integrations" label="Integrations" />
|
|
335
|
+
<Tab id="settings" label="Settings" />
|
|
336
|
+
{/snippet}
|
|
337
|
+
</Tabs>
|
|
338
|
+
|
|
339
|
+
<!-- Wrap onto multiple rows instead of scrolling -->
|
|
340
|
+
<Tabs responsive="wrap" activeId="a">...</Tabs>
|
|
341
|
+
|
|
342
|
+
<!-- Justify: tabs divide the row equally -->
|
|
343
|
+
<Tabs justify activeId="a">...</Tabs>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Autocomplete with Multiple Selection
|
|
347
|
+
```svelte
|
|
348
|
+
<script lang="ts">
|
|
349
|
+
import { Autocomplete } from 'svelte-fluentui'
|
|
350
|
+
|
|
351
|
+
const options = [
|
|
352
|
+
{ value: "1", text: "Option 1" },
|
|
353
|
+
{ value: "2", text: "Option 2" },
|
|
354
|
+
{ value: "3", text: "Option 3" }
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
let selected = $state<string[]>([])
|
|
358
|
+
</script>
|
|
359
|
+
|
|
360
|
+
<Autocomplete
|
|
361
|
+
bind:selectedOptions={selected}
|
|
362
|
+
options={options}
|
|
363
|
+
label="Select multiple"
|
|
364
|
+
placeholder="Type to search..."
|
|
365
|
+
maxSelectedOptions={5}
|
|
366
|
+
/>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### File Upload with Progress
|
|
370
|
+
```svelte
|
|
371
|
+
<script lang="ts">
|
|
372
|
+
import { InputFile } from 'svelte-fluentui'
|
|
373
|
+
import type { FileUploadHandler } from 'svelte-fluentui'
|
|
374
|
+
|
|
375
|
+
const uploadFile: FileUploadHandler = async (file, onProgress) => {
|
|
376
|
+
const formData = new FormData()
|
|
377
|
+
formData.append('file', file)
|
|
378
|
+
|
|
379
|
+
// Your upload logic here
|
|
380
|
+
// Call onProgress(percent) to update progress bar
|
|
381
|
+
|
|
382
|
+
const response = await fetch('/api/upload', {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
body: formData
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
if (!response.ok) throw new Error('Upload failed')
|
|
388
|
+
}
|
|
389
|
+
</script>
|
|
390
|
+
|
|
391
|
+
<InputFile
|
|
392
|
+
multiple={true}
|
|
393
|
+
accept="image/*"
|
|
394
|
+
maxFileSize={5 * 1024 * 1024}
|
|
395
|
+
uploadFileCallback={uploadFile}
|
|
396
|
+
onFileUploaded={(file) => console.log('Uploaded:', file.name)}
|
|
397
|
+
/>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Toast Notifications
|
|
401
|
+
```svelte
|
|
402
|
+
<script>
|
|
403
|
+
import { ToastContainer, toast } from 'svelte-fluentui'
|
|
404
|
+
|
|
405
|
+
function showSuccess() {
|
|
406
|
+
toast.success('Operation completed successfully!')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function showError() {
|
|
410
|
+
const id = toast.error('Something went wrong', {
|
|
411
|
+
persistent: true,
|
|
412
|
+
position: 'top-center'
|
|
413
|
+
})
|
|
414
|
+
// Later: toast.dismiss(id)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function showWithProgress() {
|
|
418
|
+
toast.info('Processing your request...', {
|
|
419
|
+
showProgress: true,
|
|
420
|
+
duration: 8000
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
</script>
|
|
424
|
+
|
|
425
|
+
<!-- Add once in your layout -->
|
|
426
|
+
<ToastContainer />
|
|
427
|
+
|
|
428
|
+
<button onclick={showSuccess}>Show Success</button>
|
|
429
|
+
<button onclick={showError}>Show Error</button>
|
|
430
|
+
<button onclick={showWithProgress}>Show With Progress</button>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### `portal` Action (for custom overlays)
|
|
434
|
+
```svelte
|
|
435
|
+
<script>
|
|
436
|
+
import { portal } from 'svelte-fluentui'
|
|
437
|
+
|
|
438
|
+
let open = $state(false)
|
|
439
|
+
</script>
|
|
440
|
+
|
|
441
|
+
<button onclick={() => open = true}>Show overlay</button>
|
|
442
|
+
|
|
443
|
+
{#if open}
|
|
444
|
+
<!-- Moved to <body> for the lifetime of this block so ancestor
|
|
445
|
+
stacking contexts / transforms don't affect its z-index. -->
|
|
446
|
+
<div use:portal class="my-overlay" onclick={() => open = false}>
|
|
447
|
+
My custom overlay
|
|
448
|
+
</div>
|
|
449
|
+
{/if}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Global Runtime API
|
|
453
|
+
```js
|
|
454
|
+
// In any browser console, after the page has loaded svelte-fluentui:
|
|
455
|
+
window.components["svelte-fluentui"].version()
|
|
456
|
+
// => "1.0.0-rc08"
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Also available as a direct import:
|
|
460
|
+
```js
|
|
461
|
+
import { VERSION } from 'svelte-fluentui'
|
|
462
|
+
console.log(VERSION)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Z-Index Scale
|
|
466
|
+
|
|
467
|
+
All overlays use a shared token scale so the layering stays consistent across the library. Variables are exposed at `:root` when you import the SCSS bundle; every `z-index: var(--fluent-z-*)` declaration in components also includes the numeric fallback so things still stack correctly even without our SCSS loaded.
|
|
468
|
+
|
|
469
|
+
| Token | Value | Used for |
|
|
470
|
+
|---|---|---|
|
|
471
|
+
| `--fluent-z-dropdown` | 1000 | Simple dropdowns, grid overlays |
|
|
472
|
+
| `--fluent-z-sticky` | 1020 | Sticky app bars / headers |
|
|
473
|
+
| `--fluent-z-fixed` | 1030 | Fixed sidebars / mobile nav |
|
|
474
|
+
| `--fluent-z-modal-backdrop` | 1040 | Dialog overlay |
|
|
475
|
+
| `--fluent-z-modal` | 1050 | Dialog itself |
|
|
476
|
+
| `--fluent-z-popover` | 1060 | Calendar / time / autocomplete popups (above modal) |
|
|
477
|
+
| `--fluent-z-tooltip` | 1070 | Tooltip |
|
|
478
|
+
| `--fluent-z-toast` | 1080 | Toast |
|
|
479
|
+
|
|
480
|
+
## Development
|
|
481
|
+
|
|
482
|
+
Clone the repository and install dependencies:
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
git clone https://github.com/KeenMate/svelte-fluentui.git
|
|
486
|
+
cd svelte-fluentui
|
|
487
|
+
npm install
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Working on the Library
|
|
491
|
+
|
|
492
|
+
The library source code is in `src/lib/`. To package the library:
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
npm run package
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Working on Documentation
|
|
499
|
+
|
|
500
|
+
The documentation site is in the `docs/` folder as a separate SvelteKit project:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
cd docs
|
|
504
|
+
npm install
|
|
505
|
+
npm run dev
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Or use the Makefile:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
make dev # Runs docs dev server
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Visit `http://localhost:5173` to see the component showcase and examples.
|
|
515
|
+
|
|
516
|
+
## Building
|
|
517
|
+
|
|
518
|
+
Build the library for publishing:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
npm run build
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Build documentation Docker image:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
# With local source
|
|
528
|
+
make docker-build-docs
|
|
529
|
+
|
|
530
|
+
# With specific npm version
|
|
531
|
+
make docker-build-docs VERSION=1.0.0-rc03
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Contributing
|
|
535
|
+
|
|
536
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
537
|
+
|
|
538
|
+
## License
|
|
539
|
+
|
|
540
|
+
MIT © [KeenMate](https://github.com/KeenMate)
|
|
541
|
+
|
|
542
|
+
## Credits
|
|
543
|
+
|
|
544
|
+
Built on top of [Microsoft FluentUI Web Components](https://github.com/microsoft/fluentui) v2.6.x.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move an element out of its current DOM position into another container
|
|
3
|
+
* (defaults to `document.body`) for the lifetime of the action. Useful for
|
|
4
|
+
* overlays — modals, popovers, tooltips — that must escape any ancestor
|
|
5
|
+
* `transform` / `filter` / `position: fixed` stacking context to honor the
|
|
6
|
+
* library's z-index scale.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* <div use:portal>...</div> // appends to <body>
|
|
10
|
+
* <div use:portal={'#layer-root'}> // appends to a specific selector / element
|
|
11
|
+
*/
|
|
12
|
+
export type PortalTarget = HTMLElement | string;
|
|
13
|
+
export declare function portal(node: HTMLElement, target?: PortalTarget): {
|
|
14
|
+
update(newTarget: PortalTarget): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function portal(node, target = document.body) {
|
|
2
|
+
function resolve(t) {
|
|
3
|
+
if (typeof t === "string")
|
|
4
|
+
return document.querySelector(t);
|
|
5
|
+
return t ?? null;
|
|
6
|
+
}
|
|
7
|
+
function mount(t) {
|
|
8
|
+
const host = resolve(t);
|
|
9
|
+
if (!host) {
|
|
10
|
+
console.warn("portal: target not found", t);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
host.appendChild(node);
|
|
14
|
+
}
|
|
15
|
+
mount(target);
|
|
16
|
+
return {
|
|
17
|
+
update(newTarget) {
|
|
18
|
+
mount(newTarget);
|
|
19
|
+
},
|
|
20
|
+
destroy() {
|
|
21
|
+
node.parentNode?.removeChild(node);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -31,3 +31,16 @@
|
|
|
31
31
|
gap: 1rem;
|
|
32
32
|
align-items: center;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
// Canonical label style used by form components (TextField, Select,
|
|
36
|
+
// Autocomplete, Radio, RadioGroup) so labels stay visually consistent
|
|
37
|
+
// regardless of whether the input is a fluent web component or a custom one.
|
|
38
|
+
.fluent-label {
|
|
39
|
+
display: block;
|
|
40
|
+
font-size: 0.875rem;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
line-height: 1.25;
|
|
43
|
+
color: var(--neutral-foreground-rest);
|
|
44
|
+
margin-bottom: 0.25rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -265,15 +265,6 @@ fluent-dialog {
|
|
|
265
265
|
z-index: 1050;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
// Modal backdrop - blocks interaction with content behind the dialog
|
|
269
|
-
fluent-dialog[modal]:not([hidden])::before {
|
|
270
|
-
content: "";
|
|
271
|
-
position: fixed;
|
|
272
|
-
inset: 0;
|
|
273
|
-
background-color: rgba(0, 0, 0, 0.4);
|
|
274
|
-
z-index: -1;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
268
|
fluent-dialog::part(control) {
|
|
278
269
|
--dialog-padding: calc(var(--design-unit) * 6 * 1px);
|
|
279
270
|
position: relative;
|
|
@@ -690,3 +681,16 @@ div[dragged-over] {
|
|
|
690
681
|
outline-width: var(--focus-stroke-width);
|
|
691
682
|
}
|
|
692
683
|
}
|
|
684
|
+
|
|
685
|
+
// =============================================================================
|
|
686
|
+
// Tab z-index override
|
|
687
|
+
// Blazor ships fluent-tab[aria-selected="true"] { z-index: 1 } via fluent-components.scss.
|
|
688
|
+
// That rule, combined with `position: relative` from elsewhere, creates a stacking
|
|
689
|
+
// context that can fight with overlay components when the tabs sit in an elevated
|
|
690
|
+
// ancestor (sticky header, transformed container, etc.). Reset it — selected state
|
|
691
|
+
// is already conveyed via font-weight and color tokens.
|
|
692
|
+
// =============================================================================
|
|
693
|
+
fluent-tab[aria-selected="true"] {
|
|
694
|
+
z-index: auto;
|
|
695
|
+
}
|
|
696
|
+
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Converts SCSS variables to CSS custom properties for runtime theme switching
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
@use 'tokens' as *;
|
|
6
7
|
@use 'variables' as *;
|
|
7
8
|
|
|
8
9
|
// Generate CSS custom properties from SCSS variables
|
|
@@ -72,6 +73,19 @@
|
|
|
72
73
|
|
|
73
74
|
// Transitions
|
|
74
75
|
--fluent-transition: #{$component-transition};
|
|
76
|
+
|
|
77
|
+
// Z-index scale (theme-independent — kept here so authors can use var() in any component).
|
|
78
|
+
// Order matters: dropdown < sticky < fixed < modal-backdrop < modal < popover < tooltip < toast.
|
|
79
|
+
// "popover" is intentionally above modal so dropdowns/calendars opened from inside a modal
|
|
80
|
+
// render on top of the modal.
|
|
81
|
+
--fluent-z-dropdown: #{$z-index-dropdown};
|
|
82
|
+
--fluent-z-sticky: #{$z-index-sticky};
|
|
83
|
+
--fluent-z-fixed: #{$z-index-fixed};
|
|
84
|
+
--fluent-z-modal-backdrop: #{$z-index-modal-backdrop};
|
|
85
|
+
--fluent-z-modal: #{$z-index-modal};
|
|
86
|
+
--fluent-z-popover: #{$z-index-popover};
|
|
87
|
+
--fluent-z-tooltip: #{$z-index-tooltip};
|
|
88
|
+
--fluent-z-toast: #{$z-index-toast};
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
[data-theme="dark"] {
|