Skip to content

[SUR-747] [Improvement] - Implement Force UI Date and Time Picker for Event Schema.#462

Merged
jaieds merged 40 commits into
stagingfrom
date-time-picker
Jun 11, 2026
Merged

[SUR-747] [Improvement] - Implement Force UI Date and Time Picker for Event Schema.#462
jaieds merged 40 commits into
stagingfrom
date-time-picker

Conversation

@jaieds

@jaieds jaieds commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in enableTimeSelection boolean prop (default false) to the DatePicker component, rendering time inputs between the calendar grid and the Apply/Cancel footer. This enables consumers (e.g. SureRank's Event Schema form) to replace the native browser date/time inputs with the Force UI picker.

Resolves brainstormforce/surerank#2114

Behavior

  • selectionType="single": one time input that sets hours/minutes on the selected date.
  • selectionType="range": start/end time inputs for from/to (end input disabled until an end date is selected).
  • selectionType="multiple": the flag is ignored; no time UI is rendered.
  • Times merge into the selected Date objects via date-fns setHours/setMinutes — the date portion is never replaced, and onDateSelect/onApply payloads carry the chosen time. Picking a new date preserves the previously chosen time.
  • Same-day ranges are kept valid: the end can never land before the start, whether the range is completed by click or a time edit would invert it.
  • With the flag off, behavior and rendered output are unchanged. No new dependencies, no changes to the existing public API.

Changes

  • src/components/datepicker/datepicker.tsx — new enableTimeSelection prop, passed to all variants (normal, dualdate, presets).
  • src/components/datepicker/datepicker-component.tsx — time inputs UI, time merge/preserve logic, same-day clamping.
  • src/components/datepicker/utils.tsxmergeDateTime helper.
  • src/components/datepicker/datepicker.stories.tsx — three new stories with interaction (play) tests.
  • src/components/datepicker/readme.md — prop documentation and usage example.

Test plan

  • npm run build (tsc + vite) passes.
  • ESLint clean on the datepicker directory.
  • Full storybook test suite: 216 tests / 63 files, all passing.
  • Interaction tests cover: disabled/enabled input states, time preservation across date picks, deselect with non-midnight times, range endpoint swap, same-day clamping, cleared-input no-op, and onDateSelect/onApply payload contents.

jaieds and others added 30 commits April 13, 2026 12:38
Adds auto-height adjustment to the TextArea component. When `autoResize`
is enabled, the textarea grows with its content using scrollHeight and
stops at the optional `maxHeight` cap (defaulting to 160px), at which
point it becomes scrollable. `minHeight` and `maxHeight` are applied as
CSS constraints regardless of `autoResize`. Also extends the props type
to include native textarea HTML attributes.
Update MCP setup command URL from GitHub Pages endpoint to the correct production URL (forceui.brainstormforce.com/mcp).
[SUR-675] [Force-UI] Add the props for text field to auto adjust height dynamically as per the text
Apply the unmerged Lexical Shadow DOM fix (facebook/lexical#7790) via
patch-package over registry lexical@0.38.2, and fix the mention plugin's
Shadow-DOM-unsafe assumptions:

- Port LexicalTypeaheadMenuPlugin locally to restore the menuRenderFn API
  removed in lexical 0.38 (keeps the custom EditorCombobox + public
  menuComponent/menuItemComponent props).
- Give each mention option a unique key (was '' for all), fixing keyboard
  nav, highlighting and the duplicate-key warning.
- Use event.composedPath() for outside-click detection and shadowRoot
  .activeElement for blur, so clicks inside the menu aren't misread across
  the shadow boundary.
- preventDefault on item/menu mousedown so the editor keeps selection on
  click (otherwise the mention can't be inserted).
- Portal the menu into the editor's shadow root so its styles apply.
- scrollIntoView the highlighted option directly (the #typeahead-menu
  document lookup is null in Shadow DOM).
- Match dropdown width to the editor (border-box, 100%).
- Add InsideShadowDom story mounting via a real nested React root.
…typeahead anchor

Typing in consumers mounting many EditorInputs (e.g. SureRank metabox)
lagged badly (~300ms/keystroke, multi-second main-thread tasks). Two
compounding causes, both fixed here:

- Accordion: the accessibility rework kept collapsed Accordion.Content
  children mounted (aria-hidden), so consumers with heavy panel content
  (Lexical editors) mounted everything up front and re-rendered it all
  on every keystroke. Keep the ARIA region element in the DOM so the
  trigger's aria-controls/aria-labelledby still resolve, but unmount the
  children via AnimatePresence while collapsed, as before v1.7.11.

- EditorInput typeahead: the ported menu plugin created an anchor div on
  every render and appended it to document.body during render at every
  mount, forcing style recalcs per editor mount/unmount on heavy pages
  (and risking leaked divs from discarded renders). Create the anchor
  lazily and append it only when the menu actually opens.

Also memoize the mention trigger regexes per trigger and bail out of
setMenuParent when the shadow-root parent is unchanged, trimming the
per-keystroke render path.

Measured in the SureRank metabox repro: a fast-typed sentence went from
a 15.3s main-thread task (~333ms/keystroke) to 0.38s total, with
collapsed sections back to mounting zero editors.
…ention

fix(editor-input): support Lexical mention menu in Shadow DOM
Adds auto-height adjustment to the TextArea component. When `autoResize`
is enabled, the textarea grows with its content using scrollHeight and
stops at the optional `maxHeight` cap (defaulting to 160px), at which
point it becomes scrollable. `minHeight` and `maxHeight` are applied as
CSS constraints regardless of `autoResize`. Also extends the props type
to include native textarea HTML attributes.
Update MCP setup command URL from GitHub Pages endpoint to the correct production URL (forceui.brainstormforce.com/mcp).
[SUR-675] [Force-UI] Add the props for text field to auto adjust height dynamically as per the text
Apply the unmerged Lexical Shadow DOM fix (facebook/lexical#7790) via
patch-package over registry lexical@0.38.2, and fix the mention plugin's
Shadow-DOM-unsafe assumptions:

- Port LexicalTypeaheadMenuPlugin locally to restore the menuRenderFn API
  removed in lexical 0.38 (keeps the custom EditorCombobox + public
  menuComponent/menuItemComponent props).
- Give each mention option a unique key (was '' for all), fixing keyboard
  nav, highlighting and the duplicate-key warning.
- Use event.composedPath() for outside-click detection and shadowRoot
  .activeElement for blur, so clicks inside the menu aren't misread across
  the shadow boundary.
- preventDefault on item/menu mousedown so the editor keeps selection on
  click (otherwise the mention can't be inserted).
- Portal the menu into the editor's shadow root so its styles apply.
- scrollIntoView the highlighted option directly (the #typeahead-menu
  document lookup is null in Shadow DOM).
- Match dropdown width to the editor (border-box, 100%).
- Add InsideShadowDom story mounting via a real nested React root.
…typeahead anchor

Typing in consumers mounting many EditorInputs (e.g. SureRank metabox)
lagged badly (~300ms/keystroke, multi-second main-thread tasks). Two
compounding causes, both fixed here:

- Accordion: the accessibility rework kept collapsed Accordion.Content
  children mounted (aria-hidden), so consumers with heavy panel content
  (Lexical editors) mounted everything up front and re-rendered it all
  on every keystroke. Keep the ARIA region element in the DOM so the
  trigger's aria-controls/aria-labelledby still resolve, but unmount the
  children via AnimatePresence while collapsed, as before v1.7.11.

- EditorInput typeahead: the ported menu plugin created an anchor div on
  every render and appended it to document.body during render at every
  mount, forcing style recalcs per editor mount/unmount on heavy pages
  (and risking leaked divs from discarded renders). Create the anchor
  lazily and append it only when the menu actually opens.

Also memoize the mention trigger regexes per trigger and bail out of
setMenuParent when the shadow-root parent is unchanged, trimming the
per-keystroke render path.

Measured in the SureRank metabox repro: a fast-typed sentence went from
a 15.3s main-thread task (~333ms/keystroke) to 0.38s total, with
collapsed sections back to mounting zero editors.
…ention

fix(editor-input): support Lexical mention menu in Shadow DOM
…abels

Add seriesLabels prop to LineChart for translatable tooltip series names
ravindrakele and others added 3 commits June 10, 2026 21:06
Adds an optional enableTimeSelection boolean prop (default false) that
renders time inputs between the calendar grid and the footer:

- single: one time input that sets hours/minutes on the selected date
- range: start/end time inputs for from/to (end disabled until set)
- multiple: flag is ignored, no time UI

Times merge into the selected Date objects via date-fns setHours/
setMinutes, so onDateSelect/onApply payloads carry the chosen time and
picking a new date preserves it. Same-day ranges are kept valid: the
end can never land before the start, whether completed by click or
edited via the time inputs. With the flag off, behavior and output are
unchanged.

Includes interaction tests (play functions) covering enable/disable
states, time preservation, deselect with non-midnight times, endpoint
swap, same-day clamping, and payload contents; plus stories and readme
documentation for the new prop.
Adds an optional enableTimeSelection boolean prop (default false) that
renders time inputs between the calendar grid and the footer:

- single: one time input that sets hours/minutes on the selected date
- range: start/end time inputs for from/to (end disabled until set)
- multiple: flag is ignored, no time UI

Times merge into the selected Date objects via date-fns setHours/
setMinutes, so onDateSelect/onApply payloads carry the chosen time and
picking a new date preserves it. Same-day ranges are kept valid: the
end can never land before the start, whether completed by click or
edited via the time inputs. With the flag off, behavior and output are
unchanged.

Includes interaction tests (play functions) covering enable/disable
states, time preservation, deselect with non-midnight times, endpoint
swap, same-day clamping, and payload contents; plus stories and readme
documentation for the new prop.
@jaieds jaieds changed the base branch from master to staging June 11, 2026 10:31
@@ -644,6 +818,16 @@ const DatePickerComponent = ( {
/* eslint-disable @typescript-eslint/no-explicit-any */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Ensure proper accessibility attributes are being used for the new time input fields, such as aria-labelledby to specify labels for screen readers.

Why: Accessibility is crucial for inclusivity, allowing users with disabilities to effectively interact with your UI. Missing attributes may prevent these users from understanding and using the component correctly.

How: Add aria-labelledby attributes to the time input elements that correspond to their labels for better screen reader support.

format,
startOfToday,
startOfYesterday,
startOfWeek,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The new enableTimeSelection prop added to the DatePicker should have appropriate validation in the component to ensure that it only accepts boolean values. Currently, there’s a possibility that undefined or non-boolean values might be passed, leading to unpredictable behaviors.

Why: Validating prop types helps to ensure that the component behaves reliably, reducing potential runtime errors and making the component easier to debug and use. This is especially important since other developers will use this component and might not enforce these types strictly.

How: You can use PropTypes validation or TypeScript's type checking more effectively to enforce the boolean constraint. For example, use PropTypes.bool.isRequired if not using TypeScript or ensure TypeScript correctly represents this via interfaces.

enableTimeSelection: {
control: { type: 'boolean' },
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The tests extensively cover the time selection's functionality; however, there should be tests for edge cases and invalid inputs (e.g., entering invalid time strings in the time inputs).

Why: Testing for edge cases ensures that the component can gracefully handle unexpected user inputs, thereby increasing reliability and user satisfaction. It also helps prevent potential bugs from slipping into production.

How: Add additional test scenarios that simulate incorrect inputs, such as an out-of-range time (e.g., '25:00') or non-numeric strings. Confirm that the component properly handles these cases, either by maintaining current values or resetting properly.

// Selecting a date enables the input at midnight.
await userEvent.click( getDayButton( canvasElement, 15 ) );
await expect( timeInput ).toBeEnabled();
await expect( timeInput ).toHaveValue( '00:00' );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: In the singleTimeSelectionTest, ensure you handle the possibility that the input formats could be altered or affected by localization settings. Currently, the assumptions of input formats might not hold in all environments.

Why: If the application has users in different locales, the way time is represented can vary (e.g., 12-hour vs. 24-hour formats). Failing to handle these properly could lead to incorrect data being captured or displayed, ultimately frustrating users.

How: Consider using libraries that help with localization, like date-fns, for formatting times in a way that respects user preferences. Ensure tests represent common local expectations globally.

await expect( endInput ).toHaveValue( '09:15' );

// Editing a time that would invert the range realigns the other endpoint.
await fireEvent.change( endInput, { target: { value: '06:00' } } );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The implementation of the new tests is thorough, but it could benefit from additional non-happy path scenarios to check the resilience of the components against invalid props or states.

Why: Robust tests should not only cover successful use cases but also check how the component reacts in failure scenarios. This can help to ensure robustness and reliability, driving down future maintenance costs.

How: Implement tests that ensure the component fails gracefully when provided with invalid inputs and check for proper error messages or states. You can also include tests to observe performance under heavy load.

@@ -36,6 +36,13 @@ export interface DatePickerProps {
showOutsideDays?: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The enableTimeSelection prop needs to be validated for boolean type before usage.

Why: Validating props helps prevent runtime errors and ensures that the component behaves as expected when incorrect data types are passed in. This is essential for maintaining the reliability and predictability of your component.

How: You can use PropTypes or TypeScript's type-checking features and consider adding a condition to handle unexpected values gracefully, such as defaulting to false if an invalid type is provided.

@@ -61,6 +68,7 @@ const DatePicker = ( {
cancelButtonText = 'Cancel',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Ensure that enableTimeSelection only allows boolean values (true/false).

Why: Providing a clear delineation of accepted values enhances the robustness of your component as consumers may inadvertently pass other data types which could lead to unexpected behavior.

How: You can add runtime validation in the function component like if (typeof enableTimeSelection !== 'boolean') { enableTimeSelection = false; } to enforce a boolean value.

@@ -199,6 +208,7 @@ const DatePicker = ( {
mode={ selectionType }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Consider memoizing the rendering of DatePickerComponent if the props are expensive to calculate or if they don't change often.

Why: Using React's useMemo or React.memo can prevent unnecessary re-renders of child components, thereby improving performance, especially if the DatePicker is used in a larger application with frequent updates.

How: Wrap the DatePickerComponent call inside a useMemo hook, and provide dependency arrays consisting of any props that would affect its rendering.

import { format, getHours, getMinutes, setHours, setMinutes } from 'date-fns';

export const currentTimeDot = () => {
return (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: Consider validating input types for the mergeDateTime function to ensure they are instances of Date.

Why: This validation will help prevent runtime errors if unexpected data types are passed to the function, improving overall stability and security of the code.

How: You can use instanceof Date to check if timeSource and date are valid date objects before proceeding with the logic. For example:

export const mergeDateTime = (date: Date, timeSource: Date | undefined) => {
    if (!(date instanceof Date) || (timeSource && !(timeSource instanceof Date))) {
        throw new Error('Invalid input: expected a Date instance.');
    }
    // existing logic... 
}```

@@ -14,6 +14,16 @@ export const generateYearRange = ( start: number, count = 24 ) => {
return Array.from( { length: count }, ( _, i ) => start + i );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What: The mergeDateTime function may not handle cases where timeSource has invalid or non-standard date values. Consider adding a fallback mechanism.

Why: This would ensure that any invalid date inputs do not cause application crashes and maintains a seamless user experience.

How: Check if getHours and getMinutes return valid values before using them to set hours and minutes. If they return NaN or invalid values, handle them appropriately (for example, you can default these to 0):

const hours = getHours(timeSource);
const minutes = getMinutes(timeSource);
if (isNaN(hours) || isNaN(minutes)) {
    return date; // or handle differently
}
return setMinutes(setHours(date, hours), minutes);

jaieds added 2 commits June 11, 2026 16:38
…tion off

Covers the pre-existing behavior paths to guarantee enableTimeSelection
(default false) changes nothing: single select/deselect at midnight,
range complete/deselect-from/deselect-to/endpoint-swap/same-day range,
multiple add/remove, dualdate cross-month range, presets initial
selected value and preset click, Apply/Cancel payloads, and zero
time inputs rendered in all flag-off stories.
…tion off

Covers the pre-existing behavior paths to guarantee enableTimeSelection
(default false) changes nothing: single select/deselect at midnight,
range complete/deselect-from/deselect-to/endpoint-swap/same-day range,
multiple add/remove, dualdate cross-month range, presets initial
selected value and preset click, Apply/Cancel payloads, and zero
time inputs rendered in all flag-off stories.
@jaieds jaieds merged commit 15e3baf into staging Jun 11, 2026
6 of 7 checks passed
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.

3 participants