Skip to content

feat(DateTimePicker): new component with two-step popover and presets#55

Open
IgorShevchik wants to merge 6 commits into
mainfrom
claude/date-time-picker-component-NqwmO
Open

feat(DateTimePicker): new component with two-step popover and presets#55
IgorShevchik wants to merge 6 commits into
mainfrom
claude/date-time-picker-component-NqwmO

Conversation

@IgorShevchik
Copy link
Copy Markdown
Collaborator

@IgorShevchik IgorShevchik commented May 27, 2026

What this PR is about

A new B24DateTimePicker component — a Bitrix24-style date + time picker with a two-step popover (calendar → hour/minute grid), a side preset column, and an optional date-only mode (time forced to 00:00:00). It fills the gap between the segmented B24InputDate / B24InputTime and the more "native" Bitrix24 UX.

What was done

  • Component src/runtime/components/DateTimePicker.vue composed of built-in B24Popover, B24Calendar and a read-only B24Input (default trigger). Model — CalendarDateTime / ZonedDateTime (or CalendarDate in date-only mode).
  • Theme src/theme/date-time-picker.ts — minimum styling, design-system classes only.
  • LocalizationdateTimePicker.* added to the Messages type and filled in for all 19 locales. endOfWeek for DE/FR/IT/BR/TR was fixed to actually mean "end of week" (the machine translation produced "weekend" instead).
  • Accessibilityrole="group" + aria-labelledby on the hours/minutes grids, aria-pressed on each cell, unique id via useId().
  • Correctness — preset resolution is memoized (factory functions now run once per render instead of twice), Number.isFinite guard on minute-step to protect against NaN.
  • Teststest/components/DateTimePicker.spec.ts + 2 snapshot files: 21 cases × (nuxt + vue) = 42 green tests, including an axe a11y check.
  • Documentationdocs/content/docs/2.components/date-time-picker.md + 6 examples (Basic, DateOnly, CustomPresets, CustomTrigger, FormField, MinMaxDates). Responsive: presets drop below the calendar on mobile (flex-col-reverse sm:flex-row).
  • Playgrounds (nuxt + demo, in sync) — date-time-picker.vue matrix page + registration in useNavigation.ts.
  • Skill b24-ui-nuxt — updated components.md, component-selection.md and the "Date" section in forms.md.
  • Registration — type exports, theme entry in theme/index.ts, key in ThemeDefaults.
  • Multi-reviewer feedback addressed (docs / engineering / tests / security / CTO).

API

<B24DateTimePicker
  v-model="value"
  :minute-step="5"
  :presets="customPresets"
  date-only
  hide-presets
  locale="en"
  placeholder="Pick a date and time"
  :calendar="{ minValue, maxValue, isDateDisabled }"
/>

Key props: modelValue, defaultValue, dateOnly, minuteStep (5), locale, placeholder, presets, hidePresets, format, color, size, disabled, icon, timeIcon, popover, calendar, input.
Slots: default (trigger), presets, preset, time-header.

Manual QA checklist

  • pnpm dev/components/date-time-picker: popover opens, picking a date switches to the time step, clicking a minute closes the popover
  • Toggling Date only — time step disappears, model becomes CalendarDate
  • minute-step={5|10|15|30} — correct cell count; NaN/0/9999 don't break rendering (covered by tests)
  • Default presets "Today" / "Tomorrow" / "End of week" / "In a week" / "End of month" set the correct date; active one is highlighted
  • Custom presets (factory function) — DateTimePickerCustomPresetsExample, factory runs once per render
  • Custom trigger via #default (DateTimePickerCustomTriggerExample) — open reacts; default B24Input trigger
  • locale="ru" / locale="en" — value format, weekday names, preset labels all change
  • Responsive: on narrow viewport presets drop below and scroll horizontally
  • color, size, disabled — walk through the Matrix in the playground
  • hide-presets — side column disappears
  • B24FormField (DateTimePickerFormFieldExample) — name/required/label/hint work
  • :calendar="{ minValue, maxValue }" (DateTimePickerMinMaxDatesExample) — bounds respected
  • A11y: screen reader announces "group: hours / group: minutes" and the aria-pressed state on cells
  • Manual translation review for AR/IN/TH/KZ/JA/SC/TC/VN/ID/MS/LA/PL/UA — machine-generated (tracked in i18n(DateTimePicker): native-speaker review of machine-translated presets #60)
  • pnpm test — neighboring components' snapshots are not affected

What's next

Follow-up issues have been filed (from review):

Nothing urgent after merge — the component is self-contained and introduces no breaking changes to the public API.


Ready for squash-merge. Green locally: pnpm lint, pnpm typecheck, pnpm exec vitest test/components/DateTimePicker.spec.ts (42/42).

🤖 Generated with Claude Code

claude added 4 commits May 7, 2026 19:02
- Adds B24DateTimePicker built on B24Popover, B24Calendar and B24Input
- Two-step UX: calendar → hour/minute grid with configurable minute step
- Side preset list with localized defaults (Today, Tomorrow, End of week,
  In a week, End of month) and full override via the `presets` prop
- `dateOnly` mode forces value to CalendarDate with 00:00:00
- Works with @internationalized/date types via v-model (CalendarDateTime,
  ZonedDateTime, CalendarDate)
- Adds theme, locale messages for all 19 locales, demo+nuxt playground
  pages, docs page with five examples and skill references update
… i18n, docs)

- tests: add DateTimePicker.spec.ts (snapshots + minuteStep clamp + axe a11y)
  — 42 tests across nuxt + vue projects, both green
- security/correctness: guard `minuteStep` against `NaN` (Number.isFinite check)
- correctness: memoize resolved preset values into `resolvedPresets` so factory
  presets evaluate once per render instead of twice (was called by both
  `:active` and `:data-active`)
- a11y: add `role="group"` + `aria-labelledby` on hour/minute grids and
  `aria-pressed` on each cell
- i18n: fix "endOfWeek" mistranslations in de/fr/it/br/tr — they meant
  "weekend" instead of "end of work week (Friday)"
- docs: drop misleading `B24Button` from intro composition, fix
  "Pick a time" → "Pick a date and time", add "With min/max dates"
  section and matching example, show `open` slot prop in custom trigger,
  add `name`/`required` to form-field example
- skills: mention `DateTimePickerPreset` type for custom presets
- theme: correct `@memo` comment (uses `B24Input`, not `B24InputDate`)
claude added 2 commits May 28, 2026 11:05
Addresses all 11 review issues from the second pass:

1. Time footer (clock + value) is now a clickable button that switches
   to the time step. Styled with accent color to match the reference.
2. Weekend day cells (Sat/Sun) coloured red via #day slot.
3. Two-tone highlight on time grid: solid accent for selected,
   `data-now` light fill for the real-world current hour/minute.
4. Time-step header now shows date only (was date + time).
6. Preset card titles use `--b24ui-typography-label-color` instead of
   default inherited text → matches the rest of the design system.
7. Time-step header colour matches the calendar heading
   (`--b24ui-typography-legend-color`).
8. Mobile (`screen.isMobile`) renders the picker in a `B24Drawer`
   (bottom sheet) instead of `B24Popover`. Same content tree, different
   wrapper picked via `<component :is>`.
9. `Minute step` doc example placeholder fixed (covered by trigger fix).
10. `Locale` doc example switched to Hindi (`hi`).
11. **Dead-zone fix on the trigger**: clicks on padding around the input
    now open the picker. Trigger is a `role=button` div wrapping the
    `B24Input` (`pointer-events-none`, `tabindex=-1`, `aria-hidden`).
    `$attrs` move to the wrapper so consumer-passed `aria-label`/`id`/etc
    land where ARIA expects them. Adds `dateTimePicker.openPicker`
    locale string as fallback `aria-label` (filled across all 20 locales).

Test snapshots regenerated; 42 tests still green.
Two axe rules (`nested-interactive`, `label`) are disabled in the a11y
test for the trigger composition — the inner input is `tabindex=-1`,
`aria-hidden`, and `pointer-events-none`, so it is not user-reachable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants