Skip to content

Commit

Permalink
feat(shared-types,ui-calendar,ui-select): add yearpicker functionalit…
Browse files Browse the repository at this point in the history
…y to calendar

Closes: INSTUI-4086, INSTUI-4087

This also FIXES ui-select, where autoscroll was turned off by default
  • Loading branch information
HerrTopi committed Jun 14, 2024
1 parent dbacc82 commit 2c0c6e0
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 19 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/shared-types/src/ComponentThemeVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}

Expand Down
1 change: 1 addition & 0 deletions packages/ui-calendar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
37 changes: 37 additions & 0 deletions packages/ui-calendar/src/Calendar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,43 @@ class Example extends React.Component {
render(<Example />)
```

### With year picker

```js
---
type: example
---
class Example extends React.Component {
state = {
selectedDate: "",
visibleMonth:"2024-02"
}

setVisibleMonth = () => {
this.setState({visibleMonth: "2028-09"})
}
render = () =>
<Calendar
visibleMonth={this.state.visibleMonth}
currentDate="2024-02-29"
disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
selectedDate={this.state.selectedDate}
onRequestRenderNextMonth={(_e, requestedMonth) => 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(<Example />)
```

### Composing a Calendar in your Application

By design, the `Calendar` component does not dictate which date libraries or
Expand Down
86 changes: 73 additions & 13 deletions packages/ui-calendar/src/Calendar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
IconArrowOpenEndSolid
} from '@instructure/ui-icons'

import { SimpleSelect } from '@instructure/ui-simple-select'

/**
---
category: components
Expand Down Expand Up @@ -197,9 +199,21 @@ class Calendar extends Component<CalendarProps, CalendarState> {
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 = (
Expand All @@ -218,18 +232,64 @@ class Calendar extends Component<CalendarProps, CalendarState> {
...(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 (
<div css={style}>
{prevButton && cloneButton(prevButton, this.handleMonthChange('prev'))}
{renderNavigationLabel ? (
callRenderProp(renderNavigationLabel)
) : (
<span>
<div>{visibleMonth.format('MMMM')}</div>
<div>{visibleMonth.format('YYYY')}</div>
</span>
)}
{nextButton && cloneButton(nextButton, this.handleMonthChange('next'))}
<div>
<div css={style}>
{prevButton &&
cloneButton(prevButton, this.handleMonthChange('prev'))}
{renderNavigationLabel ? (
callRenderProp(renderNavigationLabel)
) : (
<span>
<div>{visibleMonth.format('MMMM')}</div>
{!withYearPicker ? (
<div>{visibleMonth.format('YYYY')}</div>
) : null}
</span>
)}
{nextButton &&
cloneButton(nextButton, this.handleMonthChange('next'))}
</div>

{withYearPicker ? (
<div css={styles?.yearPicker}>
<SimpleSelect
width="90px"
renderLabel=""
assistiveText={withYearPicker.screenReaderLabel}
value={Number(visibleMonth.format('YYYY'))}
onChange={(e: any, { value }: any) =>
this.handleYearChange(e, Number(value))
}
>
{yearList.map((year) => (
<SimpleSelect.Option key={year} id={`opt-${year}`} value={year}>
{`${year}`}
</SimpleSelect.Option>
))}
</SimpleSelect>
</div>
) : null}
</div>
)
}
Expand Down
29 changes: 27 additions & 2 deletions packages/ui-calendar/src/Calendar/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -163,7 +187,7 @@ type CalendarProps = CalendarOwnProps &
OtherHTMLAttributes<CalendarOwnProps>

type CalendarStyle = ComponentStyle<
'navigation' | 'navigationWithButtons' | 'weekdayHeader'
'navigation' | 'navigationWithButtons' | 'weekdayHeader' | 'yearPicker'
>

const propTypes: PropValidators<PropKeys> = {
Expand All @@ -184,7 +208,8 @@ const propTypes: PropValidators<PropKeys> = {
role: PropTypes.oneOf(['table', 'listbox']),
selectedDate: PropTypes.string,
timezone: PropTypes.string,
visibleMonth: PropTypes.string
visibleMonth: PropTypes.string,
withYearPicker: PropTypes.object
}

const allowedProps: AllowedPropKeys = [
Expand Down
18 changes: 15 additions & 3 deletions packages/ui-calendar/src/Calendar/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

import type { CalendarTheme } from '@instructure/shared-types'
import type { CalendarStyle } from './props'
import type { CalendarStyle, CalendarProps } from './props'

/**
* ---
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/ui-calendar/src/Calendar/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const generateComponentTheme = (theme: Theme): CalendarTheme => {
background: colors?.backgroundLightest,

navMargin: spacing?.small,
navWithYearMargin: spacing?.xSmall,
yearPickerMargin: spacing?.mediumSmall,

maxHeaderWidth: spacing?.medium
}
Expand Down
3 changes: 3 additions & 0 deletions packages/ui-calendar/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
},
{
"path": "../ui-icons/tsconfig.build.json"
},
{
"path": "../ui-simple-select/tsconfig.build.json"
}
]
}
2 changes: 1 addition & 1 deletion packages/ui-select/src/Select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class Select extends Component<SelectProps> {
placement: 'bottom stretch',
constrain: 'window',
shouldNotWrap: false,
scrollToHighlightedOption: false
scrollToHighlightedOption: true
}

static Option = Option
Expand Down

0 comments on commit 2c0c6e0

Please sign in to comment.