From 2c0c6e081d96fc821c1296df5c4f1fba9a8c162c Mon Sep 17 00:00:00 2001 From: Toppanto Bence Date: Wed, 12 Jun 2024 20:01:38 +0200 Subject: [PATCH] feat(shared-types,ui-calendar,ui-select): add yearpicker functionality to calendar Closes: INSTUI-4086, INSTUI-4087 This also FIXES ui-select, where autoscroll was turned off by default --- package-lock.json | 1 + .../src/ComponentThemeVariables.ts | 2 + packages/ui-calendar/package.json | 1 + packages/ui-calendar/src/Calendar/README.md | 37 ++++++++ packages/ui-calendar/src/Calendar/index.tsx | 86 ++++++++++++++++--- packages/ui-calendar/src/Calendar/props.ts | 29 ++++++- packages/ui-calendar/src/Calendar/styles.ts | 18 +++- packages/ui-calendar/src/Calendar/theme.ts | 2 + packages/ui-calendar/tsconfig.build.json | 3 + packages/ui-select/src/Select/index.tsx | 2 +- 10 files changed, 162 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98488b1673..748fb49e26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46247,6 +46247,7 @@ "@instructure/ui-icons": "9.0.1", "@instructure/ui-prop-types": "9.0.1", "@instructure/ui-react-utils": "9.0.1", + "@instructure/ui-simple-select": "9.0.1", "@instructure/ui-testable": "9.0.1", "@instructure/ui-utils": "9.0.1", "@instructure/ui-view": "9.0.1", diff --git a/packages/shared-types/src/ComponentThemeVariables.ts b/packages/shared-types/src/ComponentThemeVariables.ts index 58e8840103..e787504a00 100644 --- a/packages/shared-types/src/ComponentThemeVariables.ts +++ b/packages/shared-types/src/ComponentThemeVariables.ts @@ -283,6 +283,8 @@ export type CalendarTheme = { color: Colors['textDarkest'] background: Colors['backgroundLightest'] navMargin: Spacing['small'] + navWithYearMargin: Spacing['xSmall'] + yearPickerMargin: Spacing['mediumSmall'] maxHeaderWidth: Spacing['medium'] } diff --git a/packages/ui-calendar/package.json b/packages/ui-calendar/package.json index c20e6f00ba..dfadabb979 100644 --- a/packages/ui-calendar/package.json +++ b/packages/ui-calendar/package.json @@ -45,6 +45,7 @@ "@instructure/ui-testable": "9.0.1", "@instructure/ui-utils": "9.0.1", "@instructure/ui-view": "9.0.1", + "@instructure/ui-simple-select": "9.0.1", "@instructure/uid": "9.0.1", "prop-types": "^15.8.1" }, diff --git a/packages/ui-calendar/src/Calendar/README.md b/packages/ui-calendar/src/Calendar/README.md index ed7a26c653..f11810fd17 100644 --- a/packages/ui-calendar/src/Calendar/README.md +++ b/packages/ui-calendar/src/Calendar/README.md @@ -48,6 +48,43 @@ class Example extends React.Component { render() ``` +### With year picker + +```js +--- +type: example +--- +class Example extends React.Component { + state = { + selectedDate: "", + visibleMonth:"2024-02" + } + + setVisibleMonth = () => { + this.setState({visibleMonth: "2028-09"}) + } + render = () => + this.setState({visibleMonth: requestedMonth})} + onRequestRenderPrevMonth={(_e, requestedMonth) => this.setState({visibleMonth: requestedMonth})} + onDateSelected={(date)=>{ + this.setState({selectedDate: date}) + }} + withYearPicker={{ + screenReaderLabel: "Year picker", + startYear:1999, + + maxHeight: "200px" + }} +/> +} +render() +``` + ### Composing a Calendar in your Application By design, the `Calendar` component does not dictate which date libraries or diff --git a/packages/ui-calendar/src/Calendar/index.tsx b/packages/ui-calendar/src/Calendar/index.tsx index 4ff0038b56..08abcfc3d2 100644 --- a/packages/ui-calendar/src/Calendar/index.tsx +++ b/packages/ui-calendar/src/Calendar/index.tsx @@ -58,6 +58,8 @@ import { IconArrowOpenEndSolid } from '@instructure/ui-icons' +import { SimpleSelect } from '@instructure/ui-simple-select' + /** --- category: components @@ -197,9 +199,21 @@ class Calendar extends Component { this.setState({ visibleMonth: newDate }) } - renderHeader() { - const { renderNavigationLabel, styles } = this.props + handleYearChange = (e: React.MouseEvent, year: number) => { + const { withYearPicker } = this.props const { visibleMonth } = this.state + const newDate = visibleMonth.clone() + if (withYearPicker?.onRequestYearChange) { + withYearPicker.onRequestYearChange(e, year) + return + } + newDate.year(year) + this.setState({ visibleMonth: newDate }) + } + + renderHeader() { + const { renderNavigationLabel, styles, withYearPicker } = this.props + const { visibleMonth, today } = this.state const { prevButton, nextButton } = this.renderMonthNavigationButtons() const cloneButton = ( @@ -218,18 +232,64 @@ class Calendar extends Component { ...(prevButton || nextButton ? [styles?.navigationWithButtons] : []) ] + let yearList: number[] = [] + + if (withYearPicker) { + const { startYear, endYear, years } = withYearPicker + if (years) { + yearList = years + } else { + if (!startYear) { + console.warn( + 'You need to provide at least `startYear` or `years` in `withYearPicker`' + ) + } + + const years = endYear || today.year() + for (let year = years; year >= startYear!; year--) { + yearList.push(year) + } + } + } + return ( -
- {prevButton && cloneButton(prevButton, this.handleMonthChange('prev'))} - {renderNavigationLabel ? ( - callRenderProp(renderNavigationLabel) - ) : ( - -
{visibleMonth.format('MMMM')}
-
{visibleMonth.format('YYYY')}
-
- )} - {nextButton && cloneButton(nextButton, this.handleMonthChange('next'))} +
+
+ {prevButton && + cloneButton(prevButton, this.handleMonthChange('prev'))} + {renderNavigationLabel ? ( + callRenderProp(renderNavigationLabel) + ) : ( + +
{visibleMonth.format('MMMM')}
+ {!withYearPicker ? ( +
{visibleMonth.format('YYYY')}
+ ) : null} +
+ )} + {nextButton && + cloneButton(nextButton, this.handleMonthChange('next'))} +
+ + {withYearPicker ? ( +
+ + this.handleYearChange(e, Number(value)) + } + > + {yearList.map((year) => ( + + {`${year}`} + + ))} + +
+ ) : null}
) } diff --git a/packages/ui-calendar/src/Calendar/props.ts b/packages/ui-calendar/src/Calendar/props.ts index c0f51f2b30..38b190fa01 100644 --- a/packages/ui-calendar/src/Calendar/props.ts +++ b/packages/ui-calendar/src/Calendar/props.ts @@ -152,6 +152,30 @@ type CalendarOwnProps = { * Visible month for the rendered calendar. Formatted as an ISO date string. */ visibleMonth?: string + + /** + * If set, years can be picked from a dropdown. + * It accepts an object. + * screenReaderLabel: string // e.g.: i18n("pick a year") + * + * maxHeight?: string // e.g.: 200px, Defaults to 300px. Sets the dropdown max height + * + * onRequestYearChange?:(e: React.MouseEvent,requestedYear: number): void // if set, on year change, only this will be called and no internal change will take place + * + * startYear?: number // e.g.: 2001, sets the start year of the selectable list + * + * endYear?: number // e.g.: 2030, sets the end year of the selectable list. If not provided with startYear, it'll default to the currentDate's year + * + * years?: number[] // e.g.: [2001,2002,2003,2010], if set, startYear and endYear will be ignored and this list will be used as selectable list + */ + withYearPicker?: { + screenReaderLabel: string + maxHeight?: string + onRequestYearChange?: (e: any, requestedYear: number) => void + startYear?: number + endYear?: number + years?: never + } } type PropKeys = keyof CalendarOwnProps @@ -163,7 +187,7 @@ type CalendarProps = CalendarOwnProps & OtherHTMLAttributes type CalendarStyle = ComponentStyle< - 'navigation' | 'navigationWithButtons' | 'weekdayHeader' + 'navigation' | 'navigationWithButtons' | 'weekdayHeader' | 'yearPicker' > const propTypes: PropValidators = { @@ -184,7 +208,8 @@ const propTypes: PropValidators = { role: PropTypes.oneOf(['table', 'listbox']), selectedDate: PropTypes.string, timezone: PropTypes.string, - visibleMonth: PropTypes.string + visibleMonth: PropTypes.string, + withYearPicker: PropTypes.object } const allowedProps: AllowedPropKeys = [ diff --git a/packages/ui-calendar/src/Calendar/styles.ts b/packages/ui-calendar/src/Calendar/styles.ts index ff63c50d95..3d6e25d4ed 100644 --- a/packages/ui-calendar/src/Calendar/styles.ts +++ b/packages/ui-calendar/src/Calendar/styles.ts @@ -23,7 +23,7 @@ */ import type { CalendarTheme } from '@instructure/shared-types' -import type { CalendarStyle } from './props' +import type { CalendarStyle, CalendarProps } from './props' /** * --- @@ -33,7 +33,11 @@ import type { CalendarStyle } from './props' * @param {Object} componentTheme The theme variable object. * @return {Object} The final style object, which will be used in the component */ -const generateStyle = (componentTheme: CalendarTheme): CalendarStyle => { +const generateStyle = ( + componentTheme: CalendarTheme, + props: CalendarProps +): CalendarStyle => { + const { withYearPicker } = props return { navigation: { label: 'calendar__navigation', @@ -43,7 +47,9 @@ const generateStyle = (componentTheme: CalendarTheme): CalendarStyle => { fontFamily: componentTheme.fontFamily, fontSize: componentTheme.fontSize, fontWeight: componentTheme.fontWeight, - marginBottom: componentTheme.navMargin + marginBottom: withYearPicker + ? componentTheme.navWithYearMargin + : componentTheme.navMargin }, navigationWithButtons: { label: 'calendar__navigation--withButtons', @@ -55,6 +61,12 @@ const generateStyle = (componentTheme: CalendarTheme): CalendarStyle => { label: 'calendar__weekdayHeader', textAlign: 'center', maxWidth: componentTheme.maxHeaderWidth + }, + yearPicker: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginBottom: componentTheme.yearPickerMargin } } } diff --git a/packages/ui-calendar/src/Calendar/theme.ts b/packages/ui-calendar/src/Calendar/theme.ts index c841af95bf..33e70b413c 100644 --- a/packages/ui-calendar/src/Calendar/theme.ts +++ b/packages/ui-calendar/src/Calendar/theme.ts @@ -48,6 +48,8 @@ const generateComponentTheme = (theme: Theme): CalendarTheme => { background: colors?.backgroundLightest, navMargin: spacing?.small, + navWithYearMargin: spacing?.xSmall, + yearPickerMargin: spacing?.mediumSmall, maxHeaderWidth: spacing?.medium } diff --git a/packages/ui-calendar/tsconfig.build.json b/packages/ui-calendar/tsconfig.build.json index f2ab0cb9bf..ff8dae0706 100644 --- a/packages/ui-calendar/tsconfig.build.json +++ b/packages/ui-calendar/tsconfig.build.json @@ -60,6 +60,9 @@ }, { "path": "../ui-icons/tsconfig.build.json" + }, + { + "path": "../ui-simple-select/tsconfig.build.json" } ] } diff --git a/packages/ui-select/src/Select/index.tsx b/packages/ui-select/src/Select/index.tsx index 6830ed0bfa..9429a35415 100644 --- a/packages/ui-select/src/Select/index.tsx +++ b/packages/ui-select/src/Select/index.tsx @@ -149,7 +149,7 @@ class Select extends Component { placement: 'bottom stretch', constrain: 'window', shouldNotWrap: false, - scrollToHighlightedOption: false + scrollToHighlightedOption: true } static Option = Option