@paraxe/vue 1.0.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/.loba-build.json +5 -0
- package/dist/composites/Accordion.d.ts +22 -0
- package/dist/composites/AccordionItem.d.ts +21 -0
- package/dist/composites/ButtonGroup.d.ts +21 -0
- package/dist/composites/SectionHeader.d.ts +20 -0
- package/dist/data/EmptyState.d.ts +22 -0
- package/dist/data/List.d.ts +35 -0
- package/dist/data/ListItem.d.ts +19 -0
- package/dist/data/Pagination.d.ts +17 -0
- package/dist/data/Table.d.ts +51 -0
- package/dist/elements/Badge.d.ts +19 -0
- package/dist/elements/Button.d.ts +33 -0
- package/dist/elements/Card.d.ts +21 -0
- package/dist/elements/Image.d.ts +13 -0
- package/dist/elements/Tag.d.ts +13 -0
- package/dist/feedback/Alert.d.ts +27 -0
- package/dist/feedback/Progress.d.ts +29 -0
- package/dist/feedback/Skeleton.d.ts +13 -0
- package/dist/feedback/Toast.d.ts +33 -0
- package/dist/forms/Checkbox.d.ts +27 -0
- package/dist/forms/Choice.d.ts +19 -0
- package/dist/forms/ChoiceGroup.d.ts +32 -0
- package/dist/forms/FormField.d.ts +29 -0
- package/dist/forms/Input.d.ts +29 -0
- package/dist/forms/Radio.d.ts +26 -0
- package/dist/forms/Select.d.ts +28 -0
- package/dist/forms/Switch.d.ts +24 -0
- package/dist/forms/Textarea.d.ts +24 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +1888 -0
- package/dist/navigation/Breadcrumb.d.ts +22 -0
- package/dist/navigation/Breadcrumbs.d.ts +18 -0
- package/dist/navigation/Sidebar.d.ts +22 -0
- package/dist/navigation/SidebarGroup.d.ts +18 -0
- package/dist/navigation/SidebarItem.d.ts +28 -0
- package/dist/navigation/Tab.d.ts +19 -0
- package/dist/navigation/Tabs.d.ts +22 -0
- package/dist/overlays/Dialog.d.ts +26 -0
- package/dist/overlays/Drawer.d.ts +28 -0
- package/dist/overlays/Popover.d.ts +20 -0
- package/dist/overlays/Tooltip.d.ts +21 -0
- package/dist/primitives/Container.d.ts +19 -0
- package/dist/primitives/Divider.d.ts +3 -0
- package/dist/primitives/Grid.d.ts +25 -0
- package/dist/primitives/Section.d.ts +13 -0
- package/dist/primitives/Stack.d.ts +21 -0
- package/dist/typography/Eyebrow.d.ts +13 -0
- package/dist/typography/Heading.d.ts +19 -0
- package/dist/typography/Label.d.ts +24 -0
- package/dist/typography/Text.d.ts +22 -0
- package/doc.md +527 -0
- package/package.json +29 -0
- package/src/composites/Accordion.vue +60 -0
- package/src/composites/AccordionItem.vue +66 -0
- package/src/composites/ButtonGroup.vue +27 -0
- package/src/composites/SectionHeader.vue +23 -0
- package/src/data/EmptyState.vue +40 -0
- package/src/data/List.vue +125 -0
- package/src/data/ListItem.vue +97 -0
- package/src/data/Pagination.vue +254 -0
- package/src/data/Table.vue +382 -0
- package/src/elements/Avatar.vue +0 -0
- package/src/elements/Badge.vue +30 -0
- package/src/elements/Button.vue +56 -0
- package/src/elements/Card.vue +34 -0
- package/src/elements/Icon.vue +0 -0
- package/src/elements/Image.vue +36 -0
- package/src/elements/Tag.vue +10 -0
- package/src/env.d.ts +8 -0
- package/src/feedback/Alert.vue +63 -0
- package/src/feedback/Progress.vue +95 -0
- package/src/feedback/Skeleton.vue +43 -0
- package/src/feedback/Toast.vue +141 -0
- package/src/forms/Checkbox.vue +54 -0
- package/src/forms/Choice.vue +82 -0
- package/src/forms/ChoiceGroup.vue +130 -0
- package/src/forms/FormField.vue +55 -0
- package/src/forms/Input.vue +54 -0
- package/src/forms/Radio.vue +64 -0
- package/src/forms/Select.vue +43 -0
- package/src/forms/Switch.vue +57 -0
- package/src/forms/Textarea.vue +54 -0
- package/src/index.ts +47 -0
- package/src/navigation/Breadcrumb.vue +59 -0
- package/src/navigation/Breadcrumbs.vue +30 -0
- package/src/navigation/Menu.vue +0 -0
- package/src/navigation/Sidebar.vue +35 -0
- package/src/navigation/SidebarGroup.vue +34 -0
- package/src/navigation/SidebarItem.vue +137 -0
- package/src/navigation/Tab.vue +78 -0
- package/src/navigation/Tabs.vue +68 -0
- package/src/overlays/Dialog.vue +193 -0
- package/src/overlays/Drawer.vue +200 -0
- package/src/overlays/Popover.vue +106 -0
- package/src/overlays/Tooltip.vue +69 -0
- package/src/primitives/Container.vue +24 -0
- package/src/primitives/Divider.vue +6 -0
- package/src/primitives/Grid.vue +36 -0
- package/src/primitives/Section.vue +8 -0
- package/src/primitives/Stack.vue +28 -0
- package/src/typography/Eyebrow.vue +9 -0
- package/src/typography/Heading.vue +27 -0
- package/src/typography/Label.vue +34 -0
- package/src/typography/Text.vue +38 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +30 -0
package/doc.md
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
# Loba UI Vue
|
|
2
|
+
|
|
3
|
+
Loba UI Vue is the Vue implementation of the Loba Design System. It provides reusable application UI so projects can focus on product logic, data, and behaviour instead of rebuilding common interface patterns.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Import the components you need from `@paraxe/vue`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
12
|
+
Card,
|
|
13
|
+
Stack,
|
|
14
|
+
Grid,
|
|
15
|
+
Table,
|
|
16
|
+
Sidebar,
|
|
17
|
+
} from "@paraxe/vue";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Loba Design tokens and component styles must also be available to the project.
|
|
21
|
+
|
|
22
|
+
## Core Principle
|
|
23
|
+
|
|
24
|
+
Use Loba UI components for repeated UI responsibilities.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Loba UI
|
|
28
|
+
→ components
|
|
29
|
+
→ behaviour
|
|
30
|
+
→ states
|
|
31
|
+
→ accessibility
|
|
32
|
+
→ design consistency
|
|
33
|
+
|
|
34
|
+
Application
|
|
35
|
+
→ data
|
|
36
|
+
→ business logic
|
|
37
|
+
→ routing
|
|
38
|
+
→ API calls
|
|
39
|
+
→ page-specific composition
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Do not recreate a Loba component with custom HTML/CSS when the existing component already owns that responsibility.
|
|
43
|
+
|
|
44
|
+
## Layout
|
|
45
|
+
|
|
46
|
+
### Stack
|
|
47
|
+
|
|
48
|
+
Use `Stack` for vertical composition.
|
|
49
|
+
|
|
50
|
+
```vue
|
|
51
|
+
<Stack size="large" fullWidth>
|
|
52
|
+
<Heading level="h1">Prospects</Heading>
|
|
53
|
+
<Text>Manage your sales pipeline.</Text>
|
|
54
|
+
</Stack>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`Stack` is intentionally composition-focused. Avoid adding unnecessary page-level wrappers just to reproduce its spacing.
|
|
58
|
+
|
|
59
|
+
### Grid
|
|
60
|
+
|
|
61
|
+
Use `Grid` for structured columns.
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<Grid columns="3" size="medium">
|
|
65
|
+
<Card>...</Card>
|
|
66
|
+
<Card>...</Card>
|
|
67
|
+
<Card>...</Card>
|
|
68
|
+
</Grid>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Container
|
|
72
|
+
|
|
73
|
+
Use `Container` for page content boundaries.
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<Section>
|
|
77
|
+
<Container>
|
|
78
|
+
...
|
|
79
|
+
</Container>
|
|
80
|
+
</Section>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Card and Surfaces
|
|
84
|
+
|
|
85
|
+
`Card` is the standard bounded content container.
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<Card>
|
|
89
|
+
...
|
|
90
|
+
</Card>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Card variants control containment and depth:
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<Card variant="outlined">
|
|
97
|
+
...
|
|
98
|
+
</Card>
|
|
99
|
+
|
|
100
|
+
<Card variant="elevated">
|
|
101
|
+
...
|
|
102
|
+
</Card>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Surface classes modify the visual layer independently:
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<Card class="surface-raised">
|
|
109
|
+
...
|
|
110
|
+
</Card>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The important distinction is:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
Card
|
|
117
|
+
→ container / border / radius / elevation / interaction
|
|
118
|
+
|
|
119
|
+
surface-*
|
|
120
|
+
→ background visual layer
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Do not create a `<Surface>` wrapper around a Card.
|
|
124
|
+
|
|
125
|
+
## Forms
|
|
126
|
+
|
|
127
|
+
Loba UI provides:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
Input
|
|
131
|
+
Textarea
|
|
132
|
+
Select
|
|
133
|
+
FormField
|
|
134
|
+
Checkbox
|
|
135
|
+
Radio
|
|
136
|
+
Switch
|
|
137
|
+
ChoiceGroup
|
|
138
|
+
Choice
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use existing controls rather than recreating browser controls with application CSS.
|
|
142
|
+
|
|
143
|
+
`ChoiceGroup` is intended for prominent visual choices:
|
|
144
|
+
|
|
145
|
+
```vue
|
|
146
|
+
<ChoiceGroup
|
|
147
|
+
v-model="projectType"
|
|
148
|
+
class="choice-group--grid-2"
|
|
149
|
+
>
|
|
150
|
+
<Choice value="renovation">
|
|
151
|
+
Renovation
|
|
152
|
+
</Choice>
|
|
153
|
+
|
|
154
|
+
<Choice value="new-build">
|
|
155
|
+
New build
|
|
156
|
+
</Choice>
|
|
157
|
+
</ChoiceGroup>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Data
|
|
161
|
+
|
|
162
|
+
Use the Data components for application records:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
List
|
|
166
|
+
ListItem
|
|
167
|
+
Table
|
|
168
|
+
Pagination
|
|
169
|
+
EmptyState
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
For example:
|
|
173
|
+
|
|
174
|
+
```vue
|
|
175
|
+
<Table
|
|
176
|
+
:columns="columns"
|
|
177
|
+
:rows="rows"
|
|
178
|
+
sortable
|
|
179
|
+
selectable
|
|
180
|
+
multiple
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Let the component own its behaviour. Avoid wrapping a component in custom layout CSS when the component already provides that responsibility.
|
|
185
|
+
|
|
186
|
+
For example, Pagination should normally be used directly:
|
|
187
|
+
|
|
188
|
+
```vue
|
|
189
|
+
<Pagination
|
|
190
|
+
:current-page="page"
|
|
191
|
+
:total-items="totalItems"
|
|
192
|
+
:page-size="pageSize"
|
|
193
|
+
@update:current-page="page = $event"
|
|
194
|
+
/>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Navigation
|
|
198
|
+
|
|
199
|
+
### Sidebar
|
|
200
|
+
|
|
201
|
+
Sidebar is the persistent application shell.
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
Sidebar
|
|
205
|
+
├── header
|
|
206
|
+
├── navigation
|
|
207
|
+
│ ├── SidebarGroup
|
|
208
|
+
│ │ └── SidebarItem
|
|
209
|
+
│ └── ...
|
|
210
|
+
└── footer
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
|
|
215
|
+
```vue
|
|
216
|
+
<Sidebar :collapsed="collapsed">
|
|
217
|
+
<template #header>
|
|
218
|
+
...
|
|
219
|
+
</template>
|
|
220
|
+
|
|
221
|
+
<SidebarGroup label="Workspace">
|
|
222
|
+
<SidebarItem active href="/dashboard">
|
|
223
|
+
Dashboard
|
|
224
|
+
</SidebarItem>
|
|
225
|
+
|
|
226
|
+
<SidebarItem>
|
|
227
|
+
Sales
|
|
228
|
+
|
|
229
|
+
<template #children>
|
|
230
|
+
<SidebarItem href="/calls">
|
|
231
|
+
Calls
|
|
232
|
+
</SidebarItem>
|
|
233
|
+
|
|
234
|
+
<SidebarItem href="/follow-ups">
|
|
235
|
+
Follow-ups
|
|
236
|
+
</SidebarItem>
|
|
237
|
+
</template>
|
|
238
|
+
</SidebarItem>
|
|
239
|
+
</SidebarGroup>
|
|
240
|
+
|
|
241
|
+
<template #footer>
|
|
242
|
+
...
|
|
243
|
+
</template>
|
|
244
|
+
</Sidebar>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Sidebar is router-agnostic. The application controls navigation and active state.
|
|
248
|
+
|
|
249
|
+
### Tabs
|
|
250
|
+
|
|
251
|
+
Tabs switch between views of the same context.
|
|
252
|
+
|
|
253
|
+
```vue
|
|
254
|
+
<Tabs v-model="activeTab">
|
|
255
|
+
<Tab value="overview">
|
|
256
|
+
Overview
|
|
257
|
+
</Tab>
|
|
258
|
+
|
|
259
|
+
<Tab value="activity">
|
|
260
|
+
Activity
|
|
261
|
+
</Tab>
|
|
262
|
+
|
|
263
|
+
<Tab value="documents">
|
|
264
|
+
Documents
|
|
265
|
+
</Tab>
|
|
266
|
+
</Tabs>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The application owns the content:
|
|
270
|
+
|
|
271
|
+
```vue
|
|
272
|
+
<Card v-if="activeTab === 'overview'">
|
|
273
|
+
...
|
|
274
|
+
</Card>
|
|
275
|
+
|
|
276
|
+
<Card v-else-if="activeTab === 'activity'">
|
|
277
|
+
...
|
|
278
|
+
</Card>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Loba UI does not provide a separate `TabPanel` component.
|
|
282
|
+
|
|
283
|
+
### Breadcrumbs
|
|
284
|
+
|
|
285
|
+
Breadcrumbs communicate hierarchical location.
|
|
286
|
+
|
|
287
|
+
```vue
|
|
288
|
+
<Breadcrumbs separator="›">
|
|
289
|
+
<Breadcrumb href="/dashboard">
|
|
290
|
+
Dashboard
|
|
291
|
+
</Breadcrumb>
|
|
292
|
+
|
|
293
|
+
<Breadcrumb href="/prospects">
|
|
294
|
+
Prospects
|
|
295
|
+
</Breadcrumb>
|
|
296
|
+
|
|
297
|
+
<Breadcrumb current>
|
|
298
|
+
James Wilson
|
|
299
|
+
</Breadcrumb>
|
|
300
|
+
</Breadcrumbs>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Overlays
|
|
304
|
+
|
|
305
|
+
Loba UI provides four overlay patterns:
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
Dialog
|
|
309
|
+
→ focused task or decision
|
|
310
|
+
|
|
311
|
+
Drawer
|
|
312
|
+
→ larger contextual workspace
|
|
313
|
+
|
|
314
|
+
Popover
|
|
315
|
+
→ contextual interactive content
|
|
316
|
+
|
|
317
|
+
Tooltip
|
|
318
|
+
→ short non-interactive explanation
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Dialog
|
|
322
|
+
|
|
323
|
+
Use for focused tasks:
|
|
324
|
+
|
|
325
|
+
```vue
|
|
326
|
+
<Dialog v-model="showDialog">
|
|
327
|
+
<Stack size="large">
|
|
328
|
+
...
|
|
329
|
+
</Stack>
|
|
330
|
+
</Dialog>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Drawer
|
|
334
|
+
|
|
335
|
+
Use for larger contextual workspaces:
|
|
336
|
+
|
|
337
|
+
```vue
|
|
338
|
+
<Drawer v-model="showDrawer">
|
|
339
|
+
<Stack size="large">
|
|
340
|
+
...
|
|
341
|
+
</Stack>
|
|
342
|
+
</Drawer>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Popover
|
|
346
|
+
|
|
347
|
+
Use for interactive contextual UI:
|
|
348
|
+
|
|
349
|
+
```vue
|
|
350
|
+
<Popover>
|
|
351
|
+
<template #trigger>
|
|
352
|
+
<Button variant="secondary">
|
|
353
|
+
Filters
|
|
354
|
+
</Button>
|
|
355
|
+
</template>
|
|
356
|
+
|
|
357
|
+
...
|
|
358
|
+
</Popover>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Tooltip
|
|
362
|
+
|
|
363
|
+
Use for short explanations:
|
|
364
|
+
|
|
365
|
+
```vue
|
|
366
|
+
<Tooltip text="Delete prospect">
|
|
367
|
+
<Button variant="ghost">
|
|
368
|
+
Delete
|
|
369
|
+
</Button>
|
|
370
|
+
</Tooltip>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Feedback
|
|
374
|
+
|
|
375
|
+
Loba UI provides:
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
Alert
|
|
379
|
+
→ persistent in-flow message
|
|
380
|
+
|
|
381
|
+
Progress
|
|
382
|
+
→ operation completion
|
|
383
|
+
|
|
384
|
+
Skeleton
|
|
385
|
+
→ loading placeholder
|
|
386
|
+
|
|
387
|
+
Toast
|
|
388
|
+
→ temporary floating notification
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
These should not overlap in purpose.
|
|
392
|
+
|
|
393
|
+
## Composition Rules
|
|
394
|
+
|
|
395
|
+
Prefer:
|
|
396
|
+
|
|
397
|
+
```vue
|
|
398
|
+
<Card class="surface-raised">
|
|
399
|
+
<Stack size="large" fullWidth>
|
|
400
|
+
<Heading level="h2">
|
|
401
|
+
Pipeline
|
|
402
|
+
</Heading>
|
|
403
|
+
|
|
404
|
+
<Table ... />
|
|
405
|
+
</Stack>
|
|
406
|
+
</Card>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
over creating custom page-specific UI components for every repeated visual pattern.
|
|
410
|
+
|
|
411
|
+
Create a new component only when a pattern has a **real reusable responsibility**.
|
|
412
|
+
|
|
413
|
+
Avoid:
|
|
414
|
+
|
|
415
|
+
```text
|
|
416
|
+
ProspectCard
|
|
417
|
+
DashboardPanel
|
|
418
|
+
ToolbarWrapper
|
|
419
|
+
TableContainer
|
|
420
|
+
SectionBox
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
unless repeated application work proves that they deserve to exist.
|
|
424
|
+
|
|
425
|
+
## Custom CSS
|
|
426
|
+
|
|
427
|
+
Custom CSS is allowed.
|
|
428
|
+
|
|
429
|
+
Loba UI is not intended to prevent application-specific styling. It is intended to remove unnecessary repetition.
|
|
430
|
+
|
|
431
|
+
Use custom CSS for:
|
|
432
|
+
|
|
433
|
+
```text
|
|
434
|
+
page-specific layout
|
|
435
|
+
brand-specific compositions
|
|
436
|
+
unique visual treatments
|
|
437
|
+
application-specific responsive behaviour
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Do not use custom CSS to recreate:
|
|
441
|
+
|
|
442
|
+
```text
|
|
443
|
+
buttons
|
|
444
|
+
cards
|
|
445
|
+
forms
|
|
446
|
+
tables
|
|
447
|
+
navigation
|
|
448
|
+
dialogs
|
|
449
|
+
drawers
|
|
450
|
+
feedback
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
when Loba UI already provides them.
|
|
454
|
+
|
|
455
|
+
## Design Tokens
|
|
456
|
+
|
|
457
|
+
Loba Design separates:
|
|
458
|
+
|
|
459
|
+
```text
|
|
460
|
+
tokens.css
|
|
461
|
+
→ structural values
|
|
462
|
+
|
|
463
|
+
themes/*.css
|
|
464
|
+
→ colours / semantic theme values
|
|
465
|
+
|
|
466
|
+
components.css
|
|
467
|
+
→ component implementation styles
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Components should consume existing tokens instead of inventing duplicate values.
|
|
471
|
+
|
|
472
|
+
For example:
|
|
473
|
+
|
|
474
|
+
```css
|
|
475
|
+
background: var(--surface-raised);
|
|
476
|
+
border-color: var(--border-default);
|
|
477
|
+
color: var(--text-primary);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Framework Responsibility
|
|
481
|
+
|
|
482
|
+
Loba UI Vue provides Vue-specific implementations.
|
|
483
|
+
|
|
484
|
+
The design language remains in Loba Design so the same system can eventually support other frameworks.
|
|
485
|
+
|
|
486
|
+
The Vue layer should therefore focus on:
|
|
487
|
+
|
|
488
|
+
```text
|
|
489
|
+
props
|
|
490
|
+
events
|
|
491
|
+
slots
|
|
492
|
+
state
|
|
493
|
+
Vue behaviour
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
while shared visual decisions remain in Loba Design.
|
|
497
|
+
|
|
498
|
+
## Building an Application
|
|
499
|
+
|
|
500
|
+
A typical Loba application can be structured around:
|
|
501
|
+
|
|
502
|
+
```text
|
|
503
|
+
App Shell
|
|
504
|
+
├── Sidebar
|
|
505
|
+
│
|
|
506
|
+
└── Main
|
|
507
|
+
├── Breadcrumbs
|
|
508
|
+
├── SectionHeader
|
|
509
|
+
├── Cards
|
|
510
|
+
├── Forms
|
|
511
|
+
├── Data
|
|
512
|
+
├── Tabs
|
|
513
|
+
├── Dialog / Drawer
|
|
514
|
+
└── Feedback
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The intended workflow is:
|
|
518
|
+
|
|
519
|
+
```text
|
|
520
|
+
Choose the Loba component
|
|
521
|
+
→ compose it
|
|
522
|
+
→ connect application state
|
|
523
|
+
→ add business logic
|
|
524
|
+
→ add custom CSS only when genuinely necessary
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Loba UI exists to make application building faster without replacing the developer's understanding of HTML, CSS, JavaScript, Vue, or TypeScript.
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@paraxe/vue",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "vite",
|
|
16
|
+
"build": "vite build",
|
|
17
|
+
"typecheck": "vue-tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@paraxe/core": "workspace:*",
|
|
21
|
+
"vue": "^3.5.13"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
25
|
+
"vite": "^7.1.2",
|
|
26
|
+
"vue-tsc": "^3.0.5",
|
|
27
|
+
"typescript": "^5.9.2"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { provide, ref, computed } from "vue";
|
|
3
|
+
|
|
4
|
+
type AccordionContext = {
|
|
5
|
+
openItems: Readonly<ReturnType<typeof ref<string[]>>>;
|
|
6
|
+
isOpen: (id: string) => boolean;
|
|
7
|
+
toggleItem: (id: string) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
multiple?: boolean;
|
|
13
|
+
collapsible?: boolean;
|
|
14
|
+
defaultOpen?: string[];
|
|
15
|
+
}>(),
|
|
16
|
+
{
|
|
17
|
+
multiple: false,
|
|
18
|
+
collapsible: true,
|
|
19
|
+
defaultOpen: () => [],
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const openItems = ref<string[]>([...props.defaultOpen]);
|
|
24
|
+
|
|
25
|
+
function isOpen(id: string) {
|
|
26
|
+
return openItems.value.includes(id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function toggleItem(id: string) {
|
|
30
|
+
const currentlyOpen = isOpen(id);
|
|
31
|
+
|
|
32
|
+
if (currentlyOpen) {
|
|
33
|
+
if (!props.collapsible) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
openItems.value = openItems.value.filter((item) => item !== id);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (props.multiple) {
|
|
42
|
+
openItems.value = [...openItems.value, id];
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
openItems.value = [id];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
provide("loba-accordion", {
|
|
50
|
+
openItems,
|
|
51
|
+
isOpen,
|
|
52
|
+
toggleItem,
|
|
53
|
+
} satisfies AccordionContext);
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div class="accordion">
|
|
58
|
+
<slot />
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject } from "vue";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
id: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
10
|
+
disabled: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
type AccordionContext = {
|
|
14
|
+
isOpen: (id: string) => boolean;
|
|
15
|
+
toggleItem: (id: string) => void;
|
|
16
|
+
};
|
|
17
|
+
const injectedAccordion = inject<AccordionContext>("loba-accordion");
|
|
18
|
+
|
|
19
|
+
if (!injectedAccordion) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"AccordionItem must be used inside an Accordion.",
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const accordion = injectedAccordion;
|
|
26
|
+
|
|
27
|
+
if (!accordion) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
"AccordionItem must be used inside an Accordion.",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isOpen = computed(() => accordion.isOpen(props.id));
|
|
34
|
+
|
|
35
|
+
function toggle() {
|
|
36
|
+
if (!props.disabled) {
|
|
37
|
+
accordion.toggleItem(props.id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div
|
|
44
|
+
class="accordion-item"
|
|
45
|
+
:class="{ 'accordion-item--open': isOpen }"
|
|
46
|
+
>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
class="accordion-trigger"
|
|
50
|
+
:disabled="props.disabled"
|
|
51
|
+
:aria-expanded="isOpen"
|
|
52
|
+
:aria-controls="`accordion-${props.id}`"
|
|
53
|
+
@click="toggle"
|
|
54
|
+
>
|
|
55
|
+
<slot name="trigger" />
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<div
|
|
59
|
+
v-show="isOpen"
|
|
60
|
+
:id="`accordion-${props.id}`"
|
|
61
|
+
class="accordion-content"
|
|
62
|
+
>
|
|
63
|
+
<slot />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
type ButtonGroupAlign = "start" | "center" | "end";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
align?: ButtonGroupAlign;
|
|
8
|
+
stack?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
12
|
+
align: "start",
|
|
13
|
+
stack: false,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const buttonGroupClasses = computed(() => ({
|
|
17
|
+
"button-group--center": props.align === "center",
|
|
18
|
+
"button-group--end": props.align === "end",
|
|
19
|
+
"button-group--stack": props.stack,
|
|
20
|
+
}));
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div class="button-group" :class="buttonGroupClasses">
|
|
25
|
+
<slot />
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
width?: "default" | "wide";
|
|
4
|
+
align?: "left" | "center";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
8
|
+
width: "default",
|
|
9
|
+
align: "left",
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<header
|
|
15
|
+
class="section-header"
|
|
16
|
+
:class="{
|
|
17
|
+
'section-header--wide': props.width === 'wide',
|
|
18
|
+
'section-header--center': props.align === 'center',
|
|
19
|
+
}"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
</header>
|
|
23
|
+
</template>
|