Skip to content

Commit ab39bb3

Browse files
committed
docs: add WDatePicker/WFormDatePicker docs, example pages, and fix lint issues
- Rewrite w-date-picker.md with full API docs, state variants, calendar internals - Create w-form-date-picker.md with form validation, range mode, styling examples - Add 4 example pages: date_picker_basic, date_picker_range, date_picker_styled, form_date_picker_range - Register all new routes in routes.dart - Remove 5 EXAMPLE_NEEDED TODOs from doc files - Delete orphaned test/widget/widgets/w_date_picker_test.dart (replaced by test/widgets/) - Fix unused variables in w_date_picker_test.dart and w_popover_test.dart - Fix example lint: deprecated withOpacity, unnecessary interpolation, missing braces
1 parent 5a31b34 commit ab39bb3

14 files changed

Lines changed: 1163 additions & 312 deletions

doc/widgets/w-date-picker.md

Lines changed: 285 additions & 58 deletions
Large diffs are not rendered by default.

doc/widgets/w-form-date-picker.md

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
# WFormDatePicker
2+
3+
A form-integrated date picker that wraps [WDatePicker](./w-date-picker.md) with Flutter's `FormField<DateTime>`. Provides native form validation, automatic `error:` state styling, and optional label, hint, and error message display.
4+
5+
- [Basic Usage](#basic-usage)
6+
- [Constructor](#constructor)
7+
- [Props](#props)
8+
- [Form Validation](#form-validation)
9+
- [Range Mode in Forms](#range-mode-in-forms)
10+
- [Event Handling](#event-handling)
11+
- [State Variants](#state-variants)
12+
- [Styling Examples](#styling-examples)
13+
- [All Supported Classes](#all-supported-classes)
14+
- [Customizing Theme](#customizing-theme)
15+
- [Related Documentation](#related-documentation)
16+
17+
<x-preview path="forms/form_date_picker_basic" size="md" source="example/lib/pages/forms/form_date_picker_basic.dart"></x-preview>
18+
19+
```dart
20+
WFormDatePicker(
21+
label: 'Birth Date',
22+
hint: 'We need your date of birth for verification',
23+
className: 'p-3 border border-gray-300 rounded-lg error:border-red-500',
24+
validator: (value) => value == null ? 'Please select a date' : null,
25+
onChanged: (date) => print('Selected: $date'),
26+
)
27+
```
28+
29+
## Basic Usage
30+
31+
`WFormDatePicker` works inside a Flutter `Form` widget. It renders a vertical layout with an optional label above the date picker and error/hint text below.
32+
33+
```dart
34+
final _formKey = GlobalKey<FormState>();
35+
36+
Form(
37+
key: _formKey,
38+
child: Column(
39+
children: [
40+
WFormDatePicker(
41+
label: 'Event Date',
42+
className: 'p-3 border rounded-lg error:border-red-500',
43+
validator: (date) => date == null ? 'Date is required' : null,
44+
onChanged: (date) => _eventDate = date,
45+
),
46+
WButton(
47+
className: 'mt-4 bg-blue-500 text-white px-4 py-2 rounded-lg',
48+
onPressed: () {
49+
if (_formKey.currentState!.validate()) {
50+
_formKey.currentState!.save();
51+
}
52+
},
53+
child: WText('Submit'),
54+
),
55+
],
56+
),
57+
)
58+
```
59+
60+
When validation fails, the widget automatically adds the `error` state to the inner `WDatePicker`, activating any `error:` prefixed classes.
61+
62+
## Constructor
63+
64+
```dart
65+
WFormDatePicker({
66+
Key? key,
67+
// FormField params
68+
FormFieldValidator<DateTime>? validator,
69+
FormFieldSetter<DateTime>? onSaved,
70+
AutovalidateMode? autovalidateMode,
71+
bool enabled = true,
72+
// WDatePicker params
73+
DateTime? initialValue,
74+
DateRange? initialRange,
75+
DatePickerMode mode = DatePickerMode.single,
76+
ValueChanged<DateTime>? onChanged,
77+
ValueChanged<DateRange>? onRangeChanged,
78+
DateTime? minDate,
79+
DateTime? maxDate,
80+
String? className,
81+
String? placeholder,
82+
Set<String>? states,
83+
DateDisplayFormat? displayFormat,
84+
// Form wrapper params
85+
String? label,
86+
String labelClassName = 'text-sm font-medium text-gray-700 dark:text-gray-300 mb-1',
87+
bool showError = true,
88+
String errorClassName = 'text-red-500 text-xs mt-1',
89+
String? hint,
90+
String hintClassName = 'text-gray-500 text-xs mt-1',
91+
})
92+
```
93+
94+
## Props
95+
96+
### FormField Props
97+
98+
| Prop | Type | Default | Description |
99+
|:-----|:-----|:--------|:------------|
100+
| `validator` | `FormFieldValidator<DateTime>?` | `null` | Validation function, receives `DateTime?` |
101+
| `onSaved` | `FormFieldSetter<DateTime>?` | `null` | Called when form is saved |
102+
| `autovalidateMode` | `AutovalidateMode?` | `null` | When to auto-validate |
103+
| `enabled` | `bool` | `true` | Whether the picker is interactive |
104+
| `initialValue` | `DateTime?` | `null` | Initial selected date (single mode) |
105+
106+
### Date Picker Props
107+
108+
| Prop | Type | Default | Description |
109+
|:-----|:-----|:--------|:------------|
110+
| `mode` | `DatePickerMode` | `single` | Selection mode: `single` or `range` |
111+
| `initialRange` | `DateRange?` | `null` | Initial date range (range mode) |
112+
| `onChanged` | `ValueChanged<DateTime>?` | `null` | Called on date selection (single mode) |
113+
| `onRangeChanged` | `ValueChanged<DateRange>?` | `null` | Called on range selection (range mode) |
114+
| `minDate` | `DateTime?` | `null` | Earliest selectable date |
115+
| `maxDate` | `DateTime?` | `null` | Latest selectable date |
116+
| `className` | `String?` | `null` | Wind utility classes for the trigger |
117+
| `placeholder` | `String?` | `null` | Placeholder text (defaults to `'Select date'`) |
118+
| `states` | `Set<String>?` | `null` | Custom states for dynamic styling |
119+
| `displayFormat` | `DateDisplayFormat?` | `null` | Custom date display format function |
120+
121+
### Layout Props
122+
123+
| Prop | Type | Default | Description |
124+
|:-----|:-----|:--------|:------------|
125+
| `label` | `String?` | `null` | Label text displayed above the picker |
126+
| `labelClassName` | `String` | `'text-sm font-medium text-gray-700 dark:text-gray-300 mb-1'` | Wind classes for the label |
127+
| `showError` | `bool` | `true` | Whether to display validation error text |
128+
| `errorClassName` | `String` | `'text-red-500 text-xs mt-1'` | Wind classes for error message |
129+
| `hint` | `String?` | `null` | Hint text displayed below the picker |
130+
| `hintClassName` | `String` | `'text-gray-500 text-xs mt-1'` | Wind classes for hint text |
131+
132+
> [!NOTE]
133+
> When both an error and hint are present, the error message takes priority and the hint is hidden.
134+
135+
## Form Validation
136+
137+
The `validator` function receives the currently selected `DateTime?` value. Return a `String` to show an error, or `null` if valid.
138+
139+
```dart
140+
WFormDatePicker(
141+
label: 'Departure Date',
142+
className: 'p-3 border rounded-lg error:border-red-500 error:bg-red-50',
143+
validator: (date) {
144+
if (date == null) return 'Please select a departure date';
145+
if (date.isBefore(DateTime.now())) return 'Date must be in the future';
146+
return null;
147+
},
148+
)
149+
```
150+
151+
### Auto-Validation
152+
153+
Use `autovalidateMode` to validate as the user interacts:
154+
155+
```dart
156+
WFormDatePicker(
157+
label: 'Start Date',
158+
autovalidateMode: AutovalidateMode.onUserInteraction,
159+
className: 'p-3 border rounded-lg error:border-red-500',
160+
validator: (date) => date == null ? 'Required' : null,
161+
)
162+
```
163+
164+
### Using onSaved
165+
166+
```dart
167+
DateTime? _savedDate;
168+
169+
WFormDatePicker(
170+
label: 'Meeting Date',
171+
className: 'p-3 border rounded-lg',
172+
onSaved: (date) => _savedDate = date,
173+
validator: (date) => date == null ? 'Required' : null,
174+
)
175+
```
176+
177+
## Range Mode in Forms
178+
179+
In range mode, the `FormField<DateTime>` internally tracks the range's **start date** for validation purposes. This means a simple null check still works as a "required" validator.
180+
181+
<x-preview path="forms/form_date_picker_range" size="md" source="example/lib/pages/forms/form_date_picker_range.dart"></x-preview>
182+
183+
```dart
184+
WFormDatePicker(
185+
label: 'Trip Dates',
186+
mode: DatePickerMode.range,
187+
initialRange: null,
188+
className: 'p-3 border rounded-lg error:border-red-500',
189+
placeholder: 'Select check-in / check-out',
190+
validator: (date) => date == null ? 'Please select your trip dates' : null,
191+
onRangeChanged: (range) {
192+
if (range.isComplete) {
193+
_calculateTripDuration(range);
194+
}
195+
},
196+
)
197+
```
198+
199+
> [!NOTE]
200+
> The `validator` receives the range's start `DateTime` — not a `DateRange` object. For more complex range validation (e.g., minimum stay duration), use the `onRangeChanged` callback to manage validation externally.
201+
202+
## Event Handling
203+
204+
`onChanged` and `onRangeChanged` fire alongside the form state updates. The `FormFieldState` is updated automatically — you don't need to call `didChange` yourself.
205+
206+
```dart
207+
WFormDatePicker(
208+
label: 'Appointment',
209+
onChanged: (date) {
210+
// FormField state is already updated at this point
211+
_loadAvailableSlots(date);
212+
},
213+
)
214+
```
215+
216+
## State Variants
217+
218+
`WFormDatePicker` inherits all state variants from `WDatePicker` and adds the `error` state automatically when validation fails.
219+
220+
| State | Activated When |
221+
|:------|:---------------|
222+
| `hover:` | Mouse hovers over the trigger |
223+
| `focus:` | Calendar popover is open |
224+
| `open:` | Calendar popover is open (alias) |
225+
| `disabled:` | `enabled` is `false` |
226+
| `selected:` | A date or range has been selected |
227+
| `error:` | Form validation has failed |
228+
229+
```dart
230+
WFormDatePicker(
231+
label: 'Date',
232+
className: 'p-3 border border-gray-300 rounded-lg '
233+
'hover:border-blue-400 '
234+
'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 '
235+
'error:border-red-500 error:ring-2 error:ring-red-200 '
236+
'disabled:opacity-50 disabled:bg-gray-100',
237+
validator: (date) => date == null ? 'Required' : null,
238+
)
239+
```
240+
241+
## Styling Examples
242+
243+
### Standard Form Field
244+
245+
```dart
246+
WFormDatePicker(
247+
label: 'Date of Birth',
248+
hint: 'You must be at least 18 years old',
249+
className: 'w-full p-3 border border-gray-300 rounded-lg '
250+
'focus:border-blue-500 focus:ring-2 focus:ring-blue-200 '
251+
'error:border-red-500',
252+
maxDate: DateTime.now().subtract(const Duration(days: 365 * 18)),
253+
validator: (date) => date == null ? 'Date of birth is required' : null,
254+
)
255+
```
256+
257+
### Dark Mode Ready
258+
259+
```dart
260+
WFormDatePicker(
261+
label: 'Schedule',
262+
labelClassName: 'text-sm font-medium text-gray-700 dark:text-gray-300',
263+
className: 'p-3 bg-white dark:bg-gray-800 '
264+
'border border-gray-300 dark:border-gray-600 rounded-lg '
265+
'hover:border-blue-400 dark:hover:border-blue-500 '
266+
'error:border-red-500 dark:error:border-red-400',
267+
errorClassName: 'text-red-500 dark:text-red-400 text-xs mt-1',
268+
validator: (date) => date == null ? 'Required' : null,
269+
)
270+
```
271+
272+
### Minimalist
273+
274+
```dart
275+
WFormDatePicker(
276+
label: 'When',
277+
labelClassName: 'text-xs text-gray-500 uppercase tracking-wider',
278+
className: 'border-b border-gray-200 py-2 rounded-none '
279+
'focus:border-blue-500 error:border-red-500',
280+
hintClassName: 'text-gray-400 text-xs mt-0.5',
281+
hint: 'Optional',
282+
)
283+
```
284+
285+
## All Supported Classes
286+
287+
### Trigger (className)
288+
289+
All Wind utility classes work on the trigger container. The most commonly used:
290+
291+
| Category | Examples |
292+
|:---------|:---------|
293+
| Background | `bg-white`, `bg-gray-50`, `dark:bg-gray-800` |
294+
| Border | `border`, `border-gray-300`, `rounded-lg` |
295+
| Padding | `p-3`, `px-4`, `py-2` |
296+
| Sizing | `w-full`, `w-64` |
297+
| Ring | `ring-2`, `ring-blue-200` |
298+
| Shadow | `shadow-sm` |
299+
| State prefixes | `hover:`, `focus:`, `disabled:`, `selected:`, `error:`, `dark:` |
300+
301+
### Label (labelClassName)
302+
303+
Default: `text-sm font-medium text-gray-700 dark:text-gray-300 mb-1`
304+
305+
### Error (errorClassName)
306+
307+
Default: `text-red-500 text-xs mt-1`
308+
309+
### Hint (hintClassName)
310+
311+
Default: `text-gray-500 text-xs mt-1`
312+
313+
## Customizing Theme
314+
315+
`WFormDatePicker` inherits all theme customization from `WDatePicker`. Override Tailwind color scales in `WindThemeData` to change selection colors, error states, and calendar chrome:
316+
317+
```dart
318+
WindTheme(
319+
data: WindThemeData(
320+
colors: {
321+
...WindThemeData.defaultColors,
322+
'red': {
323+
500: Color(0xFFEF4444), // Error border color
324+
200: Color(0xFFFECACA), // Error ring color
325+
},
326+
},
327+
),
328+
child: MyApp(),
329+
)
330+
```
331+
332+
## Related Documentation
333+
334+
- [WDatePicker](./w-date-picker.md) - The base date picker widget
335+
- [WFormInput](./w-form-input.md) - Form-integrated text input
336+
- [WFormSelect](./w-form-select.md) - Form-integrated dropdown
337+
- [WFormCheckbox](./w-form-checkbox.md) - Form-integrated checkbox
338+
- [WPopover](./w-popover.md) - The underlying overlay engine

example/lib/pages/core-concepts/theming_example.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class ThemingExamplePage extends StatelessWidget {
106106
className: 'w-8 bg-blue-500 rounded',
107107
child: SizedBox(height: pixels.toDouble()),
108108
),
109-
WText('$value', className: 'text-xs text-slate-500'),
109+
WText(value, className: 'text-xs text-slate-500'),
110110
WText('${pixels.toInt()}px', className: 'text-xs text-slate-400'),
111111
],
112112
);

0 commit comments

Comments
 (0)