@fastkit/vui 0.20.0 → 0.20.2
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 +932 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,4 +1,935 @@
|
|
|
1
|
+
|
|
1
2
|
# @fastkit/vui
|
|
2
3
|
|
|
4
|
+
🌐 English | [日本語](https://github.com/dadajam4/fastkit/blob/main/packages/vui/README-ja.md)
|
|
5
|
+
|
|
6
|
+
A simple and extensible UI component library for Vue.js 3 applications. Focused on full TypeScript support, color theme system, accessibility, and tight integration with @fastkit/vue-form-control.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **45+ UI Components**: Comprehensive component set including buttons, forms, navigation, data display
|
|
11
|
+
- **Integrated Ecosystem**: 20+ @fastkit packages with unified API
|
|
12
|
+
- **Full TypeScript Support**: Type-safe props, events, and slot definitions
|
|
13
|
+
- **Composition API Design**: Full adoption of Vue 3 modern patterns
|
|
14
|
+
- **Color Theme System**: Flexible theming with CSS Variables foundation
|
|
15
|
+
- **Form Integration**: Seamless integration with @fastkit/vue-form-control
|
|
16
|
+
- **Accessibility**: WAI-ARIA compliant, keyboard navigation support
|
|
17
|
+
- **Responsive Design**: Mobile-first grid system
|
|
18
|
+
- **Internationalization**: Multi-language font settings, RTL language support
|
|
19
|
+
- **Programmatic UI**: Imperative operations for dialogs, notifications, etc.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @fastkit/vui
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @fastkit/vui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Basic Usage
|
|
30
|
+
|
|
31
|
+
### Plugin Setup
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createApp } from 'vue';
|
|
35
|
+
import { VuiPlugin } from '@fastkit/vui';
|
|
36
|
+
import { createRouter } from 'vue-router';
|
|
37
|
+
|
|
38
|
+
const app = createApp(App);
|
|
39
|
+
const router = createRouter(/* routes */);
|
|
40
|
+
|
|
41
|
+
// Install VUI plugin
|
|
42
|
+
app.use(VuiPlugin, {
|
|
43
|
+
router,
|
|
44
|
+
colorScheme: {
|
|
45
|
+
// Color theme settings
|
|
46
|
+
primary: '#1976d2',
|
|
47
|
+
secondary: '#424242'
|
|
48
|
+
},
|
|
49
|
+
uiSettings: {
|
|
50
|
+
primaryScope: 'primary',
|
|
51
|
+
buttonDefault: {
|
|
52
|
+
color: 'primary',
|
|
53
|
+
variant: 'contained'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Application Setup
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<template>
|
|
63
|
+
<VApp>
|
|
64
|
+
<!-- Application content -->
|
|
65
|
+
<router-view />
|
|
66
|
+
</VApp>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
import { VApp } from '@fastkit/vui';
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Component List
|
|
75
|
+
|
|
76
|
+
### Layout & Structure
|
|
77
|
+
|
|
78
|
+
- **VApp** - Application root container
|
|
79
|
+
- **VGrid** (`VGridContainer`, `VGridItem`) - Responsive grid system
|
|
80
|
+
- **VPaper** - Material Design-style paper container
|
|
81
|
+
- **VCard** (`VCardContent`, `VCardActions`) - Card layout
|
|
82
|
+
- **VToolbar** (`VToolbarTitle`, `VToolbarMenu`, `VToolbarEdge`) - Toolbar
|
|
83
|
+
|
|
84
|
+
### Navigation
|
|
85
|
+
|
|
86
|
+
- **VNavigation**, **VNavigationItem** - Side navigation
|
|
87
|
+
- **VBreadcrumbs** - Breadcrumb navigation
|
|
88
|
+
- **VTabs**, **VTab** - Tab interface
|
|
89
|
+
- **VPagination** - Pagination
|
|
90
|
+
|
|
91
|
+
### Form Controls
|
|
92
|
+
|
|
93
|
+
- **VButton**, **VButtonGroup** - Button and button group
|
|
94
|
+
- **VTextField** - Text input field
|
|
95
|
+
- **VTextarea** - Multi-line text input
|
|
96
|
+
- **VNumberField** - Number input field
|
|
97
|
+
- **VSelect** - Select dropdown
|
|
98
|
+
- **VCheckbox**, **VCheckboxGroup** - Checkbox
|
|
99
|
+
- **VRadio**, **VRadioGroup** - Radio button
|
|
100
|
+
- **VSwitch**, **VSwitchGroup** - Switch toggle
|
|
101
|
+
- **VOption**, **VOptionGroup** - Option element
|
|
102
|
+
|
|
103
|
+
### Data Display
|
|
104
|
+
|
|
105
|
+
- **VDataTable** - Data table
|
|
106
|
+
- **VListTile** - List item
|
|
107
|
+
- **VAvatar** - User avatar
|
|
108
|
+
- **VChip** - Chip/tag element
|
|
109
|
+
- **VIcon** - Icon display
|
|
110
|
+
|
|
111
|
+
### Feedback & Interaction
|
|
112
|
+
|
|
113
|
+
- **VDialog** - Modal dialog
|
|
114
|
+
- **VSnackbar** - Notification snackbar
|
|
115
|
+
- **VTooltip** - Tooltip
|
|
116
|
+
- **VMenu** - Context menu
|
|
117
|
+
- **VSheetModal** - Sheet modal
|
|
118
|
+
|
|
119
|
+
### Others
|
|
120
|
+
|
|
121
|
+
- **VSkeltonLoader** - Skeleton loading
|
|
122
|
+
- **VBusyImage** - Lazy loading image
|
|
123
|
+
- **VHero** - Hero section
|
|
124
|
+
- **VContentSwitcher** - Content switcher
|
|
125
|
+
|
|
126
|
+
## Usage Examples
|
|
127
|
+
|
|
128
|
+
### Button Components
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<template>
|
|
132
|
+
<div>
|
|
133
|
+
<!-- Basic button -->
|
|
134
|
+
<VButton @click="handleClick">Click</VButton>
|
|
135
|
+
|
|
136
|
+
<!-- Primary button -->
|
|
137
|
+
<VButton color="primary" variant="contained">
|
|
138
|
+
Save
|
|
139
|
+
</VButton>
|
|
140
|
+
|
|
141
|
+
<!-- Button with icon -->
|
|
142
|
+
<VButton
|
|
143
|
+
startIcon="mdi-search"
|
|
144
|
+
color="primary"
|
|
145
|
+
variant="outlined"
|
|
146
|
+
:loading="searching"
|
|
147
|
+
@click="handleSearch"
|
|
148
|
+
>
|
|
149
|
+
Search
|
|
150
|
+
</VButton>
|
|
151
|
+
|
|
152
|
+
<!-- Button group -->
|
|
153
|
+
<VButtonGroup>
|
|
154
|
+
<VButton>Left</VButton>
|
|
155
|
+
<VButton>Center</VButton>
|
|
156
|
+
<VButton>Right</VButton>
|
|
157
|
+
</VButtonGroup>
|
|
158
|
+
</div>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<script setup lang="ts">
|
|
162
|
+
import { ref } from 'vue';
|
|
163
|
+
import { VButton, VButtonGroup } from '@fastkit/vui';
|
|
164
|
+
|
|
165
|
+
const searching = ref(false);
|
|
166
|
+
|
|
167
|
+
const handleClick = () => {
|
|
168
|
+
console.log('Button clicked');
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const handleSearch = async () => {
|
|
172
|
+
searching.value = true;
|
|
173
|
+
try {
|
|
174
|
+
// Search processing
|
|
175
|
+
await performSearch();
|
|
176
|
+
} finally {
|
|
177
|
+
searching.value = false;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
</script>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Form Components
|
|
184
|
+
|
|
185
|
+
```vue
|
|
186
|
+
<template>
|
|
187
|
+
<VCard>
|
|
188
|
+
<VCardContent>
|
|
189
|
+
<h2>User Registration</h2>
|
|
190
|
+
|
|
191
|
+
<!-- Text input -->
|
|
192
|
+
<VTextField
|
|
193
|
+
v-model="form.name.value"
|
|
194
|
+
label="Name"
|
|
195
|
+
required
|
|
196
|
+
:rules="[required(), minLength(2)]"
|
|
197
|
+
:invalid="form.name.invalid"
|
|
198
|
+
:error-message="form.name.errorMessage"
|
|
199
|
+
/>
|
|
200
|
+
|
|
201
|
+
<!-- Email input -->
|
|
202
|
+
<VTextField
|
|
203
|
+
v-model="form.email.value"
|
|
204
|
+
label="Email Address"
|
|
205
|
+
type="email"
|
|
206
|
+
required
|
|
207
|
+
:rules="[required(), email()]"
|
|
208
|
+
:invalid="form.email.invalid"
|
|
209
|
+
:error-message="form.email.errorMessage"
|
|
210
|
+
/>
|
|
211
|
+
|
|
212
|
+
<!-- Select -->
|
|
213
|
+
<VSelect
|
|
214
|
+
v-model="form.category.value"
|
|
215
|
+
label="Category"
|
|
216
|
+
:items="categories"
|
|
217
|
+
placeholder="Select category"
|
|
218
|
+
required
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
<!-- Checkbox -->
|
|
222
|
+
<VCheckbox
|
|
223
|
+
v-model="form.agreement.value"
|
|
224
|
+
:rules="[required()]"
|
|
225
|
+
>
|
|
226
|
+
I agree to the terms of use
|
|
227
|
+
</VCheckbox>
|
|
228
|
+
</VCardContent>
|
|
229
|
+
|
|
230
|
+
<VCardActions>
|
|
231
|
+
<VButton
|
|
232
|
+
color="primary"
|
|
233
|
+
variant="contained"
|
|
234
|
+
:disabled="form.invalid"
|
|
235
|
+
@click="handleSubmit"
|
|
236
|
+
>
|
|
237
|
+
Register
|
|
238
|
+
</VButton>
|
|
239
|
+
<VButton variant="text" @click="handleCancel">
|
|
240
|
+
Cancel
|
|
241
|
+
</VButton>
|
|
242
|
+
</VCardActions>
|
|
243
|
+
</VCard>
|
|
244
|
+
</template>
|
|
245
|
+
|
|
246
|
+
<script setup lang="ts">
|
|
247
|
+
import {
|
|
248
|
+
VCard, VCardContent, VCardActions,
|
|
249
|
+
VTextField, VSelect, VCheckbox, VButton
|
|
250
|
+
} from '@fastkit/vui';
|
|
251
|
+
import { useForm } from '@fastkit/vue-form-control';
|
|
252
|
+
import { required, email, minLength } from '@fastkit/rules';
|
|
253
|
+
|
|
254
|
+
const categories = [
|
|
255
|
+
{ value: 'personal', label: 'Personal' },
|
|
256
|
+
{ value: 'business', label: 'Business' },
|
|
257
|
+
{ value: 'education', label: 'Education' }
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
const form = useForm({
|
|
261
|
+
name: {
|
|
262
|
+
value: '',
|
|
263
|
+
rules: [required(), minLength(2)]
|
|
264
|
+
},
|
|
265
|
+
email: {
|
|
266
|
+
value: '',
|
|
267
|
+
rules: [required(), email()]
|
|
268
|
+
},
|
|
269
|
+
category: {
|
|
270
|
+
value: '',
|
|
271
|
+
rules: [required()]
|
|
272
|
+
},
|
|
273
|
+
agreement: {
|
|
274
|
+
value: false,
|
|
275
|
+
rules: [required()]
|
|
276
|
+
}
|
|
277
|
+
}, {
|
|
278
|
+
onSubmit: async (values) => {
|
|
279
|
+
console.log('Form submitted:', values);
|
|
280
|
+
await api.register(values);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const handleSubmit = () => {
|
|
285
|
+
form.submit();
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const handleCancel = () => {
|
|
289
|
+
form.reset();
|
|
290
|
+
};
|
|
291
|
+
</script>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Data Table
|
|
295
|
+
|
|
296
|
+
```vue
|
|
297
|
+
<template>
|
|
298
|
+
<VDataTable
|
|
299
|
+
:items="users"
|
|
300
|
+
:headers="headers"
|
|
301
|
+
:loading="loading"
|
|
302
|
+
item-key="id"
|
|
303
|
+
selectable
|
|
304
|
+
@select="handleSelect"
|
|
305
|
+
>
|
|
306
|
+
<!-- Custom column -->
|
|
307
|
+
<template #item.status="{ item }">
|
|
308
|
+
<VChip
|
|
309
|
+
:color="item.status === 'active' ? 'success' : 'warning'"
|
|
310
|
+
size="sm"
|
|
311
|
+
>
|
|
312
|
+
{{ item.status === 'active' ? 'Active' : 'Inactive' }}
|
|
313
|
+
</VChip>
|
|
314
|
+
</template>
|
|
315
|
+
|
|
316
|
+
<template #item.actions="{ item }">
|
|
317
|
+
<VButton size="sm" variant="text" @click="editUser(item)">
|
|
318
|
+
Edit
|
|
319
|
+
</VButton>
|
|
320
|
+
<VButton
|
|
321
|
+
size="sm"
|
|
322
|
+
variant="text"
|
|
323
|
+
color="error"
|
|
324
|
+
@click="deleteUser(item)"
|
|
325
|
+
>
|
|
326
|
+
Delete
|
|
327
|
+
</VButton>
|
|
328
|
+
</template>
|
|
329
|
+
</VDataTable>
|
|
330
|
+
</template>
|
|
331
|
+
|
|
332
|
+
<script setup lang="ts">
|
|
333
|
+
import { ref } from 'vue';
|
|
334
|
+
import { VDataTable, VChip, VButton } from '@fastkit/vui';
|
|
335
|
+
|
|
336
|
+
const loading = ref(false);
|
|
337
|
+
const users = ref([
|
|
338
|
+
{ id: 1, name: 'John Tanaka', email: 'tanaka@example.com', status: 'active' },
|
|
339
|
+
{ id: 2, name: 'Hanako Sato', email: 'sato@example.com', status: 'inactive' }
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
const headers = [
|
|
343
|
+
{ key: 'name', title: 'Name', sortable: true },
|
|
344
|
+
{ key: 'email', title: 'Email', sortable: true },
|
|
345
|
+
{ key: 'status', title: 'Status' },
|
|
346
|
+
{ key: 'actions', title: 'Actions', width: 120 }
|
|
347
|
+
];
|
|
348
|
+
|
|
349
|
+
const handleSelect = (selectedItems: any[]) => {
|
|
350
|
+
console.log('Selected items:', selectedItems);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const editUser = (user: any) => {
|
|
354
|
+
console.log('Edit:', user);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const deleteUser = (user: any) => {
|
|
358
|
+
console.log('Delete:', user);
|
|
359
|
+
};
|
|
360
|
+
</script>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Layout System
|
|
364
|
+
|
|
365
|
+
```vue
|
|
366
|
+
<template>
|
|
367
|
+
<VApp>
|
|
368
|
+
<!-- Toolbar -->
|
|
369
|
+
<VToolbar color="primary" variant="flat">
|
|
370
|
+
<VToolbarTitle>My App</VToolbarTitle>
|
|
371
|
+
<VToolbarMenu>
|
|
372
|
+
<VButton variant="text" color="on-primary">
|
|
373
|
+
Menu
|
|
374
|
+
</VButton>
|
|
375
|
+
</VToolbarMenu>
|
|
376
|
+
</VToolbar>
|
|
377
|
+
|
|
378
|
+
<!-- Main content -->
|
|
379
|
+
<VGridContainer>
|
|
380
|
+
<VGridItem cols="12" md="3">
|
|
381
|
+
<!-- Side navigation -->
|
|
382
|
+
<VNavigation>
|
|
383
|
+
<VNavigationItem to="/dashboard" icon="mdi-dashboard">
|
|
384
|
+
Dashboard
|
|
385
|
+
</VNavigationItem>
|
|
386
|
+
<VNavigationItem to="/users" icon="mdi-account-group">
|
|
387
|
+
Users
|
|
388
|
+
</VNavigationItem>
|
|
389
|
+
<VNavigationItem to="/settings" icon="mdi-cog">
|
|
390
|
+
Settings
|
|
391
|
+
</VNavigationItem>
|
|
392
|
+
</VNavigation>
|
|
393
|
+
</VGridItem>
|
|
394
|
+
|
|
395
|
+
<VGridItem cols="12" md="9">
|
|
396
|
+
<!-- Page content -->
|
|
397
|
+
<VPaper class="pa-4">
|
|
398
|
+
<router-view />
|
|
399
|
+
</VPaper>
|
|
400
|
+
</VGridItem>
|
|
401
|
+
</VGridContainer>
|
|
402
|
+
</VApp>
|
|
403
|
+
</template>
|
|
404
|
+
|
|
405
|
+
<script setup lang="ts">
|
|
406
|
+
import {
|
|
407
|
+
VApp, VToolbar, VToolbarTitle, VToolbarMenu,
|
|
408
|
+
VGridContainer, VGridItem, VNavigation, VNavigationItem,
|
|
409
|
+
VPaper, VButton
|
|
410
|
+
} from '@fastkit/vui';
|
|
411
|
+
</script>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Programmatic UI Operations
|
|
415
|
+
|
|
416
|
+
You can control UI elements from JavaScript using VUI services.
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { useVui } from '@fastkit/vui';
|
|
420
|
+
|
|
421
|
+
const vui = useVui();
|
|
422
|
+
|
|
423
|
+
// Alert dialog
|
|
424
|
+
await vui.alert('Processing completed');
|
|
425
|
+
|
|
426
|
+
// Confirmation dialog
|
|
427
|
+
const confirmed = await vui.confirm({
|
|
428
|
+
title: 'Confirmation',
|
|
429
|
+
message: 'Do you want to execute this operation?',
|
|
430
|
+
okText: 'Execute',
|
|
431
|
+
cancelText: 'Cancel'
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
if (confirmed) {
|
|
435
|
+
// Process when confirmed
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Prompt dialog
|
|
439
|
+
const result = await vui.prompt({
|
|
440
|
+
title: 'Enter name',
|
|
441
|
+
message: 'Please enter a new name',
|
|
442
|
+
defaultValue: 'Default value'
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Form prompt
|
|
446
|
+
const formResult = await vui.formPrompt(
|
|
447
|
+
{
|
|
448
|
+
state: { name: '', description: '' },
|
|
449
|
+
title: 'Create New'
|
|
450
|
+
},
|
|
451
|
+
(state) => (
|
|
452
|
+
<>
|
|
453
|
+
<VTextField
|
|
454
|
+
label="Name"
|
|
455
|
+
v-model={state.name}
|
|
456
|
+
required
|
|
457
|
+
/>
|
|
458
|
+
<VTextarea
|
|
459
|
+
label="Description"
|
|
460
|
+
v-model={state.description}
|
|
461
|
+
rows={3}
|
|
462
|
+
/>
|
|
463
|
+
</>
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// Snackbar notification
|
|
468
|
+
vui.snackbar.show({
|
|
469
|
+
message: 'Saved',
|
|
470
|
+
color: 'success',
|
|
471
|
+
timeout: 3000
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Custom dialog
|
|
475
|
+
const dialog = await vui.dialog.show({
|
|
476
|
+
component: MyCustomDialog,
|
|
477
|
+
props: {
|
|
478
|
+
data: someData
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Theme Customization
|
|
484
|
+
|
|
485
|
+
### CSS Variables
|
|
486
|
+
|
|
487
|
+
```css
|
|
488
|
+
:root {
|
|
489
|
+
/* Color palette */
|
|
490
|
+
--color-primary: #1976d2;
|
|
491
|
+
--color-secondary: #424242;
|
|
492
|
+
--color-success: #4caf50;
|
|
493
|
+
--color-warning: #ff9800;
|
|
494
|
+
--color-error: #f44336;
|
|
495
|
+
|
|
496
|
+
/* Size settings */
|
|
497
|
+
--control-field-rem-sm: 0.875rem;
|
|
498
|
+
--control-field-rem-md: 1rem;
|
|
499
|
+
--control-field-rem-lg: 1.125rem;
|
|
500
|
+
|
|
501
|
+
/* Spacing */
|
|
502
|
+
--root-spacing: 8px;
|
|
503
|
+
|
|
504
|
+
/* Font */
|
|
505
|
+
--typo-base-font: Roboto, 'Noto Sans JP', sans-serif;
|
|
506
|
+
|
|
507
|
+
/* Shadow */
|
|
508
|
+
--shadow-1: 0px 2px 1px -1px rgba(0, 0, 0, 0.2);
|
|
509
|
+
--shadow-4: 0px 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
510
|
+
|
|
511
|
+
/* Transition */
|
|
512
|
+
--transition-primary: cubic-bezier(0.25, 0.8, 0.5, 1);
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Custom Color Scheme
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// Define custom colors
|
|
520
|
+
app.use(VuiPlugin, {
|
|
521
|
+
colorScheme: {
|
|
522
|
+
primary: {
|
|
523
|
+
main: '#1976d2',
|
|
524
|
+
light: '#42a5f5',
|
|
525
|
+
dark: '#1565c0',
|
|
526
|
+
contrast: '#ffffff'
|
|
527
|
+
},
|
|
528
|
+
secondary: {
|
|
529
|
+
main: '#dc004e',
|
|
530
|
+
light: '#ff5983',
|
|
531
|
+
dark: '#9a0036',
|
|
532
|
+
contrast: '#ffffff'
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Dark Theme Support
|
|
539
|
+
|
|
540
|
+
```vue
|
|
541
|
+
<template>
|
|
542
|
+
<VApp :theme="currentTheme">
|
|
543
|
+
<VButton @click="toggleTheme">
|
|
544
|
+
{{ currentTheme === 'dark' ? 'Light' : 'Dark' }} Theme
|
|
545
|
+
</VButton>
|
|
546
|
+
<!-- App content -->
|
|
547
|
+
</VApp>
|
|
548
|
+
</template>
|
|
549
|
+
|
|
550
|
+
<script setup lang="ts">
|
|
551
|
+
import { ref } from 'vue';
|
|
552
|
+
import { useColorScheme } from '@fastkit/vui';
|
|
553
|
+
|
|
554
|
+
const { theme: currentTheme, toggle: toggleTheme } = useColorScheme();
|
|
555
|
+
</script>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Advanced Usage Examples
|
|
559
|
+
|
|
560
|
+
### Custom Component Creation
|
|
561
|
+
|
|
562
|
+
```vue
|
|
563
|
+
<template>
|
|
564
|
+
<VCard class="user-profile">
|
|
565
|
+
<VCardContent>
|
|
566
|
+
<div class="user-profile__header">
|
|
567
|
+
<VAvatar :src="user.avatar" size="lg" />
|
|
568
|
+
<div class="user-profile__info">
|
|
569
|
+
<h3>{{ user.name }}</h3>
|
|
570
|
+
<p>{{ user.role }}</p>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<VTabs v-model="activeTab">
|
|
575
|
+
<VTab value="profile">Profile</VTab>
|
|
576
|
+
<VTab value="settings">Settings</VTab>
|
|
577
|
+
<VTab value="activity">Activity</VTab>
|
|
578
|
+
</VTabs>
|
|
579
|
+
|
|
580
|
+
<VContentSwitcher :value="activeTab">
|
|
581
|
+
<template #profile>
|
|
582
|
+
<UserProfileTab :user="user" />
|
|
583
|
+
</template>
|
|
584
|
+
<template #settings>
|
|
585
|
+
<UserSettingsTab :user="user" />
|
|
586
|
+
</template>
|
|
587
|
+
<template #activity>
|
|
588
|
+
<UserActivityTab :user="user" />
|
|
589
|
+
</template>
|
|
590
|
+
</VContentSwitcher>
|
|
591
|
+
</VCardContent>
|
|
592
|
+
</VCard>
|
|
593
|
+
</template>
|
|
594
|
+
|
|
595
|
+
<script setup lang="ts">
|
|
596
|
+
import { ref } from 'vue';
|
|
597
|
+
import {
|
|
598
|
+
VCard, VCardContent, VAvatar, VTabs, VTab, VContentSwitcher
|
|
599
|
+
} from '@fastkit/vui';
|
|
600
|
+
|
|
601
|
+
interface User {
|
|
602
|
+
name: string;
|
|
603
|
+
role: string;
|
|
604
|
+
avatar: string;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const props = defineProps<{
|
|
608
|
+
user: User;
|
|
609
|
+
}>();
|
|
610
|
+
|
|
611
|
+
const activeTab = ref('profile');
|
|
612
|
+
</script>
|
|
613
|
+
|
|
614
|
+
<style scoped>
|
|
615
|
+
.user-profile__header {
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
gap: 1rem;
|
|
619
|
+
margin-bottom: 1.5rem;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.user-profile__info h3 {
|
|
623
|
+
margin: 0;
|
|
624
|
+
font-size: 1.25rem;
|
|
625
|
+
font-weight: 500;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.user-profile__info p {
|
|
629
|
+
margin: 0.25rem 0 0;
|
|
630
|
+
color: var(--color-text-secondary);
|
|
631
|
+
}
|
|
632
|
+
</style>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Complex Form Wizard
|
|
636
|
+
|
|
637
|
+
```vue
|
|
638
|
+
<template>
|
|
639
|
+
<VCard class="form-wizard">
|
|
640
|
+
<VCardContent>
|
|
641
|
+
<VToolbar variant="flat" class="mb-4">
|
|
642
|
+
<VToolbarTitle>Registration Wizard</VToolbarTitle>
|
|
643
|
+
<VToolbarEdge>
|
|
644
|
+
Step {{ currentStep + 1 }} / {{ steps.length }}
|
|
645
|
+
</VToolbarEdge>
|
|
646
|
+
</VToolbar>
|
|
647
|
+
|
|
648
|
+
<!-- Step indicator -->
|
|
649
|
+
<div class="step-indicator">
|
|
650
|
+
<div
|
|
651
|
+
v-for="(step, index) in steps"
|
|
652
|
+
:key="step.name"
|
|
653
|
+
class="step-indicator__item"
|
|
654
|
+
:class="{
|
|
655
|
+
'step-indicator__item--active': index === currentStep,
|
|
656
|
+
'step-indicator__item--completed': index < currentStep
|
|
657
|
+
}"
|
|
658
|
+
>
|
|
659
|
+
<VIcon
|
|
660
|
+
:name="index < currentStep ? 'mdi-check' : step.icon"
|
|
661
|
+
size="sm"
|
|
662
|
+
/>
|
|
663
|
+
<span>{{ step.title }}</span>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- Step content -->
|
|
668
|
+
<VContentSwitcher :value="currentStep">
|
|
669
|
+
<template #0>
|
|
670
|
+
<PersonalInfoStep v-model="formData.personal" />
|
|
671
|
+
</template>
|
|
672
|
+
<template #1>
|
|
673
|
+
<ContactInfoStep v-model="formData.contact" />
|
|
674
|
+
</template>
|
|
675
|
+
<template #2>
|
|
676
|
+
<PreferencesStep v-model="formData.preferences" />
|
|
677
|
+
</template>
|
|
678
|
+
<template #3>
|
|
679
|
+
<ConfirmationStep :data="formData" />
|
|
680
|
+
</template>
|
|
681
|
+
</VContentSwitcher>
|
|
682
|
+
</VCardContent>
|
|
683
|
+
|
|
684
|
+
<VCardActions>
|
|
685
|
+
<VButton
|
|
686
|
+
variant="text"
|
|
687
|
+
:disabled="currentStep === 0"
|
|
688
|
+
@click="previousStep"
|
|
689
|
+
>
|
|
690
|
+
Back
|
|
691
|
+
</VButton>
|
|
692
|
+
|
|
693
|
+
<div class="flex-grow" />
|
|
694
|
+
|
|
695
|
+
<VButton
|
|
696
|
+
v-if="currentStep < steps.length - 1"
|
|
697
|
+
color="primary"
|
|
698
|
+
variant="contained"
|
|
699
|
+
:disabled="!canProceed"
|
|
700
|
+
@click="nextStep"
|
|
701
|
+
>
|
|
702
|
+
Next
|
|
703
|
+
</VButton>
|
|
704
|
+
|
|
705
|
+
<VButton
|
|
706
|
+
v-else
|
|
707
|
+
color="primary"
|
|
708
|
+
variant="contained"
|
|
709
|
+
:loading="submitting"
|
|
710
|
+
@click="submitForm"
|
|
711
|
+
>
|
|
712
|
+
Complete
|
|
713
|
+
</VButton>
|
|
714
|
+
</VCardActions>
|
|
715
|
+
</VCard>
|
|
716
|
+
</template>
|
|
717
|
+
|
|
718
|
+
<script setup lang="ts">
|
|
719
|
+
import { ref, computed } from 'vue';
|
|
720
|
+
import {
|
|
721
|
+
VCard, VCardContent, VCardActions,
|
|
722
|
+
VToolbar, VToolbarTitle, VToolbarEdge,
|
|
723
|
+
VContentSwitcher, VButton, VIcon
|
|
724
|
+
} from '@fastkit/vui';
|
|
725
|
+
|
|
726
|
+
const currentStep = ref(0);
|
|
727
|
+
const submitting = ref(false);
|
|
728
|
+
|
|
729
|
+
const steps = [
|
|
730
|
+
{ name: 'personal', title: 'Personal Info', icon: 'mdi-account' },
|
|
731
|
+
{ name: 'contact', title: 'Contact Info', icon: 'mdi-email' },
|
|
732
|
+
{ name: 'preferences', title: 'Preferences', icon: 'mdi-cog' },
|
|
733
|
+
{ name: 'confirm', title: 'Confirmation', icon: 'mdi-check-circle' }
|
|
734
|
+
];
|
|
735
|
+
|
|
736
|
+
const formData = ref({
|
|
737
|
+
personal: { name: '', birthday: '' },
|
|
738
|
+
contact: { email: '', phone: '' },
|
|
739
|
+
preferences: { newsletter: false, theme: 'light' }
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const canProceed = computed(() => {
|
|
743
|
+
// Validation for each step
|
|
744
|
+
switch (currentStep.value) {
|
|
745
|
+
case 0:
|
|
746
|
+
return formData.value.personal.name && formData.value.personal.birthday;
|
|
747
|
+
case 1:
|
|
748
|
+
return formData.value.contact.email;
|
|
749
|
+
case 2:
|
|
750
|
+
return true;
|
|
751
|
+
default:
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const nextStep = () => {
|
|
757
|
+
if (currentStep.value < steps.length - 1) {
|
|
758
|
+
currentStep.value++;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
const previousStep = () => {
|
|
763
|
+
if (currentStep.value > 0) {
|
|
764
|
+
currentStep.value--;
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const submitForm = async () => {
|
|
769
|
+
submitting.value = true;
|
|
770
|
+
try {
|
|
771
|
+
await api.submitRegistration(formData.value);
|
|
772
|
+
// Success processing
|
|
773
|
+
} finally {
|
|
774
|
+
submitting.value = false;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
</script>
|
|
778
|
+
|
|
779
|
+
<style scoped>
|
|
780
|
+
.step-indicator {
|
|
781
|
+
display: flex;
|
|
782
|
+
justify-content: space-between;
|
|
783
|
+
margin-bottom: 2rem;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.step-indicator__item {
|
|
787
|
+
display: flex;
|
|
788
|
+
flex-direction: column;
|
|
789
|
+
align-items: center;
|
|
790
|
+
gap: 0.5rem;
|
|
791
|
+
opacity: 0.6;
|
|
792
|
+
transition: opacity 0.2s;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.step-indicator__item--active,
|
|
796
|
+
.step-indicator__item--completed {
|
|
797
|
+
opacity: 1;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.flex-grow {
|
|
801
|
+
flex-grow: 1;
|
|
802
|
+
}
|
|
803
|
+
</style>
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## API
|
|
807
|
+
|
|
808
|
+
### Composables
|
|
809
|
+
|
|
810
|
+
#### useVui()
|
|
811
|
+
|
|
812
|
+
Access VUI service instance.
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
const vui = useVui();
|
|
816
|
+
|
|
817
|
+
// Dialog operations
|
|
818
|
+
vui.alert(message)
|
|
819
|
+
vui.confirm(options)
|
|
820
|
+
vui.prompt(options)
|
|
821
|
+
vui.formPrompt(state, renderer)
|
|
822
|
+
|
|
823
|
+
// Notifications
|
|
824
|
+
vui.snackbar.show(options)
|
|
825
|
+
vui.snackbar.hide()
|
|
826
|
+
|
|
827
|
+
// Navigation
|
|
828
|
+
vui.router.push(location)
|
|
829
|
+
vui.location.assign(url)
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
#### useControl()
|
|
833
|
+
|
|
834
|
+
Provides common functionality for control elements.
|
|
835
|
+
|
|
836
|
+
```typescript
|
|
837
|
+
const control = useControl(props, options);
|
|
838
|
+
|
|
839
|
+
// Properties
|
|
840
|
+
control.size // 'sm' | 'md' | 'lg'
|
|
841
|
+
control.classes // Computed CSS classes
|
|
842
|
+
control.isDisabled // Disabled state
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### useColorScheme()
|
|
846
|
+
|
|
847
|
+
Provides color theme control.
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
const { theme, toggle, setTheme } = useColorScheme();
|
|
851
|
+
|
|
852
|
+
theme.value // Current theme
|
|
853
|
+
toggle() // Toggle theme
|
|
854
|
+
setTheme('dark') // Set specific theme
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### Plugin Options
|
|
858
|
+
|
|
859
|
+
```typescript
|
|
860
|
+
interface VuiPluginOptions {
|
|
861
|
+
router: Router;
|
|
862
|
+
colorScheme?: VueColorSchemePluginSettings;
|
|
863
|
+
uiSettings?: {
|
|
864
|
+
primaryScope: ScopeName;
|
|
865
|
+
buttonDefault: {
|
|
866
|
+
color: ScopeName;
|
|
867
|
+
variant: ColorVariant;
|
|
868
|
+
};
|
|
869
|
+
dialogOk?: {
|
|
870
|
+
color?: ScopeName;
|
|
871
|
+
variant?: ColorVariant;
|
|
872
|
+
};
|
|
873
|
+
};
|
|
874
|
+
icons?: {
|
|
875
|
+
menuDown: IconName;
|
|
876
|
+
navigationExpand: RawIconProp;
|
|
877
|
+
// Other icon settings
|
|
878
|
+
};
|
|
879
|
+
stack?: VueStackPluginOptions;
|
|
880
|
+
form?: VueFormServiceOptions;
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
## Accessibility
|
|
885
|
+
|
|
886
|
+
### Keyboard Navigation
|
|
887
|
+
|
|
888
|
+
- **Tab/Shift+Tab**: Focus movement
|
|
889
|
+
- **Enter/Space**: Button/checkbox operation
|
|
890
|
+
- **Arrow Keys**: Radio button/tab/menu navigation
|
|
891
|
+
- **Escape**: Close dialog/menu
|
|
892
|
+
- **Home/End**: Move to beginning/end of list/table
|
|
893
|
+
|
|
894
|
+
### ARIA Support
|
|
895
|
+
|
|
896
|
+
```html
|
|
897
|
+
<!-- Automatically applied ARIA attributes -->
|
|
898
|
+
<button aria-disabled="true" aria-label="Save button">
|
|
899
|
+
<input aria-invalid="true" aria-describedby="error-message">
|
|
900
|
+
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
### Screen Reader Support
|
|
904
|
+
|
|
905
|
+
- Voice reading of form errors
|
|
906
|
+
- State change notifications
|
|
907
|
+
- Proper placement of landmark elements
|
|
908
|
+
|
|
909
|
+
## Dependencies
|
|
910
|
+
|
|
911
|
+
```json
|
|
912
|
+
{
|
|
913
|
+
"dependencies": {
|
|
914
|
+
"@fastkit/vue-form-control": "Form functionality",
|
|
915
|
+
"@fastkit/vue-color-scheme": "Color theme",
|
|
916
|
+
"@fastkit/vue-stack": "Stack management",
|
|
917
|
+
"@fastkit/vue-action": "Action functionality",
|
|
918
|
+
"@fastkit/vue-app-layout": "Layout",
|
|
919
|
+
"@fastkit/rules": "Validation",
|
|
920
|
+
"@fastkit/helpers": "Utilities"
|
|
921
|
+
},
|
|
922
|
+
"peerDependencies": {
|
|
923
|
+
"vue": "^3.4.0",
|
|
924
|
+
"vue-router": "^4.0.0"
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
```
|
|
928
|
+
|
|
3
929
|
## Documentation
|
|
4
|
-
|
|
930
|
+
|
|
931
|
+
For detailed documentation, please visit [here](https://dadajam4.github.io/fastkit/vui/).
|
|
932
|
+
|
|
933
|
+
## License
|
|
934
|
+
|
|
935
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastkit/vui",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "A simple, extensible UI kit for Vue applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fastkit",
|
|
@@ -44,31 +44,31 @@
|
|
|
44
44
|
"src"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@fastkit/helpers": "^0.14.5",
|
|
47
48
|
"@fastkit/color-scheme": "^2.0.10",
|
|
48
|
-
"@fastkit/color-scheme-gen": "^0.14.13",
|
|
49
49
|
"@fastkit/dom": "^0.2.6",
|
|
50
|
-
"@fastkit/
|
|
50
|
+
"@fastkit/rules": "^0.14.7",
|
|
51
|
+
"@fastkit/color-scheme-gen": "^0.14.13",
|
|
51
52
|
"@fastkit/icon-font": "^2.0.7",
|
|
52
|
-
"@fastkit/
|
|
53
|
+
"@fastkit/tiny-logger": "^0.14.5",
|
|
53
54
|
"@fastkit/media-match-gen": "^0.14.13",
|
|
54
|
-
"@fastkit/
|
|
55
|
+
"@fastkit/vue-body-scroll-lock": "^0.2.13",
|
|
56
|
+
"@fastkit/media-match": "^2.0.7",
|
|
55
57
|
"@fastkit/stylebase": "^0.13.1",
|
|
56
|
-
"@fastkit/tiny-logger": "^0.14.5",
|
|
57
|
-
"@fastkit/vue-action": "^0.3.19",
|
|
58
58
|
"@fastkit/vue-app-layout": "^0.16.2",
|
|
59
|
-
"@fastkit/vue-
|
|
59
|
+
"@fastkit/vue-action": "^0.3.19",
|
|
60
60
|
"@fastkit/vue-click-outside": "^0.2.12",
|
|
61
|
+
"@fastkit/vue-keyboard": "^0.2.5",
|
|
61
62
|
"@fastkit/vue-color-scheme": "^0.15.15",
|
|
62
63
|
"@fastkit/vue-form-control": "^0.21.0",
|
|
63
|
-
"@fastkit/vue-keyboard": "^0.2.5",
|
|
64
|
-
"@fastkit/vue-loading": "^0.15.15",
|
|
65
64
|
"@fastkit/vue-location": "^0.4.2",
|
|
66
|
-
"@fastkit/vue-
|
|
65
|
+
"@fastkit/vue-loading": "^0.15.15",
|
|
67
66
|
"@fastkit/vue-resize": "^0.2.12",
|
|
68
67
|
"@fastkit/vue-scroller": "^0.15.12",
|
|
69
|
-
"@fastkit/vue-stack": "^0.
|
|
68
|
+
"@fastkit/vue-stack": "^0.17.0",
|
|
69
|
+
"@fastkit/vue-utils": "^0.15.12",
|
|
70
70
|
"@fastkit/vue-transitions": "^0.2.13",
|
|
71
|
-
"@fastkit/vue-
|
|
71
|
+
"@fastkit/vue-media-match": "^0.14.10"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"vue": "^3.4.0",
|