[SUR-747] [Improvement] - Implement Force UI Date and Time Picker for Event Schema.#462
Conversation
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
…s and perf fixes" This reverts commit b106218.
…abels Add seriesLabels prop to LineChart for translatable tooltip series names
Update changelog.txt
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.
| @@ -644,6 +818,16 @@ const DatePickerComponent = ( { | |||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | |||
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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' }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
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' ); |
There was a problem hiding this comment.
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' } } ); |
There was a problem hiding this comment.
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; | |||
There was a problem hiding this comment.
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', | |||
There was a problem hiding this comment.
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 } | |||
There was a problem hiding this comment.
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 ( |
There was a problem hiding this comment.
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 ); | |||
There was a problem hiding this comment.
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);…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.
…/force-ui into date-time-picker
Summary
Adds an opt-in
enableTimeSelectionboolean prop (defaultfalse) to theDatePickercomponent, 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 forfrom/to(end input disabled until an end date is selected).selectionType="multiple": the flag is ignored; no time UI is rendered.Dateobjects via date-fnssetHours/setMinutes— the date portion is never replaced, andonDateSelect/onApplypayloads carry the chosen time. Picking a new date preserves the previously chosen time.Changes
src/components/datepicker/datepicker.tsx— newenableTimeSelectionprop, 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.tsx—mergeDateTimehelper.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.onDateSelect/onApplypayload contents.