@openreachtech/furo-vue 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +2 -0
- package/README.md +2 -0
- package/docs/design-tokens.md +419 -0
- package/docs/furo-accordion.md +109 -0
- package/docs/furo-alert-dialog.md +107 -0
- package/docs/furo-attachment.md +133 -0
- package/docs/furo-autocomplete-field.md +719 -0
- package/docs/furo-avatar-group.md +87 -0
- package/docs/furo-avatar.md +101 -0
- package/docs/furo-badge.md +126 -0
- package/docs/furo-breadcrumb.md +89 -0
- package/docs/furo-button.md +263 -0
- package/docs/furo-checkbox.md +384 -0
- package/docs/furo-collapsible.md +104 -0
- package/docs/furo-control-block.md +102 -0
- package/docs/furo-date-picker.md +270 -0
- package/docs/furo-date-time-picker.md +110 -0
- package/docs/furo-dialog.md +88 -0
- package/docs/furo-drawer.md +122 -0
- package/docs/furo-dropdown-menu.md +298 -0
- package/docs/furo-editable-field.md +391 -0
- package/docs/furo-editor.md +304 -0
- package/docs/furo-email-field.md +93 -0
- package/docs/furo-empty-state.md +97 -0
- package/docs/furo-error-state.md +97 -0
- package/docs/furo-file-field.md +163 -0
- package/docs/furo-icon.md +288 -0
- package/docs/furo-number-field.md +397 -0
- package/docs/furo-pagination.md +101 -0
- package/docs/furo-password-field.md +75 -0
- package/docs/furo-popover.md +114 -0
- package/docs/furo-radio.md +125 -0
- package/docs/furo-scroll-area.md +112 -0
- package/docs/furo-search-field.md +94 -0
- package/docs/furo-section-header.md +135 -0
- package/docs/furo-select.md +443 -0
- package/docs/furo-separator.md +87 -0
- package/docs/furo-sidebar.md +178 -0
- package/docs/furo-skeleton.md +92 -0
- package/docs/furo-splitter.md +103 -0
- package/docs/furo-stepper.md +127 -0
- package/docs/furo-switch.md +107 -0
- package/docs/furo-table.md +233 -0
- package/docs/furo-tabs.md +173 -0
- package/docs/furo-text-field.md +249 -0
- package/docs/furo-textarea.md +214 -0
- package/docs/furo-time-field.md +276 -0
- package/docs/furo-toast.md +217 -0
- package/docs/furo-toggle-group.md +123 -0
- package/docs/furo-toggle.md +104 -0
- package/docs/furo-tool-bar.md +88 -0
- package/docs/furo-tooltip.md +107 -0
- package/lib/assets/css/furo/0030.variables-semantic-color.css +15 -1
- package/lib/components/atoms/FuroButton/FuroButtonContext.js +8 -8
- package/lib/components/atoms/FuroCheckbox/FuroCheckboxContext.js +1 -1
- package/lib/components/atoms/FuroIcon/FuroIcon.vue +1 -1
- package/lib/components/atoms/FuroSwitch/FuroSwitchContext.js +1 -1
- package/lib/components/atoms/FuroToggle/FuroToggleContext.js +1 -1
- package/lib/components/molecules/FuroAccordion/FuroAccordionContext.js +1 -1
- package/lib/components/molecules/FuroAttachment/FuroAttachment.vue +1 -1
- package/lib/components/molecules/FuroBreadcrumb/FuroBreadcrumb.vue +1 -1
- package/lib/components/molecules/FuroCollapsible/FuroCollapsibleContext.js +1 -1
- package/lib/components/molecules/FuroControlBlock/FuroControlBlock.vue +2 -2
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenu.vue +1 -1
- package/lib/components/molecules/FuroDropdownMenu/FuroDropdownMenuContext.js +1 -2
- package/lib/components/molecules/FuroErrorState/FuroErrorState.vue +1 -1
- package/lib/components/molecules/FuroPopover/FuroPopoverContext.js +1 -1
- package/lib/components/molecules/FuroSearchField/FuroSearchField.vue +1 -1
- package/lib/components/molecules/FuroSelect/FuroSelect.vue +8 -6
- package/lib/components/molecules/FuroSelect/FuroSelectContext.js +35 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionText.vue +40 -0
- package/lib/components/molecules/FuroSelect/FuroSelectOptionTextContext.js +275 -0
- package/lib/components/molecules/FuroSplitter/FuroSplitterContext.js +1 -1
- package/lib/components/molecules/FuroToggleGroup/FuroToggleGroupContext.js +1 -1
- package/lib/components/molecules/FuroTooltip/FuroTooltipContext.js +1 -1
- package/lib/components/organisms/FuroSidebar/FuroSidebar.vue +3 -3
- package/lib/components/organisms/FuroTable/FuroTable.vue +1 -1
- package/lib/components/organisms/FuroTable/FuroTableContext.js +3 -3
- package/lib/components/organisms/FuroToast/FuroToastContext.js +6 -2
- package/lib/components/organisms/FuroToast/ToastQueue.js +4 -4
- package/nuxt/index.js +9 -9
- package/package.json +23 -22
- package/public/furo-vue/components.json +1 -1
- package/types/generated/components/atoms/FuroButton/FuroButtonContext.d.ts +4 -4
- package/types/generated/components/molecules/FuroSelect/FuroSelect.vue.d.ts +23 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectContext.d.ts +14 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionText.vue.d.ts +50 -0
- package/types/generated/components/molecules/FuroSelect/FuroSelectOptionTextContext.d.ts +141 -0
- package/types/generated/components/organisms/FuroTable/FuroTableContext.d.ts +2 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# FuroDatePicker
|
|
2
|
+
|
|
3
|
+
Single-date molecule built on a headless date-picker primitive. Features an inline date field (segments for month/day/year) plus a calendar popover with dropdown selects for month and year navigation, the usual date-of-birth picker pattern.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Layer** | Molecule |
|
|
10
|
+
| **Path** | `lib/components/molecules/FuroDatePicker/` |
|
|
11
|
+
| **Base primitive** | Headless date-picker (`Root` + calendar popover) |
|
|
12
|
+
| **Value type** | `YYYY-MM-DD` ISO string or `null` |
|
|
13
|
+
| **Granularity** | `day` (date-only; no time) |
|
|
14
|
+
| **Timezone** | Configurable via `timezone` parcel key (default: timezone-naive) |
|
|
15
|
+
| **Calendar nav** | Dropdown selects for month and year (+ Prev/Next buttons) |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Public API
|
|
20
|
+
|
|
21
|
+
### Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| `parcel` | `DatePickerParcel \| null` | `null` | Reactive data object. Furo-only keys are stripped before passthrough to the underlying primitive. |
|
|
26
|
+
| `triggerParcel` | `Record<string, unknown> \| null` | `null` | Passed to the date field / trigger via v-bind. |
|
|
27
|
+
| `contentParcel` | `Record<string, unknown> \| null` | `null` | Passed to the popover content via v-bind. |
|
|
28
|
+
|
|
29
|
+
### Parcel keys
|
|
30
|
+
|
|
31
|
+
| Key | Type | Description |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `value` | `string \| null` | Currently selected date as `YYYY-MM-DD` or `null`. |
|
|
34
|
+
| `invalid` | `boolean` | Shows error chrome and sets `aria-invalid`. |
|
|
35
|
+
| `placeholder` | `string \| null` | Placeholder text for the date field. |
|
|
36
|
+
| `disabled` | `boolean` | Disables all interaction. |
|
|
37
|
+
| `minValue` | `string \| null` | Minimum selectable date (`YYYY-MM-DD`). Earlier dates are non-interactive. |
|
|
38
|
+
| `maxValue` | `string \| null` | Maximum selectable date (`YYYY-MM-DD`). Later dates are non-interactive. |
|
|
39
|
+
| `displayFormat` | `string \| null` | Reserved for v1.1 localized display format (not yet applied). |
|
|
40
|
+
| `timezone` | `string \| null` | IANA timezone identifier (e.g. `'Asia/Tokyo'`, `'UTC'`). When set, `Date` object inputs are interpreted in this timezone before normalization. Has no effect when `value` is a `YYYY-MM-DD` string (already date-only). Defaults to `null` (timezone-naive: uses local date parts). |
|
|
41
|
+
| `closeOnSelect` | `boolean \| null` | Whether the popover closes when a date is selected. Defaults to `true`. `FuroDateTimePicker` passes `false` to keep the popover open for time selection. |
|
|
42
|
+
| `locale` | `string \| null` | Locale that drives the date field's segment **order** (e.g. `dd/mm/yyyy` vs `mm/dd/yyyy`). Defaults to the browser locale (`navigator.language`), falling back to `'en'` outside the browser. Filled day/month segments are always shown 2-digit regardless of locale (see `formatSegmentValue`). |
|
|
43
|
+
| `preventDeselect` | `boolean \| null` | When `true` (default), re-selecting the already-selected date **keeps** it instead of clearing it. Pass `false` to restore the toggle-off behavior. |
|
|
44
|
+
| `todayButtonText` | `string \| null` | Label for the footer **Today** button. Defaults to `'Today'`; pass `null` to hide the button. |
|
|
45
|
+
| `clearButtonText` | `string \| null` | Label for the footer **Clear** button. Defaults to `'Clear'`; pass `null` to hide the button. |
|
|
46
|
+
|
|
47
|
+
All parcel keys above are **Furo-only** — they are stripped before the v-bind spread onto the root. Pass additional primitive props via `parcel` to forward them.
|
|
48
|
+
|
|
49
|
+
### Emits
|
|
50
|
+
|
|
51
|
+
| Event | Payload | Description |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `change-value` | `DatePickerEmitPayload` | Fired when the user selects a date. |
|
|
54
|
+
| `commit-value` | `DatePickerEmitPayload` | Fired on the same selection event (single-date close-on-select UX). |
|
|
55
|
+
| `update:value` | `string \| null` | ISO `YYYY-MM-DD` string or `null`. Suitable for `v-model:value`. |
|
|
56
|
+
|
|
57
|
+
### Slots
|
|
58
|
+
|
|
59
|
+
| Slot | Description |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `trigger-icon` | Replaces the default calendar icon inside the trigger. |
|
|
62
|
+
| `prev-icon` | Replaces the left-arrow icon in the calendar header. |
|
|
63
|
+
| `next-icon` | Replaces the right-arrow icon in the calendar header. |
|
|
64
|
+
| `field-suffix` | Renders custom content inside the trigger field, after the date segments and before the trigger icon. A no-op when empty. Used by `FuroDateTimePicker` to host an inline, directly-editable time field in the closed field. |
|
|
65
|
+
| `content-footer` | Renders custom content inside the popover, below the calendar. A no-op when empty. Used by `FuroDateTimePicker` to inject a time field into the same popover. |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
### Basic
|
|
72
|
+
|
|
73
|
+
```vue
|
|
74
|
+
<FuroDatePicker
|
|
75
|
+
:parcel="{ placeholder: 'Pick a date' }"
|
|
76
|
+
@update:value="onValueChange"
|
|
77
|
+
/>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### v-model binding
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<FuroDatePicker
|
|
84
|
+
v-model:value="selectedDate"
|
|
85
|
+
:parcel="{ placeholder: 'Pick a date' }"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Min / Max constraints
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<FuroDatePicker
|
|
93
|
+
:parcel="{
|
|
94
|
+
placeholder: 'Booking date',
|
|
95
|
+
minValue: '2024-06-01',
|
|
96
|
+
maxValue: '2024-12-31',
|
|
97
|
+
}"
|
|
98
|
+
@update:value="onValueChange"
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
When no value is set, the calendar view and the date segment placeholders default to **today clamped into `[minValue, maxValue]`** (`resolveCalendarPlaceholder()`). This keeps the year segment within the allowed range when bounds are set but today falls outside them — otherwise the year segment would render blank.
|
|
103
|
+
|
|
104
|
+
### Invalid state
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<FuroDatePicker
|
|
108
|
+
:parcel="{
|
|
109
|
+
placeholder: 'Required field',
|
|
110
|
+
invalid: isFieldInvalid,
|
|
111
|
+
}"
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Disabled state
|
|
116
|
+
|
|
117
|
+
```vue
|
|
118
|
+
<FuroDatePicker
|
|
119
|
+
:parcel="{
|
|
120
|
+
placeholder: 'Not editable',
|
|
121
|
+
disabled: true,
|
|
122
|
+
}"
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Japan Standard Time (CloudLink spec)
|
|
127
|
+
|
|
128
|
+
When the consumer always operates in JST (UTC+9) — e.g. CloudLink CRM — pass `timezone: 'Asia/Tokyo'`
|
|
129
|
+
so that `Date` object inputs (which are UTC-based internally) are interpreted on the correct calendar day.
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<!-- String value: already date-only, timezone has no effect -->
|
|
133
|
+
<FuroDatePicker
|
|
134
|
+
:parcel="{
|
|
135
|
+
value: '2024-01-15',
|
|
136
|
+
timezone: 'Asia/Tokyo',
|
|
137
|
+
placeholder: 'Select date',
|
|
138
|
+
}"
|
|
139
|
+
@update:value="onValueChange"
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<!-- Date object: extracted in JST before normalization -->
|
|
143
|
+
<!-- new Date('2024-01-14T15:30:00Z') → JST 2024-01-15 00:30 → shown as 2024-01-15 -->
|
|
144
|
+
<FuroDatePicker
|
|
145
|
+
:parcel="{
|
|
146
|
+
value: utcDateObject,
|
|
147
|
+
timezone: 'Asia/Tokyo',
|
|
148
|
+
placeholder: 'Select date',
|
|
149
|
+
}"
|
|
150
|
+
@update:value="onValueChange"
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The emitted `update:value` is always a `YYYY-MM-DD` string (date-only). The timezone only affects how incoming `Date` objects are mapped to a calendar day.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Value contract
|
|
159
|
+
|
|
160
|
+
| Concern | Decision |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Wire format | `YYYY-MM-DD` string — context accepts `Date` objects and `CalendarDate` at boundary and normalizes |
|
|
163
|
+
| Timezone | Configurable via `parcel.timezone` (IANA string). When `null` (default), `Date` objects use local date parts. When set, date parts are extracted via `Intl.DateTimeFormat` in that timezone. YYYY-MM-DD string inputs are always timezone-neutral. |
|
|
164
|
+
| Range | Single-date only; range support deferred to v2 |
|
|
165
|
+
| Clear | Pass `null` to `parcel.value` to clear the field programmatically |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## CSS classes
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
Field (inline date input)
|
|
173
|
+
.unit-form-control.date-picker ← host div
|
|
174
|
+
.unit-form-control.date-picker > .field ← date field
|
|
175
|
+
.unit-form-control.date-picker > .field.invalid ← invalid state
|
|
176
|
+
.unit-form-control.date-picker > .field.disabled ← disabled state
|
|
177
|
+
.unit-form-control.date-picker > .field > .field-input ← segment (mm/dd/yyyy)
|
|
178
|
+
.unit-form-control.date-picker > .field > .field-input[data-placeholder] ← placeholder segment
|
|
179
|
+
.unit-form-control.date-picker > .field > .field-input[data-focused] ← keyboard-active segment
|
|
180
|
+
.unit-form-control.date-picker > .field > .trigger ← calendar icon button
|
|
181
|
+
|
|
182
|
+
Popover (portaled)
|
|
183
|
+
.unit-date-picker-content ← popover content
|
|
184
|
+
.unit-date-picker-content > .calendar ← calendar
|
|
185
|
+
.unit-date-picker-content > .calendar-actions ← Today/Clear footer bar
|
|
186
|
+
.unit-date-picker-content > .calendar-actions > .calendar-action.today ← Today button
|
|
187
|
+
.unit-date-picker-content > .calendar-actions > .calendar-action.clear ← Clear button
|
|
188
|
+
|
|
189
|
+
Calendar header
|
|
190
|
+
.unit-date-picker-content > .calendar > .calendar-header ← header row
|
|
191
|
+
.unit-date-picker-content > .calendar > .calendar-header > .calendar-nav-button ← Prev / Next
|
|
192
|
+
.unit-date-picker-content > .calendar > .calendar-header > .calendar-caption ← dropdown wrapper
|
|
193
|
+
.unit-date-picker-content > .calendar > .calendar-header > .calendar-caption > .caption-select ← month/year <select>
|
|
194
|
+
|
|
195
|
+
Calendar grid
|
|
196
|
+
.unit-date-picker-content > .calendar > .calendar-grids ← grid container
|
|
197
|
+
.unit-date-picker-content > .calendar > .calendar-grids > .calendar-grid ← <table>
|
|
198
|
+
… > .calendar-head-row > .calendar-head-cell ← weekday labels
|
|
199
|
+
… > .calendar-row > .calendar-cell ← date cell
|
|
200
|
+
… > .calendar-row > .calendar-cell > .calendar-cell-trigger ← date button
|
|
201
|
+
… > .calendar-cell-trigger[data-selected] ← selected date
|
|
202
|
+
… > .calendar-cell-trigger[data-today] ← today (dot indicator)
|
|
203
|
+
… > .calendar-cell-trigger[data-focused] ← keyboard-focused date
|
|
204
|
+
… > .calendar-cell-trigger[data-disabled] ← out-of-range date
|
|
205
|
+
… > .calendar-cell-trigger[data-outside-view] ← adjacent month dates
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
All CSS is scoped under `@layer furo` and uses semantic tokens only (`--color-*`, `--size-*`, `--font-size-*`).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Context: `FuroDatePickerContext`
|
|
213
|
+
|
|
214
|
+
`FuroDatePickerContext` (extends `BaseFuroContext`) holds all reactive state and computed logic. The Vue component is a thin template wrapper.
|
|
215
|
+
|
|
216
|
+
Key methods:
|
|
217
|
+
|
|
218
|
+
| Method | Description |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `normalizeToCalendarDate({ raw })` | Converts `YYYY-MM-DD` / `Date` / `CalendarDate` / `null` → `CalendarDate \| null`. When `parcel.timezone` is set, delegates `Date` objects to `normalizeDateToCalendarDateInTimezone`. |
|
|
221
|
+
| `normalizeDateToCalendarDate({ raw })` | Converts `Date` → `CalendarDate`. Uses `parcel.timezone` when set; otherwise uses local date parts. |
|
|
222
|
+
| `normalizeDateToCalendarDateInTimezone({ raw, timezone })` | Converts `Date` → `CalendarDate` in a specific IANA timezone via `Intl.DateTimeFormat`. Returns `null` for invalid timezone strings. |
|
|
223
|
+
| `extractDatePartsInTimezone({ raw, timezone })` | Returns `{ year, month, day }` string parts extracted by `Intl.DateTimeFormat` for a given timezone. |
|
|
224
|
+
| `normalizeToIsoString({ calendarDate })` | Converts `CalendarDate \| null` → `YYYY-MM-DD` string or `null` |
|
|
225
|
+
| `normalizeMinValue()` | Returns `CalendarDate` from `parcel.minValue` or `undefined` |
|
|
226
|
+
| `normalizeMaxValue()` | Returns `CalendarDate` from `parcel.maxValue` or `undefined` |
|
|
227
|
+
| `extractRootAttributes()` | Builds the root attribute object (strips Furo-only keys, sets `granularity: 'day'`, `closeOnSelect: true`, injects `placeholder` for calendar nav) |
|
|
228
|
+
| `extractCalendarMonths()` | Returns the 12-month array (`{ value, label }[]`) for the month dropdown |
|
|
229
|
+
| `extractCalendarYears()` | Returns year options (`{ value }[]`) bounded by `minValue`/`maxValue` (defaults: −100 / +50 from today) |
|
|
230
|
+
| `stripFuroParcelKeys()` | Removes `FURO_DATE_PICKER_PARCEL_KEYS` from parcel before spreading to the underlying primitive (includes `timezone`) |
|
|
231
|
+
| `onChangeValue({ calendarDate })` | Handles the primitive's `update:modelValue`; emits all three events |
|
|
232
|
+
| `onPlaceholderChange({ calendarDate })` | Syncs `placeholderRef` when Prev/Next navigation fires `update:placeholder` |
|
|
233
|
+
| `onMonthChange({ changeEvent, currentYear })` | Navigates calendar to selected month; reads value from `HTMLSelectElement` |
|
|
234
|
+
| `onYearChange({ changeEvent, currentMonth })` | Navigates calendar to selected year; reads value from `HTMLSelectElement` |
|
|
235
|
+
| `formatSegmentValue({ part, value })` | Zero-pads filled day/month segments to two digits (`3` → `03`, `5` → `05`); year, placeholders, and literals pass through. The underlying primitive renders filled day/month as single digits, so this restores the 2-digit display. |
|
|
236
|
+
| `resolveCloseOnSelect()` | Resolves `parcel.closeOnSelect` (default `true`) — the popover's close-on-select behavior. |
|
|
237
|
+
| `resolveLocale()` | Resolves `parcel.locale`, defaulting to the browser locale, then `'en'` — drives segment order. |
|
|
238
|
+
| `resolvePreventDeselect()` | Resolves `parcel.preventDeselect` (default `true`) — keeps the date when its cell is re-clicked. |
|
|
239
|
+
| `onSelectToday()` | Footer **Today** button handler — selects today clamped into `[minValue, maxValue]` and emits the change trio. |
|
|
240
|
+
| `onClearValue()` | Footer **Clear** button handler — clears the value and emits the change trio with `null`. |
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Files
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
lib/components/molecules/FuroDatePicker/
|
|
248
|
+
├── FuroDatePicker.vue ← component
|
|
249
|
+
├── FuroDatePickerContext.js ← context
|
|
250
|
+
└── DatePickerEmitPayload.js ← typed emit payload
|
|
251
|
+
|
|
252
|
+
pages/samples/components/playground/FuroDatePickerPlayground/
|
|
253
|
+
├── index.vue ← playground page
|
|
254
|
+
└── FuroDatePickerPlaygroundContext.js
|
|
255
|
+
|
|
256
|
+
tests/__tests__/jsdom/lib/components/molecules/FuroDatePicker/
|
|
257
|
+
├── FuroDatePickerContext.js ← context tests
|
|
258
|
+
└── DatePickerEmitPayload.js ← payload tests
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Out of scope (v1)
|
|
264
|
+
|
|
265
|
+
- Time segments (`FuroTimeField` — Story 1.4)
|
|
266
|
+
- Combined datetime molecule (`FuroDateTimePicker` — Story 1.5)
|
|
267
|
+
- Date range / dual-calendar picker
|
|
268
|
+
- CRM adapter wiring
|
|
269
|
+
- Today/Clear localized button bar
|
|
270
|
+
- i18n locale packs (format string is prop-only v1)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# FuroDateTimePicker
|
|
2
|
+
|
|
3
|
+
Combined date + time form control. Composes `FuroDatePicker` (calendar) and `FuroTimeField` (segmented time) into a **single popover** and exposes one combined `v-model:value` as a canonical `YYYY-MM-DDTHH:MM:SS` string. Intended as the `datetime` field editor for the consumer-built ListView / DetailView / FormSequence templates (in `crm-kit-frontend`).
|
|
4
|
+
|
|
5
|
+
The molecule renders `FuroDatePicker` and injects `FuroTimeField` into the date picker's `content-footer` slot, so the calendar stays single-sourced — there is no duplicated calendar logic.
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Layer** | Molecule |
|
|
12
|
+
| **Path** | `lib/components/molecules/FuroDateTimePicker/` |
|
|
13
|
+
| **Composes** | `FuroDatePicker` + `FuroTimeField` (the time field is injected into the calendar popover) |
|
|
14
|
+
| **Value type** | `YYYY-MM-DDTHH:MM:SS` ISO string or `null` |
|
|
15
|
+
| **Wire format** | Always second-precision; seconds default to `00`. `granularity` controls only the visible time segments. |
|
|
16
|
+
| **Timezone** | Naive v1; `timezone` parcel key is forwarded to both atoms (the atoms own `Date` normalization). |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Public API
|
|
21
|
+
|
|
22
|
+
### Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| `parcel` | `DateTimePickerParcel \| null` | `null` | Reactive data object. Molecule-only keys are consumed here and used to derive the child parcels. |
|
|
27
|
+
|
|
28
|
+
### Parcel keys
|
|
29
|
+
|
|
30
|
+
| Key | Type | Description |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `value` | `string \| null` | Combined value as `YYYY-MM-DDTHH:MM:SS` or `null`. The context splits it into date/time parts and recombines on change. |
|
|
33
|
+
| `invalid` | `boolean` | Shows error chrome and sets `aria-invalid` on the unit shell; also forwarded to both atoms. |
|
|
34
|
+
| `placeholder` | `string \| null` | Placeholder text for the date field. |
|
|
35
|
+
| `disabled` | `boolean` | Disables all interaction. |
|
|
36
|
+
| `minValue` | `string \| null` | Minimum selectable datetime (`YYYY-MM-DDTHH:MM:SS`). See cross-field rule below. |
|
|
37
|
+
| `maxValue` | `string \| null` | Maximum selectable datetime (`YYYY-MM-DDTHH:MM:SS`). See cross-field rule below. |
|
|
38
|
+
| `timezone` | `string \| null` | IANA timezone identifier forwarded to both atoms. Default `null` (timezone-naive). |
|
|
39
|
+
| `granularity` | `'hour' \| 'minute' \| 'second' \| null` | Visible time segments. Does not change the wire format (always second precision). |
|
|
40
|
+
| `step` | `number \| null` | Step amount for the time field's increment/decrement buttons. |
|
|
41
|
+
| `timeFormat` | `number \| null` | `12` or `24`. When unset, the locale default applies. |
|
|
42
|
+
|
|
43
|
+
Consumer passthrough keys (e.g. `name`) that are not in the list above are forwarded to **both** child atoms unchanged.
|
|
44
|
+
|
|
45
|
+
### Emits
|
|
46
|
+
|
|
47
|
+
| Event | Payload | Description |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `change-value` | `DateTimePickerEmitPayload` | Fired when either the date or time part changes. |
|
|
50
|
+
| `commit-value` | `DateTimePickerEmitPayload` | Fired on the same change event. |
|
|
51
|
+
| `update:value` | `string \| null` | Combined `YYYY-MM-DDTHH:MM:SS` string. Selecting a date fabricates a default time (`12:00:00`) so a complete value is produced; `null` only while no date is set. Suitable for `v-model:value`. |
|
|
52
|
+
|
|
53
|
+
`DateTimePickerEmitPayload` exposes `value`, `datePart`, `timePart`, `hasValue()`, and `hasEmptyValue()`.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Value contract
|
|
58
|
+
|
|
59
|
+
| Concern | Behavior |
|
|
60
|
+
|---|---|
|
|
61
|
+
| **Partial emit** | Selecting a date fabricates a default time of `12:00:00` (UAT decision), so the combined value is complete as soon as a date exists and the popover stays open for the user to refine the time. `null` is emitted only while no date is set. |
|
|
62
|
+
| **Popover stays open on date select** | The molecule sets `closeOnSelect: false` on the date atom, so picking a date keeps the popover open for time selection. (Standalone `FuroDatePicker` still closes on select.) |
|
|
63
|
+
| **Cross-field min/max** | The date range is always enforced. The **minimum time** applies only when the selected date equals the minimum date; the **maximum time** applies only when it equals the maximum date. On any other date the full day is allowed. |
|
|
64
|
+
| **Seconds** | The wire value is always `YYYY-MM-DDTHH:MM:SS` (seconds default `00`). |
|
|
65
|
+
| **Preserve-on-change** | Changing the date keeps the current time part; changing the time keeps the current date part. |
|
|
66
|
+
| **Timezone** | Naive v1: `parcel.timezone` is forwarded to both atoms, which own normalization. |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
```vue
|
|
73
|
+
<FuroDateTimePicker
|
|
74
|
+
:parcel="{
|
|
75
|
+
placeholder: 'Pick a date & time',
|
|
76
|
+
value: scheduledAt,
|
|
77
|
+
minValue: '2024-06-01T08:00:00',
|
|
78
|
+
maxValue: '2024-12-31T18:00:00',
|
|
79
|
+
granularity: 'second',
|
|
80
|
+
}"
|
|
81
|
+
@update:value="onScheduledAtChange({ value: $event })"
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Composition notes
|
|
88
|
+
|
|
89
|
+
- The time field is rendered inside the date picker popover via the additive `content-footer` slot on `FuroDatePicker`. Because the time field lives **inside** the popover content (not outside it), editing the time does not trigger an outside-focus/pointer event and the popover stays open.
|
|
90
|
+
- Picking a date does **not** close the popover (`extractDateParcel()` sets `closeOnSelect: false`), and the molecule fabricates a default time of `12:00:00` on first date selection (`applyDefaultTimeOnDateSelection()`). The user can then refine the time without reopening the popover.
|
|
91
|
+
- Re-selecting the already-selected date keeps it (the date atom defaults to `preventDeselect: true`). The atom's footer **Today** / **Clear** buttons and their `todayButtonText` / `clearButtonText` labels pass through the molecule unchanged — **Today** sets the date (and fabricates the noon default), **Clear** resets **both** the date and time parts to `null` (clearing the date with `preventDeselect` on clears the time too — `syncTimeWithDateSelection()`).
|
|
92
|
+
- On open, the calendar is auto-focused by the underlying popover primitive. The molecule forces the date picker into **modal** mode (`extractDateParcel()` sets `modal: true`), so the popover traps focus (the underlying popover primitive's focus scope). Tab then cycles within the popover — calendar → footer time field — instead of escaping to the inline time field in the trigger (the teleported popover sits late in the DOM, so without trapping the focus order is wrong).
|
|
93
|
+
- The trigger field carries a **directly editable** time field: the molecule injects a `FuroTimeField` into `FuroDatePicker`'s `field-suffix` slot, beside the date segments. Users can type the time inline without opening the popover, and the calendar icon still opens the picker for date selection. The inline copy is styled borderless with its steppers hidden so it blends into the single trigger field; the full stepper version lives in the popover footer.
|
|
94
|
+
- The inline field (trigger) and the footer field (popover) are **two views of the same time part**: both bind `value` to `timePart` via `extractTimeParcel()` and route edits through `onTimeChange()`, so editing either keeps both — and the combined `v-model:value` — in sync.
|
|
95
|
+
- Stepping the time before a date is chosen no longer resets itself. Because the combined value emits `null` until both parts are set (Q2), the parent's `v-model` echoes `null` back; `syncFromExternalValue()` skips re-splitting when the external value already equals the current parts' combination, preserving the in-progress part.
|
|
96
|
+
- The popover does not shift when the time changes. The date/time fields use `font-variant-numeric: tabular-nums` (equal-width digits), and the popover is **start-aligned** (`align="start"` on the date content) so a width change in the trigger's inline time field never re-centers/moves the popover.
|
|
97
|
+
- The molecule imports the two atoms only — no GraphQL, PrimeVue, Vue Router, or Nuxt-specific imports.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
|
|
103
|
+
- Context and payload logic are covered by jsdom unit tests (`splitValue` / `combineValue`, cross-field bounds, preserve-on-change, partial/null emit, parcel derivation).
|
|
104
|
+
- The full popover open-cycle ("open → pick date → edit time inside → combined value updates, popover stays open") is verified by **manual real-browser UAT** in the playground. A full SFC mount is infeasible in jsdom because the composed atoms import the headless primitive's namespaced entry + Nuxt `#components`, which do not resolve under the jest module map; the integration test is therefore a documented partial driving the context through the same handlers the SFC binds.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Playground
|
|
109
|
+
|
|
110
|
+
`/samples/components/playground` → **Date Time** tab. Demonstrates basic, pre-filled, bounded window (cross-field time), programmatic set/clear, timezone passthrough, disabled, invalid, and the emit trio event log.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# FuroDialog
|
|
2
|
+
|
|
3
|
+
Organism — a modal (or non-modal) dialog built on a headless dialog primitive
|
|
4
|
+
(`Root` + `Portal` + `Overlay` + `Content` + `Title` + `Description` + `Close`).
|
|
5
|
+
Composes overlay, focus trap, Esc/overlay-click dismissal, and an accessible
|
|
6
|
+
heading. Domain-agnostic: no GraphQL, router, or Nuxt imports.
|
|
7
|
+
|
|
8
|
+
Location: `lib/components/organisms/FuroDialog/`
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
| Prop | Type | Default | Notes |
|
|
13
|
+
|------|------|---------|-------|
|
|
14
|
+
| `parcel` | `FuroDialogParcel \| null` | `null` | Reactive behavior object (below) |
|
|
15
|
+
|
|
16
|
+
### `parcel`
|
|
17
|
+
|
|
18
|
+
| Key | Type | Default | Notes |
|
|
19
|
+
|-----|------|---------|-------|
|
|
20
|
+
| `open` | `boolean` | — | Controlled open state. When set, the parent owns open/close. When omitted, the dialog manages its own state. |
|
|
21
|
+
| `disabled` | `boolean` | `false` | Disables the trigger. |
|
|
22
|
+
| `modal` | `boolean` | `true` | `false` renders without the overlay scrim and keeps the page interactive. |
|
|
23
|
+
| `title` | `string \| null` | `null` | Heading text. Provide a title (or the `title` slot) for accessibility. |
|
|
24
|
+
| `description` | `string \| null` | `null` | Sub-heading text below the title. |
|
|
25
|
+
| `closeText` | `string` | `'Close'` | Accessible label for the built-in close button. |
|
|
26
|
+
| `closeOnOutsideClick` | `boolean` | `true` | `false` ignores overlay / outside-click dismissal — only the close button or an explicit action closes it. |
|
|
27
|
+
| `closeOnEscape` | `boolean` | `true` | `false` ignores the Escape key. |
|
|
28
|
+
| `size` | `'sm' \| 'default' \| 'lg' \| 'fullscreen'` | `'default'` | Content width variant. `fullscreen` fills the viewport. |
|
|
29
|
+
| `busy` | `boolean` | `false` | While `true`, all dismissal (outside / Esc / close button / `close()`) is blocked — use during async work. |
|
|
30
|
+
| `initialFocus` | `string \| null` | `null` | CSS selector (scoped to the dialog content) of the element to focus on open. When unset, the first focusable element is focused. |
|
|
31
|
+
|
|
32
|
+
## Events
|
|
33
|
+
|
|
34
|
+
| Event | Payload | When |
|
|
35
|
+
|-------|---------|------|
|
|
36
|
+
| `update:open` | `boolean` | Open state changed — enables `v-model:open`. |
|
|
37
|
+
| `open:change` | `{ open: boolean }` | Open state changed (semantic form). |
|
|
38
|
+
|
|
39
|
+
## Slots
|
|
40
|
+
|
|
41
|
+
| Slot | Purpose |
|
|
42
|
+
|------|---------|
|
|
43
|
+
| `trigger` | Optional element that opens the dialog. Rendered as the trigger via `as-child` — supply your own button/element. Omit when the dialog is opened via controlled `open`. |
|
|
44
|
+
| `title` | Overrides the heading (falls back to `parcel.title`). |
|
|
45
|
+
| `description` | Overrides the description (falls back to `parcel.description`). |
|
|
46
|
+
| default | Dialog body content. Receives a `close()` slot prop to dismiss from inside. |
|
|
47
|
+
| `footer` | Footer region (e.g. action buttons). Receives a `close()` slot prop so action buttons can dismiss the dialog (after running their own logic). |
|
|
48
|
+
| `close` | Content of the built-in close button (defaults to `✕`). |
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### Uncontrolled (own trigger)
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<FuroDialog :parcel="{ title: 'Delete record', description: 'This cannot be undone.' }">
|
|
56
|
+
<template #trigger>
|
|
57
|
+
<button type="button">Delete…</button>
|
|
58
|
+
</template>
|
|
59
|
+
<p>The record will be permanently removed.</p>
|
|
60
|
+
<template #footer>
|
|
61
|
+
<button type="button">Cancel</button>
|
|
62
|
+
<button type="button">Delete</button>
|
|
63
|
+
</template>
|
|
64
|
+
</FuroDialog>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Controlled (`v-model:open`)
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<FuroDialog
|
|
71
|
+
:parcel="{ open: isDialogOpen, title: 'Settings' }"
|
|
72
|
+
@open:change="isDialogOpen = $event.open"
|
|
73
|
+
>
|
|
74
|
+
<p>Open state is owned by the parent.</p>
|
|
75
|
+
</FuroDialog>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- The dialog content is portaled to the document body; overlay and content carry
|
|
81
|
+
`unit-dialog-overlay` / `unit-dialog-content` classes inside `@layer furo`.
|
|
82
|
+
- Always provide a title (text or slot) — it is the dialog's accessible name.
|
|
83
|
+
- A description is optional. Without `parcel.description` or a `description` slot the content declares no described-by target, so the headless primitive logs no accessibility warning.
|
|
84
|
+
- By default the built-in close button, Esc key, and overlay click all dismiss the dialog.
|
|
85
|
+
Set `closeOnOutsideClick` / `closeOnEscape` to `false` to require an explicit close
|
|
86
|
+
(e.g. a footer action calling the `close()` slot prop).
|
|
87
|
+
- Overlay scrim color is the `--furo-dialog-overlay-background` component token.
|
|
88
|
+
- The built-in close button takes its size from `--size-close-button-medium`.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# FuroDrawer
|
|
2
|
+
|
|
3
|
+
Organism — a slide-in panel docked to a viewport edge, built on a headless
|
|
4
|
+
drawer primitive (`Root` + `Portal` + `Overlay` + `Content` + `Handle` +
|
|
5
|
+
`Title` + `Description` + `Close` + `SwipeArea`). Supports swipe-to-dismiss with
|
|
6
|
+
momentum, optional swipe-to-open, snap points for partially-open resting
|
|
7
|
+
positions, modal / focus-trapped non-modal / fully non-modal modes, controlled
|
|
8
|
+
or uncontrolled open state, and built-in Title/Description + Esc handling.
|
|
9
|
+
Domain-agnostic: no GraphQL, router, or Nuxt imports.
|
|
10
|
+
|
|
11
|
+
Location: `lib/components/organisms/FuroDrawer/`
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Notes |
|
|
16
|
+
|------|------|---------|-------|
|
|
17
|
+
| `parcel` | `FuroDrawerParcel \| null` | `null` | Reactive behavior object (below) |
|
|
18
|
+
|
|
19
|
+
### `parcel`
|
|
20
|
+
|
|
21
|
+
| Key | Type | Default | Notes |
|
|
22
|
+
|-----|------|---------|-------|
|
|
23
|
+
| `open` | `boolean` | — | Controlled open state. When set, the parent owns open/close. When omitted, the drawer manages its own state. |
|
|
24
|
+
| `disabled` | `boolean` | `false` | Disables the trigger. |
|
|
25
|
+
| `modal` | `boolean \| 'trap-focus'` | `true` | `true`: overlay scrim + focus trap + scroll lock. `'trap-focus'`: focus trapped but the page stays interactive (side-panel mode). `false`: fully non-modal. |
|
|
26
|
+
| `title` | `string \| null` | `null` | Heading text. Provide a title (or the `title` slot) for accessibility. |
|
|
27
|
+
| `description` | `string \| null` | `null` | Sub-heading text below the title. |
|
|
28
|
+
| `closeText` | `string` | `'Close'` | Accessible label for the built-in close button. |
|
|
29
|
+
| `closeOnOutsideClick` | `boolean` | `true` | `false` ignores overlay / outside-click dismissal. |
|
|
30
|
+
| `closeOnEscape` | `boolean` | `true` | `false` ignores the Escape key. |
|
|
31
|
+
| `side` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'right'` | Edge the panel docks to, slides in from, and is swiped toward to dismiss. |
|
|
32
|
+
| `size` | `'sm' \| 'default' \| 'lg' \| 'fullscreen'` | `'default'` | Cross-axis extent: width for `left`/`right`, height for `top`/`bottom`. `fullscreen` fills the docking axis. |
|
|
33
|
+
| `snapPoints` | `Array<number \| string> \| null` | `null` | Preset resting positions: fractions `0–1`, pixels `> 1`, or `'148px'`/`'30rem'` strings. |
|
|
34
|
+
| `activeSnapPoint` | `number \| string \| null` | `null` | Controlled active snap point. When set, the parent owns it (`update:activeSnapPoint`). |
|
|
35
|
+
| `sequentialSnap` | `boolean` | `false` | `true` snaps one point at a time; `false` snaps to the nearest by distance. |
|
|
36
|
+
| `swipeToOpen` | `boolean` | `false` | Renders an edge grab strip so the panel can be swiped open from closed. |
|
|
37
|
+
| `dragHandle` | `boolean` | `false` | Renders a drag handle (grab indicator) on the docked edge. |
|
|
38
|
+
| `busy` | `boolean` | `false` | While `true`, all dismissal (outside / Esc / close button / `close()`) is blocked — use during async work. |
|
|
39
|
+
| `initialFocus` | `string \| null` | `null` | CSS selector (scoped to the drawer content) of the element to focus on open. |
|
|
40
|
+
|
|
41
|
+
## Events
|
|
42
|
+
|
|
43
|
+
| Event | Payload | When |
|
|
44
|
+
|-------|---------|------|
|
|
45
|
+
| `update:open` | `boolean` | Open state changed — enables `v-model:open`. |
|
|
46
|
+
| `open:change` | `{ open: boolean }` | Open state changed (semantic form). |
|
|
47
|
+
| `update:activeSnapPoint` | `number \| string \| null` | Active snap point changed — enables `v-model:activeSnapPoint`. |
|
|
48
|
+
| `snap:change` | `{ snapPoint: number \| string \| null }` | Active snap point changed (semantic form). |
|
|
49
|
+
|
|
50
|
+
## Slots
|
|
51
|
+
|
|
52
|
+
| Slot | Purpose |
|
|
53
|
+
|------|---------|
|
|
54
|
+
| `trigger` | Optional element that opens the drawer (rendered via `as-child`). Omit when opened via controlled `open` or swipe-to-open. |
|
|
55
|
+
| `title` | Overrides the heading (falls back to `parcel.title`). |
|
|
56
|
+
| `description` | Overrides the description (falls back to `parcel.description`). |
|
|
57
|
+
| default | Drawer body content. Receives a `close()` slot prop to dismiss from inside. |
|
|
58
|
+
| `footer` | Footer region (e.g. action buttons). Receives a `close()` slot prop. |
|
|
59
|
+
| `close` | Content of the built-in close button (defaults to `✕`). |
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Uncontrolled (own trigger)
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<FuroDrawer :parcel="{ side: 'right', title: 'Filters', dragHandle: true }">
|
|
67
|
+
<template #trigger>
|
|
68
|
+
<button type="button">Filters…</button>
|
|
69
|
+
</template>
|
|
70
|
+
<p>Filter controls go here. Swipe right or press Esc to close.</p>
|
|
71
|
+
<template #footer="{ close }">
|
|
72
|
+
<button type="button" @click="close()">Apply</button>
|
|
73
|
+
</template>
|
|
74
|
+
</FuroDrawer>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Snap points (bottom sheet)
|
|
78
|
+
|
|
79
|
+
```vue
|
|
80
|
+
<FuroDrawer
|
|
81
|
+
:parcel="{
|
|
82
|
+
side: 'bottom',
|
|
83
|
+
size: 'fullscreen',
|
|
84
|
+
dragHandle: true,
|
|
85
|
+
snapPoints: [0.4, 0.75, 1],
|
|
86
|
+
}"
|
|
87
|
+
@snap:change="onSnap($event.snapPoint)"
|
|
88
|
+
>
|
|
89
|
+
<template #trigger>
|
|
90
|
+
<button type="button">Open sheet</button>
|
|
91
|
+
</template>
|
|
92
|
+
<p>Drag the handle to rest at 40%, 75%, or 100%.</p>
|
|
93
|
+
</FuroDrawer>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Controlled (`v-model:open`)
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<FuroDrawer
|
|
100
|
+
:parcel="{ open: isOpen, side: 'left', title: 'Navigation' }"
|
|
101
|
+
@open:change="isOpen = $event.open"
|
|
102
|
+
>
|
|
103
|
+
<p>Open state is owned by the parent.</p>
|
|
104
|
+
</FuroDrawer>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Notes
|
|
108
|
+
|
|
109
|
+
- The drawer content is portaled to the document body; overlay and content carry
|
|
110
|
+
`unit-drawer-overlay` / `unit-drawer-content` classes inside `@layer furo`.
|
|
111
|
+
- Position composes the active snap-point offset (on the docking axis) with the
|
|
112
|
+
live swipe-drag offset the primitive exposes via CSS variables; enter/exit use
|
|
113
|
+
the independent `translate` property so they compose with the drag transform.
|
|
114
|
+
Both are suppressed under `prefers-reduced-motion: reduce`.
|
|
115
|
+
- `size` controls the cross-axis extent only. `fullscreen` fills the docking axis.
|
|
116
|
+
- `'trap-focus'` mode still renders the overlay element but keeps the page
|
|
117
|
+
interactive; `false` renders no overlay at all.
|
|
118
|
+
- Always provide a title (text or slot) — it is the drawer's accessible name.
|
|
119
|
+
- Overlay scrim reuses the `--furo-dialog-overlay-background` component token.
|
|
120
|
+
- The built-in close button takes its size from `--size-close-button-medium`.
|
|
121
|
+
- Swipe, momentum, and snap behaviors are pointer-driven and require manual
|
|
122
|
+
browser QA (the playground "Drawer" tab exercises every mode).
|