From 2ba19f6a2b0d50c178e4d6eb45e385b6fcb53870 Mon Sep 17 00:00:00 2001 From: "Blake V." <87083504+bvandercar-vt@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:05:16 -0600 Subject: [PATCH 001/121] refactor(mergeRefs): allow undefined (#6987) --- packages/core/src/common/refs.ts | 2 +- packages/core/src/components/dialog/dialog.tsx | 5 +---- packages/core/src/components/forms/controls.tsx | 2 +- packages/datetime/src/components/date-input/dateInput.tsx | 2 +- .../datetime2/src/components/date-input3/dateInput3.tsx | 2 +- packages/popover2/src/popover2.tsx | 2 +- .../select/src/components/multi-select/multiSelect.tsx | 7 +------ 7 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/core/src/common/refs.ts b/packages/core/src/common/refs.ts index ad4f238621..02705456c2 100644 --- a/packages/core/src/common/refs.ts +++ b/packages/core/src/common/refs.ts @@ -40,7 +40,7 @@ export function setRef(refTarget: React.Ref | undefined, ref: T | null): v * Utility for merging refs into one singular callback ref. * If using in a functional component, would recomend using `useMemo` to preserve function identity. */ -export function mergeRefs(...refs: Array>): React.RefCallback { +export function mergeRefs(...refs: Array | undefined>): React.RefCallback { return value => { refs.forEach(ref => { setRef(ref, value); diff --git a/packages/core/src/components/dialog/dialog.tsx b/packages/core/src/components/dialog/dialog.tsx index 6e07ced978..7d5212806c 100644 --- a/packages/core/src/components/dialog/dialog.tsx +++ b/packages/core/src/components/dialog/dialog.tsx @@ -142,10 +142,7 @@ export class Dialog extends AbstractPureComponent { childRef={this.childRef} hasBackdrop={true} > -
+
= React.forwardRef((props, ref) = ); const localInputRef = React.useRef(null); - const inputRef = props.inputRef === undefined ? localInputRef : mergeRefs(props.inputRef, localInputRef); + const inputRef = mergeRefs(props.inputRef, localInputRef); const handleChange = React.useCallback( (evt: React.ChangeEvent) => { diff --git a/packages/datetime/src/components/date-input/dateInput.tsx b/packages/datetime/src/components/date-input/dateInput.tsx index e30154dd0c..0a017195d6 100644 --- a/packages/datetime/src/components/date-input/dateInput.tsx +++ b/packages/datetime/src/components/date-input/dateInput.tsx @@ -647,7 +647,7 @@ export const DateInput: React.FC = React.memo(function _DateInpu aria-expanded={targetIsOpen} disabled={props.disabled} fill={fill} - inputRef={mergeRefs(ref, inputRef, props.inputProps?.inputRef ?? null)} + inputRef={mergeRefs(ref, inputRef, props.inputProps?.inputRef)} onBlur={handleInputBlur} onChange={handleInputChange} onClick={handleInputClick} diff --git a/packages/datetime2/src/components/date-input3/dateInput3.tsx b/packages/datetime2/src/components/date-input3/dateInput3.tsx index 38f767abeb..f8d1cde790 100644 --- a/packages/datetime2/src/components/date-input3/dateInput3.tsx +++ b/packages/datetime2/src/components/date-input3/dateInput3.tsx @@ -518,7 +518,7 @@ export const DateInput3: React.FC = React.memo(function _DateIn aria-expanded={targetIsOpen} disabled={disabled} fill={fill} - inputRef={mergeRefs(ref, inputRef, inputProps?.inputRef ?? null)} + inputRef={mergeRefs(ref, inputRef, inputProps?.inputRef)} onBlur={handleInputBlur} onChange={handleInputChange} onClick={handleInputClick} diff --git a/packages/popover2/src/popover2.tsx b/packages/popover2/src/popover2.tsx index fa82e25627..8fb46af73d 100644 --- a/packages/popover2/src/popover2.tsx +++ b/packages/popover2/src/popover2.tsx @@ -49,7 +49,7 @@ export class Popover2< >, this.ref)} + ref={mergeRefs(ref as React.Ref>, this.ref)} {...props} /> ); diff --git a/packages/select/src/components/multi-select/multiSelect.tsx b/packages/select/src/components/multi-select/multiSelect.tsx index c1be8deeae..1d69815585 100644 --- a/packages/select/src/components/multi-select/multiSelect.tsx +++ b/packages/select/src/components/multi-select/multiSelect.tsx @@ -215,11 +215,6 @@ export class MultiSelect extends AbstractPureComponent, M const { disabled, popoverContentProps = {}, popoverProps = {} } = this.props; const { handleKeyDown, handleKeyUp } = listProps; - const popoverRef = - this.props.popoverRef === undefined - ? this.refHandlers.popover - : mergeRefs(this.refHandlers.popover, this.props.popoverRef); - // N.B. no need to set `popoverProps.fill` since that is unused with the `renderTarget` API return ( extends AbstractPureComponent, M onOpened={this.handlePopoverOpened} popoverClassName={classNames(Classes.MULTISELECT_POPOVER, popoverProps.popoverClassName)} popupKind={PopupKind.LISTBOX} - ref={popoverRef} + ref={mergeRefs(this.refHandlers.popover, this.props.popoverRef)} renderTarget={this.getPopoverTargetRenderer(listProps, this.state.isOpen)} /> ); From 1395f0195f3232615bc231900a8d945851fcff2e Mon Sep 17 00:00:00 2001 From: bofa Date: Mon, 7 Oct 2024 18:11:05 +0200 Subject: [PATCH 002/121] Update _colors.scss, make red colors default! as well (#7006) --- packages/colors/src/_colors.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/colors/src/_colors.scss b/packages/colors/src/_colors.scss index 7ca6548110..d9d226e150 100644 --- a/packages/colors/src/_colors.scss +++ b/packages/colors/src/_colors.scss @@ -49,11 +49,11 @@ $orange3: #c87619 !default; $orange4: #ec9a3c !default; $orange5: #fbb360 !default; -$red1: #8e292c; -$red2: #ac2f33; -$red3: #cd4246; -$red4: #e76a6e; -$red5: #fa999c; +$red1: #8e292c !default; +$red2: #ac2f33 !default; +$red3: #cd4246 !default; +$red4: #e76a6e !default; +$red5: #fa999c !default; // Extended colors From 0cb9e27f39e305b13d45787984c79e5a74e4b95c Mon Sep 17 00:00:00 2001 From: William Hammond <71682563+williamshammond@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:12:31 -0400 Subject: [PATCH 003/121] feat: new icon - Globe: network add (#7008) Co-authored-by: Will Hammond Co-authored-by: Gregory Douglas <2968519+ggdouglas@users.noreply.github.com> --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/globe-network-add.svg | 4 ++++ resources/icons/20px/globe-network-add.svg | 4 ++++ 3 files changed, 15 insertions(+) create mode 100644 resources/icons/16px/globe-network-add.svg create mode 100644 resources/icons/20px/globe-network-add.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index e370333dc0..ac8b291387 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4485,5 +4485,12 @@ "tags": "sidebar, layout, window, side bar, drawer, menu, website, dashboard", "group": "table", "codepoint": 62337 + }, + { + "displayName": "Globe: network add", + "iconName": "globe-network-add", + "tags": "planet, earth, map, world, internet, website, create, plus", + "group": "miscellaneous", + "codepoint": 62338 } ] diff --git a/resources/icons/16px/globe-network-add.svg b/resources/icons/16px/globe-network-add.svg new file mode 100644 index 0000000000..a2704249fe --- /dev/null +++ b/resources/icons/16px/globe-network-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/globe-network-add.svg b/resources/icons/20px/globe-network-add.svg new file mode 100644 index 0000000000..c9c8c892ce --- /dev/null +++ b/resources/icons/20px/globe-network-add.svg @@ -0,0 +1,4 @@ + + + + From 73502b15a56eccbd3e67ef16e4871ad378913f71 Mon Sep 17 00:00:00 2001 From: "Blake V." <87083504+bvandercar-vt@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:22:18 -0600 Subject: [PATCH 004/121] feat(Breadcrumbs): use ol instead of ul (#7007) --- packages/core/src/components/breadcrumbs/_breadcrumbs.scss | 6 +++--- packages/core/src/components/breadcrumbs/breadcrumbs.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss index 096ac874c7..c068826167 100644 --- a/packages/core/src/components/breadcrumbs/_breadcrumbs.scss +++ b/packages/core/src/components/breadcrumbs/_breadcrumbs.scss @@ -8,13 +8,13 @@ Breadcrumbs Markup: - + Styleguide breadcrumbs */ @@ -30,7 +30,7 @@ Styleguide breadcrumbs margin: 0; padding: 0; - // descendant selector because nothing should come between ul and li + // descendant selector because nothing should come between ol and li > li { align-items: center; display: flex; diff --git a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx index cbc0596882..55bda266a6 100644 --- a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx +++ b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx @@ -97,7 +97,7 @@ export class Breadcrumbs extends AbstractPureComponent { Date: Tue, 8 Oct 2024 11:52:18 -0400 Subject: [PATCH 005/121] [Icons] Add cube-edit icon (#7010) --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/cube-edit.svg | 4 ++++ resources/icons/20px/cube-edit.svg | 4 ++++ 3 files changed, 15 insertions(+) create mode 100644 resources/icons/16px/cube-edit.svg create mode 100644 resources/icons/20px/cube-edit.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index ac8b291387..59679ebca6 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4492,5 +4492,12 @@ "tags": "planet, earth, map, world, internet, website, create, plus", "group": "miscellaneous", "codepoint": 62338 + }, + { + "displayName": "Cube: edit", + "iconName": "cube-edit", + "tags": "shape, 3d, object, box, module, block, geometry, change, modify, update", + "group": "miscellaneous", + "codepoint": 62339 } ] diff --git a/resources/icons/16px/cube-edit.svg b/resources/icons/16px/cube-edit.svg new file mode 100644 index 0000000000..a515f08a29 --- /dev/null +++ b/resources/icons/16px/cube-edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/cube-edit.svg b/resources/icons/20px/cube-edit.svg new file mode 100644 index 0000000000..7dd8d9ae6f --- /dev/null +++ b/resources/icons/20px/cube-edit.svg @@ -0,0 +1,4 @@ + + + + From f4b4126be1e581ed5e53479cf5142ff0a699c1b8 Mon Sep 17 00:00:00 2001 From: "Blake V." <87083504+bvandercar-vt@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:53:04 -0600 Subject: [PATCH 006/121] rename: DragReorderableProps (#7009) --- packages/table/src/interactions/reorderable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/table/src/interactions/reorderable.tsx b/packages/table/src/interactions/reorderable.tsx index ab2f5da0ec..fb7d5bd00c 100644 --- a/packages/table/src/interactions/reorderable.tsx +++ b/packages/table/src/interactions/reorderable.tsx @@ -65,7 +65,7 @@ export interface ReorderableProps { selectedRegions?: Region[]; } -export interface DragReorderable extends ReorderableProps, DraggableChildrenProps { +export interface DragReorderableProps extends ReorderableProps, DraggableChildrenProps { /** * Whether the reordering behavior is disabled. * @@ -93,8 +93,8 @@ export interface DragReorderable extends ReorderableProps, DraggableChildrenProp toRegion: (index1: number, index2?: number) => Region; } -export class DragReorderable extends React.PureComponent { - public static defaultProps: Partial = { +export class DragReorderable extends React.PureComponent { + public static defaultProps: Partial = { selectedRegions: [], }; From 05237570f7ce3f9251e01e2af60fc01d249cca32 Mon Sep 17 00:00:00 2001 From: "Blake V." <87083504+bvandercar-vt@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:54:46 -0600 Subject: [PATCH 007/121] fix breadcrumbs collapsed a11y failure (#6985) Co-authored-by: Gregory Douglas <2968519+ggdouglas@users.noreply.github.com> --- .../src/components/breadcrumbs/breadcrumbs.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx index 55bda266a6..73ce366f4f 100644 --- a/packages/core/src/components/breadcrumbs/breadcrumbs.tsx +++ b/packages/core/src/components/breadcrumbs/breadcrumbs.tsx @@ -65,6 +65,11 @@ export interface BreadcrumbsProps extends Props { */ minVisibleItems?: number; + /** + * Props to spread to the `OverflowList` popover target. + */ + overflowButtonProps?: React.HTMLProps; + /** * Props to spread to `OverflowList`. Note that `items`, * `overflowRenderer`, and `visibleItemRenderer` cannot be changed. @@ -108,7 +113,7 @@ export class Breadcrumbs extends AbstractPureComponent { } private renderOverflow = (items: readonly BreadcrumbProps[]) => { - const { collapseFrom, popoverProps } = this.props; + const { collapseFrom, overflowButtonProps, popoverProps } = this.props; let orderedItems = items; if (collapseFrom === Boundary.START) { @@ -127,7 +132,13 @@ export class Breadcrumbs extends AbstractPureComponent { content={{orderedItems.map(this.renderOverflowBreadcrumb)}} {...popoverProps} > - + ); From 6da26b85565dee5cd561a31275f3fa51f38f3d45 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Tue, 8 Oct 2024 16:41:49 -0400 Subject: [PATCH 008/121] [docs] Update/fix broken links in datetime docs (#7013) --- .../datetime/src/components/date-input/dateInput.tsx | 2 +- packages/datetime2/README.md | 2 +- packages/datetime2/src/common/dateFnsLocaleProps.ts | 2 +- packages/datetime2/src/common/reactDayPickerProps.ts | 4 ++-- .../src/components/date-input3/date-input3.md | 4 ++-- .../src/components/date-input3/dateInput3Props.ts | 2 +- .../src/components/date-picker3/date-picker3.md | 12 ++++++------ .../date-range-input3/date-range-input3.md | 2 +- .../date-range-input3/dateRangeInput3Props.ts | 2 +- .../react-day-picker/datePicker3Caption.tsx | 2 +- .../react-day-picker/datePicker3Dropdown.tsx | 2 +- packages/datetime2/src/index.md | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/datetime/src/components/date-input/dateInput.tsx b/packages/datetime/src/components/date-input/dateInput.tsx index 0a017195d6..e70616c948 100644 --- a/packages/datetime/src/components/date-input/dateInput.tsx +++ b/packages/datetime/src/components/date-input/dateInput.tsx @@ -189,7 +189,7 @@ export interface DateInputProps extends DatePickerBaseProps, DateFormatProps, Da * * Mutually exclusive with `defaultTimezone` prop. * - * @see https://www.iana.org/time-zones + * See [IANA Time Zones](https://www.iana.org/time-zones). */ timezone?: string; diff --git a/packages/datetime2/README.md b/packages/datetime2/README.md index e9975aead6..83f1cda095 100644 --- a/packages/datetime2/README.md +++ b/packages/datetime2/README.md @@ -8,7 +8,7 @@ This package contains next-generation components for interacting with dates & ti Compared to the "V1" components in @blueprintjs/datetime, the "V3" components in this package: -- use [react-day-picker](https://react-day-picker.js.org/) v8 instead of v7 (this unblocks React 18 compatibility) +- use [react-day-picker](https://daypicker.dev/v8) v8 instead of v7 (this unblocks React 18 compatibility) - are easier to internationalize & localize since date-fns is now a dependency (instead of `localeUtils`, you can specify a locale code and we'll automatically load the date-fns locale object) This package also contains legacy APIs which are re-exported aliases for components from @blueprintjs/datetime v5.x. diff --git a/packages/datetime2/src/common/dateFnsLocaleProps.ts b/packages/datetime2/src/common/dateFnsLocaleProps.ts index 41d90a2cf0..d78bf6d63b 100644 --- a/packages/datetime2/src/common/dateFnsLocaleProps.ts +++ b/packages/datetime2/src/common/dateFnsLocaleProps.ts @@ -37,9 +37,9 @@ export interface DateFnsLocaleProps { * * If you provide a locale code string and receive a loading error, please make sure it is included in the list of * date-fns' [supported locales](https://github.com/date-fns/date-fns/tree/main/src/locale). + * See date-fns [Locale](https://date-fns.org/v2.28.0/docs/Locale). * * @default "en-US" - * @see https://date-fns.org/docs/Locale */ locale?: Locale | string; } diff --git a/packages/datetime2/src/common/reactDayPickerProps.ts b/packages/datetime2/src/common/reactDayPickerProps.ts index 44deca0b1f..75044e1b53 100644 --- a/packages/datetime2/src/common/reactDayPickerProps.ts +++ b/packages/datetime2/src/common/reactDayPickerProps.ts @@ -41,7 +41,7 @@ export type DayPickerProps = Omit; export interface ReactDayPickerRangeProps { /** * Props to pass to react-day-picker's day range picker. See API documentation - * [here](https://react-day-picker.js.org/api/interfaces/DayPickerRangeProps). + * [here](https://daypicker.dev/v8/api/interfaces/DayPickerRangeProps). * * Some properties are unavailable since they are set by the component design and cannot be changed: * - "captionLayout" @@ -62,7 +62,7 @@ export interface ReactDayPickerRangeProps { export interface ReactDayPickerSingleProps { /** * Props to pass to react-day-picker's single day picker. See API documentation - * [here](https://react-day-picker.js.org/api/interfaces/DayPickerSingleProps). + * [here](https://daypicker.dev/v8/api/interfaces/DayPickerSingleProps). * * Some properties are unavailable since they are set by the component design and cannot be changed: * - "captionLayout" diff --git a/packages/datetime2/src/components/date-input3/date-input3.md b/packages/datetime2/src/components/date-input3/date-input3.md index 37f8622d37..3c1cbef0f1 100644 --- a/packages/datetime2/src/components/date-input3/date-input3.md +++ b/packages/datetime2/src/components/date-input3/date-input3.md @@ -19,7 +19,7 @@ on the wiki.
**DateInput3** has the same functionality as [DateInput](#datetime/date-input) but uses -[react-day-picker v8](https://react-day-picker.js.org/) instead of [v7](https://react-day-picker-v7.netlify.app/) +[react-day-picker v8](https://daypicker.dev/v8) instead of [v7](https://react-day-picker-v7.netlify.app/) to render its calendar. It renders an interactive [**InputGroup**](#core/components/input-group) which, when focussed, displays a [**DatePicker3**](#datetime2/date-picker3) inside a [**Popover**](#core/components/popover). It optionally renders a [**TimezoneSelect**](#datetime/timezone-select) @@ -42,7 +42,7 @@ and the `onChange` callback. In addition to top-level **DateInput3** props, you may forward some props to `` to customize react-day-picker's behavior via `dayPickerProps` (the full list is -[documented here](https://react-day-picker.js.org/api/interfaces/DayPickerSingleProps)). +[documented here](https://daypicker.dev/v8/api/interfaces/DayPickerSingleProps)). Shortcuts and modifiers are also configurable via the same API as [**DatePicker3**](#datetime2/date-picker3); see those docs for more info. diff --git a/packages/datetime2/src/components/date-input3/dateInput3Props.ts b/packages/datetime2/src/components/date-input3/dateInput3Props.ts index 321907955c..19e4021be9 100644 --- a/packages/datetime2/src/components/date-input3/dateInput3Props.ts +++ b/packages/datetime2/src/components/date-input3/dateInput3Props.ts @@ -40,7 +40,7 @@ export interface DateInput3Props * * Mutually exclusive with the `formatDate` and `parseDate` props. * - * @see https://date-fns.org/docs/format + * See date-fns [format](https://date-fns.org/docs/format). */ dateFnsFormat?: string; } diff --git a/packages/datetime2/src/components/date-picker3/date-picker3.md b/packages/datetime2/src/components/date-picker3/date-picker3.md index 7880cc6f72..32a9d2ad02 100644 --- a/packages/datetime2/src/components/date-picker3/date-picker3.md +++ b/packages/datetime2/src/components/date-picker3/date-picker3.md @@ -19,7 +19,7 @@ on the wiki.
**DatePicker3** has the same functionality as [DatePicker](#datetime/datepicker) but uses -[react-day-picker v8](https://react-day-picker.js.org/) instead of [v7](https://react-day-picker-v7.netlify.app/) +[react-day-picker v8](https://daypicker.dev/v8) instead of [v7](https://react-day-picker-v7.netlify.app/) to render its calendar. It renders a UI to choose a single date and (optionally) a time of day. Time selection is enabled by the [TimePicker](#datetime/timepicker) component. @@ -35,7 +35,7 @@ prop to listen for changes to the selected day. In addition to top-level **DatePicker3** props, you may forward some props to `` to customize react-day-picker's behavior via `dayPickerProps` (the full list is -[documented here](https://react-day-picker.js.org/api/interfaces/DayPickerSingleProps)). +[documented here](https://daypicker.dev/v8/api/interfaces/DayPickerSingleProps)). @interface DatePicker3Props @@ -63,22 +63,22 @@ The built-in **preset shortcuts** can be seen in the example above. They are as @## Modifiers -**DatePicker3** utilizes react-day-picker's built-in [modifiers](https://react-day-picker.js.org/basics/modifiers) for +**DatePicker3** utilizes react-day-picker's built-in [modifiers](https://daypicker.dev/guides/custom-modifiers#built-in-modifiers) for various functionality (highlighting the current day, showing selected days, etc.). You may extend and customize the default modifiers by specifying various properties in the `dayPickerProps` prop object. In the example below, we add a custom class name to every odd-numbered day in the calendar using a simple -[Matcher](https://react-day-picker.js.org/api/types/matcher). +[Matcher](https://daypicker.dev/api/type-aliases/Matcher). @reactExample DatePicker3ModifierExample -See [react-day-picker's "Custom modifiers" documentation](https://react-day-picker.js.org/basics/modifiers#custom-modifiers) +See [react-day-picker's "Custom modifiers" documentation](https://daypicker.dev/guides/custom-modifiers) for more info. @## Localization **DatePicker3**, **DateInput3**, **DateRangePicker3**, and **DateRangeInput3** support calendar -localization using date-fns's [Locale](https://date-fns.org/docs/Locale) features. The `locale` prop on each +localization using date-fns's [Locale](https://date-fns.org/v2.28.0/docs/Locale) features. The `locale` prop on each of these components accepts two types of values, either a `Locale` object or a locale code `string`. ### Using a locale code diff --git a/packages/datetime2/src/components/date-range-input3/date-range-input3.md b/packages/datetime2/src/components/date-range-input3/date-range-input3.md index 9ef2fb6c6b..cc18d6848d 100644 --- a/packages/datetime2/src/components/date-range-input3/date-range-input3.md +++ b/packages/datetime2/src/components/date-range-input3/date-range-input3.md @@ -19,7 +19,7 @@ on the wiki.
**DateRangeInput3** has the same functionality as [DateRangeInput](#datetime/date-range-input) but uses -[react-day-picker v8](https://react-day-picker.js.org/) instead of [v7](https://react-day-picker-v7.netlify.app/) +[react-day-picker v8](https://daypicker.dev/v8) instead of [v7](https://react-day-picker-v7.netlify.app/) to render its calendar(s). It renders a [**ControlGroup**](#core/components/control-group) composed of two [**InputGroups**](#core/components/input-group) and shows a [**DateRangePicker3**](#datetime2/date-range-picker3) inside a [**Popover**](#core/components/popover) upon focus. diff --git a/packages/datetime2/src/components/date-range-input3/dateRangeInput3Props.ts b/packages/datetime2/src/components/date-range-input3/dateRangeInput3Props.ts index 71fa7874fd..3b24432f4d 100644 --- a/packages/datetime2/src/components/date-range-input3/dateRangeInput3Props.ts +++ b/packages/datetime2/src/components/date-range-input3/dateRangeInput3Props.ts @@ -40,7 +40,7 @@ export interface DateRangeInput3Props * * Mutually exclusive with the `formatDate` and `parseDate` props. * - * @see https://date-fns.org/docs/format + * See date-fns [format](https://date-fns.org/docs/format). */ dateFnsFormat?: string; } diff --git a/packages/datetime2/src/components/react-day-picker/datePicker3Caption.tsx b/packages/datetime2/src/components/react-day-picker/datePicker3Caption.tsx index 4de7a64243..f3e6676d20 100644 --- a/packages/datetime2/src/components/react-day-picker/datePicker3Caption.tsx +++ b/packages/datetime2/src/components/react-day-picker/datePicker3Caption.tsx @@ -33,7 +33,7 @@ import { DatePicker3Context } from "../date-picker3/datePicker3Context"; * We need to override the whole caption instead of its lower-level components because react-day-picker * does not have built-in support for non-contiguous range pickers. * - * @see https://react-day-picker.js.org/guides/custom-components + * @see https://daypicker.dev/guides/custom-components */ export const DatePicker3Caption: React.FC = props => { const { classNames: rdpClassNames, formatters, fromDate, toDate, labels } = useDayPicker(); diff --git a/packages/datetime2/src/components/react-day-picker/datePicker3Dropdown.tsx b/packages/datetime2/src/components/react-day-picker/datePicker3Dropdown.tsx index aee721512a..3667173d76 100644 --- a/packages/datetime2/src/components/react-day-picker/datePicker3Dropdown.tsx +++ b/packages/datetime2/src/components/react-day-picker/datePicker3Dropdown.tsx @@ -25,7 +25,7 @@ import { useMonthSelectRightOffset } from "../../common/useMonthSelectRightOffse * Custom react-day-picker dropdown component which implements Blueprint's datepicker design * for month and year dropdowns. * - * @see https://react-day-picker.js.org/guides/custom-components + * @see https://daypicker.dev/guides/custom-components */ export function DatePicker3Dropdown({ caption, children, ...props }: DropdownProps) { const containerElement = React.useRef(null); diff --git a/packages/datetime2/src/index.md b/packages/datetime2/src/index.md index 89565e71a3..3f319e3c23 100644 --- a/packages/datetime2/src/index.md +++ b/packages/datetime2/src/index.md @@ -27,7 +27,7 @@ up for forward compatibility in the Blueprint ecosystem. Compared to their "V1" and "V2" counterparts, these components: -- use [react-day-picker](https://react-day-picker.js.org/) v8 instead of v7 (this unblocks React 18 compatibility) +- use [react-day-picker](https://daypicker.dev/v8) v8 instead of v7 (this unblocks React 18 compatibility) - are easier to internationalize & localize since date-fns is now a dependency (instead of `localeUtils`, you can specify a locale code and we'll automatically load the date-fns locale object) ### Installation From 825aad315f159686b32d05cccfbe8189ffd10f62 Mon Sep 17 00:00:00 2001 From: Sasi Date: Wed, 9 Oct 2024 11:02:44 -0400 Subject: [PATCH 009/121] Add fighter jet (#7014) --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/fighter-jet.svg | 3 +++ resources/icons/20px/fighter-jet.svg | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 resources/icons/16px/fighter-jet.svg create mode 100644 resources/icons/20px/fighter-jet.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index 59679ebca6..2ba917549c 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4499,5 +4499,12 @@ "tags": "shape, 3d, object, box, module, block, geometry, change, modify, update", "group": "miscellaneous", "codepoint": 62339 + }, + { + "displayName": "Fighter Jet", + "iconName": "fighter-jet", + "tags": "shape, 3d, object, vehicle, military, flight, jet, fly, aircraft, aerospace", + "group": "interface", + "codepoint": 62340 } ] diff --git a/resources/icons/16px/fighter-jet.svg b/resources/icons/16px/fighter-jet.svg new file mode 100644 index 0000000000..40238b52e4 --- /dev/null +++ b/resources/icons/16px/fighter-jet.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/20px/fighter-jet.svg b/resources/icons/20px/fighter-jet.svg new file mode 100644 index 0000000000..97c3dbbc5a --- /dev/null +++ b/resources/icons/20px/fighter-jet.svg @@ -0,0 +1,3 @@ + + + From d78625c3ec196479864703f5870cbc7f41d760ce Mon Sep 17 00:00:00 2001 From: paja1t <132699563+paja1t@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:44:15 -0700 Subject: [PATCH 010/121] feat: new icon - linked-squares (#7023) --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/linked-squares.svg | 4 ++++ resources/icons/20px/linked-squares.svg | 4 ++++ 3 files changed, 15 insertions(+) create mode 100644 resources/icons/16px/linked-squares.svg create mode 100644 resources/icons/20px/linked-squares.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index 2ba917549c..49365e4393 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4506,5 +4506,12 @@ "tags": "shape, 3d, object, vehicle, military, flight, jet, fly, aircraft, aerospace", "group": "interface", "codepoint": 62340 + }, + { + "displayName": "Linked squares", + "iconName": "linked-squares", + "tags": "app pairing, application, browser, cams, chain, connect, data, link, page, pages, platforms, sync, website, windows", + "group": "interface", + "codepoint": 62341 } ] diff --git a/resources/icons/16px/linked-squares.svg b/resources/icons/16px/linked-squares.svg new file mode 100644 index 0000000000..d3e691b91f --- /dev/null +++ b/resources/icons/16px/linked-squares.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/linked-squares.svg b/resources/icons/20px/linked-squares.svg new file mode 100644 index 0000000000..1f901b3258 --- /dev/null +++ b/resources/icons/20px/linked-squares.svg @@ -0,0 +1,4 @@ + + + + From ee26faf0ad8fe4bfd8ccbecf510d8ab385342003 Mon Sep 17 00:00:00 2001 From: Natsu Date: Wed, 23 Oct 2024 13:34:46 +0900 Subject: [PATCH 011/121] Add isComposing check to EditableText toggleEdit (#7021) Co-authored-by: Natsu Ozawa --- packages/core/src/components/editable-text/editableText.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/editable-text/editableText.tsx b/packages/core/src/components/editable-text/editableText.tsx index 56a82bd673..3d82f44a5a 100644 --- a/packages/core/src/components/editable-text/editableText.tsx +++ b/packages/core/src/components/editable-text/editableText.tsx @@ -357,7 +357,7 @@ export class EditableText extends AbstractPureComponent Date: Tue, 29 Oct 2024 14:24:01 +0000 Subject: [PATCH 012/121] add british pound icons (#7018) Co-authored-by: Gregory Douglas --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/british-pound.svg | 3 +++ resources/icons/20px/british-pound.svg | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 resources/icons/16px/british-pound.svg create mode 100644 resources/icons/20px/british-pound.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index 49365e4393..be73887cd1 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4513,5 +4513,12 @@ "tags": "app pairing, application, browser, cams, chain, connect, data, link, page, pages, platforms, sync, website, windows", "group": "interface", "codepoint": 62341 + }, + { + "displayName": "British Pound", + "iconName": "british-pound", + "tags": "currency, money", + "group": "miscellaneous", + "codepoint": 62342 } ] diff --git a/resources/icons/16px/british-pound.svg b/resources/icons/16px/british-pound.svg new file mode 100644 index 0000000000..c7ebf0db40 --- /dev/null +++ b/resources/icons/16px/british-pound.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/20px/british-pound.svg b/resources/icons/20px/british-pound.svg new file mode 100644 index 0000000000..b1c4b7de7b --- /dev/null +++ b/resources/icons/20px/british-pound.svg @@ -0,0 +1,3 @@ + + + From bee98b21eddae294c534f6a8c7d9f526e29ed3b3 Mon Sep 17 00:00:00 2001 From: Natsu Date: Wed, 30 Oct 2024 09:25:26 +0900 Subject: [PATCH 013/121] Do not toggle edit mode with escape key during IME composition (#7024) Co-authored-by: Natsu Ozawa --- .../core/src/components/editable-text/editableText.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/editable-text/editableText.tsx b/packages/core/src/components/editable-text/editableText.tsx index 3d82f44a5a..85f1f9a272 100644 --- a/packages/core/src/components/editable-text/editableText.tsx +++ b/packages/core/src/components/editable-text/editableText.tsx @@ -350,6 +350,11 @@ export class EditableText extends AbstractPureComponent) => { + // During IME composition, Enter and Escape has special meanings that we will not override + if (event.nativeEvent.isComposing) { + return; + } + const { altKey, ctrlKey, metaKey, shiftKey } = event; if (event.key === "Escape") { this.cancelEditing(); @@ -357,7 +362,7 @@ export class EditableText extends AbstractPureComponent Date: Wed, 30 Oct 2024 10:40:54 -0400 Subject: [PATCH 014/121] fix(KeyComboTag): Better rendering on non-Mac operating systems (#7025) Co-authored-by: svc-changelog --- .../core/changelog/@unreleased/pr-7025.v2.yml | 6 ++ .../core/src/components/hotkeys/_hotkeys.scss | 5 +- .../src/components/hotkeys/hotkeyParser.ts | 6 +- .../src/components/hotkeys/keyComboTag.tsx | 64 +++++++++++++------ .../core/test/hotkeys/keyComboTagTests.tsx | 17 ++++- .../changelog/@unreleased/pr-7025.v2.yml | 6 ++ .../core-examples/hotkeyTesterExample.tsx | 1 + 7 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 packages/core/changelog/@unreleased/pr-7025.v2.yml create mode 100644 packages/docs-app/changelog/@unreleased/pr-7025.v2.yml diff --git a/packages/core/changelog/@unreleased/pr-7025.v2.yml b/packages/core/changelog/@unreleased/pr-7025.v2.yml new file mode 100644 index 0000000000..6d12b686a8 --- /dev/null +++ b/packages/core/changelog/@unreleased/pr-7025.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: The KeyComboTag component no longer renders modifier key icons on non-Mac + operating systems + links: + - https://github.com/palantir/blueprint/pull/7025 diff --git a/packages/core/src/components/hotkeys/_hotkeys.scss b/packages/core/src/components/hotkeys/_hotkeys.scss index c031154216..f03e206796 100644 --- a/packages/core/src/components/hotkeys/_hotkeys.scss +++ b/packages/core/src/components/hotkeys/_hotkeys.scss @@ -4,7 +4,10 @@ @import "../../common/mixins"; .#{$ns}-key-combo { - @include pt-flex-container(row, $pt-grid-size * 0.5); + &:not(.#{$ns}-minimal) { + @include pt-flex-container(row, $pt-grid-size * 0.5); + } + align-items: center; } diff --git a/packages/core/src/components/hotkeys/hotkeyParser.ts b/packages/core/src/components/hotkeys/hotkeyParser.ts index 14f0c5dbfe..9a1bfd401b 100644 --- a/packages/core/src/components/hotkeys/hotkeyParser.ts +++ b/packages/core/src/components/hotkeys/hotkeyParser.ts @@ -50,7 +50,7 @@ export const CONFIG_ALIASES: KeyMap = { esc: "escape", escape: "escape", minus: "-", - mod: isMac() ? "meta" : "ctrl", + mod: isMac(undefined) ? "meta" : "ctrl", option: "alt", plus: "+", return: "enter", @@ -232,7 +232,7 @@ export const getKeyCombo = (e: KeyboardEvent): KeyCombo => { * Unlike the parseKeyCombo method, this method does NOT convert shifted * action keys. So `"@"` will NOT be converted to `["shift", "2"]`). */ -export const normalizeKeyCombo = (combo: string, platformOverride?: string): string[] => { +export const normalizeKeyCombo = (combo: string, platformOverride: string | undefined): string[] => { const keys = combo.replace(/\s/g, "").split("+"); return keys.map(key => { const keyName = CONFIG_ALIASES[key] != null ? CONFIG_ALIASES[key] : key; @@ -240,7 +240,7 @@ export const normalizeKeyCombo = (combo: string, platformOverride?: string): str }); }; -function isMac(platformOverride?: string) { +export function isMac(platformOverride: string | undefined) { // HACKHACK: see https://github.com/palantir/blueprint/issues/5174 // eslint-disable-next-line deprecation/deprecation const platform = platformOverride ?? (typeof navigator !== "undefined" ? navigator.platform : undefined); diff --git a/packages/core/src/components/hotkeys/keyComboTag.tsx b/packages/core/src/components/hotkeys/keyComboTag.tsx index b97e548314..e1a4ee8cd8 100644 --- a/packages/core/src/components/hotkeys/keyComboTag.tsx +++ b/packages/core/src/components/hotkeys/keyComboTag.tsx @@ -33,20 +33,20 @@ import { import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, type Props } from "../../common"; import { Icon } from "../icon/icon"; -import { normalizeKeyCombo } from "./hotkeyParser"; +import { isMac, normalizeKeyCombo } from "./hotkeyParser"; -const KEY_ICONS: Record = { - ArrowDown: { icon: , iconTitle: "Down key" }, - ArrowLeft: { icon: , iconTitle: "Left key" }, - ArrowRight: { icon: , iconTitle: "Right key" }, - ArrowUp: { icon: , iconTitle: "Up key" }, - alt: { icon: , iconTitle: "Alt/Option key" }, - cmd: { icon: , iconTitle: "Command key" }, - ctrl: { icon: , iconTitle: "Control key" }, +const KEY_ICONS: Record = { + alt: { icon: , iconTitle: "Alt/Option key", isMacOnly: true }, + arrowdown: { icon: , iconTitle: "Down key" }, + arrowleft: { icon: , iconTitle: "Left key" }, + arrowright: { icon: , iconTitle: "Right key" }, + arrowup: { icon: , iconTitle: "Up key" }, + cmd: { icon: , iconTitle: "Command key", isMacOnly: true }, + ctrl: { icon: , iconTitle: "Control key", isMacOnly: true }, delete: { icon: , iconTitle: "Delete key" }, enter: { icon: , iconTitle: "Enter key" }, - meta: { icon: , iconTitle: "Command key" }, - shift: { icon: , iconTitle: "Shift key" }, + meta: { icon: , iconTitle: "Command key", isMacOnly: true }, + shift: { icon: , iconTitle: "Shift key", isMacOnly: true }, }; /** Reverse table of some CONFIG_ALIASES fields, for display by KeyComboTag */ @@ -71,20 +71,30 @@ export interface KeyComboTagProps extends Props { minimal?: boolean; } -export class KeyComboTag extends AbstractPureComponent { +interface KeyComboTagInternalProps extends KeyComboTagProps { + /** Override the oeprating system rendering for internal testing purposes */ + platformOverride?: string; +} + +export class KeyComboTagInternal extends AbstractPureComponent { public static displayName = `${DISPLAYNAME_PREFIX}.KeyComboTag`; public render() { - const { className, combo, minimal } = this.props; - const keys = normalizeKeyCombo(combo) + const { className, combo, minimal, platformOverride } = this.props; + const normalizedKeys = normalizeKeyCombo(combo, platformOverride); + const keys = normalizedKeys .map(key => (key.length === 1 ? key.toUpperCase() : key)) - .map(minimal ? this.renderMinimalKey : this.renderKey); - return {keys}; + .map((key, index) => + minimal + ? this.renderMinimalKey(key, index, index === normalizedKeys.length - 1) + : this.renderKey(key, index), + ); + return {keys}; } private renderKey = (key: string, index: number) => { const keyString = DISPLAY_ALIASES[key] ?? key; - const icon = KEY_ICONS[key]; + const icon = this.getKeyIcon(key); const reactKey = `key-${index}`; return ( @@ -94,8 +104,22 @@ export class KeyComboTag extends AbstractPureComponent { ); }; - private renderMinimalKey = (key: string, index: number) => { - const icon = KEY_ICONS[key]; - return icon == null ? key : ; + private renderMinimalKey = (key: string, index: number, isLastKey: boolean) => { + const icon = this.getKeyIcon(key); + if (icon == null) { + return isLastKey ? key : {key} + ; + } + return ; }; + + private getKeyIcon(key: string) { + const { platformOverride } = this.props; + const icon = KEY_ICONS[key]; + if (icon?.isMacOnly && !isMac(platformOverride)) { + return undefined; + } + return icon; + } } + +export const KeyComboTag: React.ComponentType = KeyComboTagInternal; diff --git a/packages/core/test/hotkeys/keyComboTagTests.tsx b/packages/core/test/hotkeys/keyComboTagTests.tsx index 28cda3df17..fcef12c134 100644 --- a/packages/core/test/hotkeys/keyComboTagTests.tsx +++ b/packages/core/test/hotkeys/keyComboTagTests.tsx @@ -18,11 +18,24 @@ import { render, screen } from "@testing-library/react"; import { expect } from "chai"; import * as React from "react"; -import { KeyComboTag } from "../../src/components/hotkeys"; +import { KeyComboTagInternal } from "../../src/components/hotkeys/keyComboTag"; describe("KeyCombo", () => { it("renders key combo", () => { - render(); + render(); expect(screen.getByText("C")).not.to.be.undefined; }); + + it("should render minimal key combos on Mac using icons", () => { + render(); + expect(() => screen.getByText("cmd + C", { exact: false })).to.throw; + }); + + it("should render minimal key combos on non-Macs using text", () => { + render(); + const text = screen.getByText("ctrl + C", { exact: false }).innerText; + expect(text).to.contain("ctrl"); + expect(text).to.contain("+"); + expect(text).to.contain("C"); + }); }); diff --git a/packages/docs-app/changelog/@unreleased/pr-7025.v2.yml b/packages/docs-app/changelog/@unreleased/pr-7025.v2.yml new file mode 100644 index 0000000000..0235cfe800 --- /dev/null +++ b/packages/docs-app/changelog/@unreleased/pr-7025.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: The useHotkeys documentation now shows minimal and non-minimal states + for the KeyComboTag component. + links: + - https://github.com/palantir/blueprint/pull/7025 diff --git a/packages/docs-app/src/examples/core-examples/hotkeyTesterExample.tsx b/packages/docs-app/src/examples/core-examples/hotkeyTesterExample.tsx index 9871a55e0f..1d6d6542bc 100644 --- a/packages/docs-app/src/examples/core-examples/hotkeyTesterExample.tsx +++ b/packages/docs-app/src/examples/core-examples/hotkeyTesterExample.tsx @@ -51,6 +51,7 @@ export class HotkeyTesterExample extends React.PureComponent + {combo} ); From aa64811be56c3f4ef24df8c9032cc5a9fd95f155 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Wed, 30 Oct 2024 15:14:17 -0400 Subject: [PATCH 015/121] Fix style order lint error (#7035) --- packages/core/src/components/hotkeys/_hotkeys.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/hotkeys/_hotkeys.scss b/packages/core/src/components/hotkeys/_hotkeys.scss index f03e206796..9b0c4f3ea5 100644 --- a/packages/core/src/components/hotkeys/_hotkeys.scss +++ b/packages/core/src/components/hotkeys/_hotkeys.scss @@ -4,11 +4,11 @@ @import "../../common/mixins"; .#{$ns}-key-combo { + align-items: center; + &:not(.#{$ns}-minimal) { @include pt-flex-container(row, $pt-grid-size * 0.5); } - - align-items: center; } .#{$ns}-hotkey-dialog { From 051361b08429697e11283a502c4240ac34d0fad1 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Wed, 30 Oct 2024 16:35:45 -0400 Subject: [PATCH 016/121] [DateRangePicker] Allow date picker popover to be closed by esc key press (#7033) --- .../date-range-input/dateRangeInput.tsx | 8 ++++++++ .../test/components/dateRangeInputTests.tsx | 15 +++++++++++++++ .../date-range-input3/dateRangeInput3.tsx | 8 ++++++++ .../test/components/dateRangeInput3Tests.tsx | 15 +++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/packages/datetime/src/components/date-range-input/dateRangeInput.tsx b/packages/datetime/src/components/date-range-input/dateRangeInput.tsx index 0b8dd45ace..bbb771591d 100644 --- a/packages/datetime/src/components/date-range-input/dateRangeInput.tsx +++ b/packages/datetime/src/components/date-range-input/dateRangeInput.tsx @@ -630,10 +630,18 @@ export class DateRangeInput extends AbstractPureComponent) => { const isTabPressed = e.key === "Tab"; const isEnterPressed = e.key === "Enter"; + const isEscapeKeyPressed = e.key === "Escape"; const isShiftPressed = e.shiftKey; const { selectedStart, selectedEnd } = this.state; + if (isEscapeKeyPressed) { + this.startInputElement?.blur(); + this.endInputElement?.blur(); + this.setState({ isOpen: false, isStartInputFocused: false, isEndInputFocused: false }); + return; + } + // order of JS events is our enemy here. when tabbing between fields, // this handler will fire in the middle of a focus exchange when no // field is currently focused. we work around this by referring to the diff --git a/packages/datetime/test/components/dateRangeInputTests.tsx b/packages/datetime/test/components/dateRangeInputTests.tsx index deb4c72109..018d4aecab 100644 --- a/packages/datetime/test/components/dateRangeInputTests.tsx +++ b/packages/datetime/test/components/dateRangeInputTests.tsx @@ -602,6 +602,21 @@ describe("", () => { expect(root.state("isOpen"), "popover closed at end").to.be.false; }); + it("pressing Escape closes the popover", () => { + const { root } = wrap(); + root.setState({ isOpen: true }); + + const startInput = getStartInput(root); + startInput.simulate("focus"); + + expect(root.state("isOpen")).to.be.true; + + startInput.simulate("keydown", { key: "Escape" }); + + expect(root.state("isOpen")).to.be.false; + expect(isStartInputFocused(root)).to.be.false; + }); + it("Clicking a date invokes onChange with the new date range and updates the input fields", () => { const defaultValue = [START_DATE, null] as DateRange; diff --git a/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx b/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx index 58bd965c29..655112a80b 100644 --- a/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx +++ b/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx @@ -484,10 +484,18 @@ export class DateRangeInput3 extends DateFnsLocalizedComponent) => { const isTabPressed = e.key === "Tab"; const isEnterPressed = e.key === "Enter"; + const isEscapeKeyPressed = e.key === "Escape"; const isShiftPressed = e.shiftKey; const { selectedStart, selectedEnd } = this.state; + if (isEscapeKeyPressed) { + this.startInputElement?.blur(); + this.endInputElement?.blur(); + this.setState({ isOpen: false, isStartInputFocused: false, isEndInputFocused: false }); + return; + } + // order of JS events is our enemy here. when tabbing between fields, // this handler will fire in the middle of a focus exchange when no // field is currently focused. we work around this by referring to the diff --git a/packages/datetime2/test/components/dateRangeInput3Tests.tsx b/packages/datetime2/test/components/dateRangeInput3Tests.tsx index 81760e54e6..c28e1c03ef 100644 --- a/packages/datetime2/test/components/dateRangeInput3Tests.tsx +++ b/packages/datetime2/test/components/dateRangeInput3Tests.tsx @@ -2343,6 +2343,21 @@ describe("", () => { assertDateRangesEqual(onChange.args[1][0], [START_STR, null]); }); + it("pressing Escape closes the popover", () => { + const { root } = wrap(); + root.setState({ isOpen: true }); + + const startInput = getStartInput(root); + startInput.simulate("focus"); + + expect(root.state("isOpen")).to.be.true; + + startInput.simulate("keydown", { key: "Escape" }); + + expect(root.state("isOpen")).to.be.false; + expect(isStartInputFocused(root)).to.be.false; + }); + it("Clicking a date invokes onChange with the new date range and updates the input field text", () => { const onChange = sinon.spy(); const { root, getDayElement } = wrap( From 4d1297a857c3fa82787d5e927e12057641dd2f84 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Thu, 31 Oct 2024 14:10:51 -0400 Subject: [PATCH 017/121] Use generics for Select arrays (#7036) --- .../select-examples/multiSelectExample.tsx | 22 ++++---- .../select-examples/omnibarExample.tsx | 2 +- .../select-examples/selectExample.tsx | 10 ++-- .../select-examples/suggestExample.tsx | 6 +- .../select/src/__examples__/filmSelect.tsx | 8 +-- packages/select/src/__examples__/films.tsx | 22 ++++---- .../select/src/common/itemListRenderer.ts | 14 +++-- packages/select/src/common/listItemsProps.ts | 12 ++-- packages/select/src/common/predicate.ts | 2 +- .../components/multi-select/multiSelect.tsx | 29 +++++----- .../select/src/components/omnibar/omnibar.tsx | 8 +-- .../src/components/query-list/queryList.tsx | 55 ++++++++++++------- .../select/src/components/select/select.tsx | 16 +++--- .../select/src/components/suggest/suggest.tsx | 21 ++++--- packages/select/test/multiSelectTests.tsx | 12 ++-- packages/select/test/queryListTests.tsx | 4 +- packages/select/test/selectComponentSuite.tsx | 4 +- packages/select/test/selectTests.tsx | 6 +- packages/select/test/suggestTests.tsx | 6 +- 19 files changed, 143 insertions(+), 116 deletions(-) diff --git a/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx b/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx index ff5d0129c2..5006a5ac08 100644 --- a/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx +++ b/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx @@ -40,13 +40,13 @@ const INTENTS = [Intent.NONE, Intent.PRIMARY, Intent.SUCCESS, Intent.DANGER, Int export interface MultiSelectExampleState { allowCreate: boolean; - createdItems: Film[]; + createdItems: readonly Film[]; disabled: boolean; fill: boolean; - films: Film[]; + films: readonly Film[]; hasInitialContent: boolean; intent: boolean; - items: Film[]; + items: readonly Film[]; matchTargetWidth: boolean; openOnKeyDown: boolean; popoverMinimal: boolean; @@ -124,7 +124,7 @@ export class MultiSelectExample extends React.PureComponent - + ; + private renderCustomTarget = (selectedItems: readonly Film[]) => ( + + ); private renderTag = (film: Film) => film.title; @@ -287,11 +289,11 @@ export class MultiSelectExample extends React.PureComponent { - let nextCreatedItems = createdItems.slice(); - let nextFilms = films.slice(); - let nextItems = items.slice(); + let nextCreatedItems = createdItems; + let nextFilms = films; + let nextItems = items; filmsToSelect.forEach(film => { const results = maybeAddCreatedFilmToArrays(nextItems, nextCreatedItems, film); @@ -336,7 +338,7 @@ export class MultiSelectExample extends React.PureComponent { + private handleFilmsPaste = (films: readonly Film[]) => { // On paste, don't bother with deselecting already selected values, just // add the new ones. this.selectFilms(films); diff --git a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx index 19d4596ef0..b30c197123 100644 --- a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx +++ b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx @@ -92,7 +92,7 @@ export class OmnibarExample extends React.PureComponent - + { + private getGroupedItems = (filteredItems: readonly Film[]) => { return filteredItems.reduce>( (acc, item, index) => { const group = this.getGroup(item); @@ -193,7 +193,7 @@ export class SelectExample extends React.PureComponent { + private groupedItemListPredicate = (query: string, items: readonly Film[]) => { return items .filter((item, index) => filterFilm(query, item, index)) .sort((a, b) => this.getGroup(a).localeCompare(this.getGroup(b))); @@ -208,7 +208,7 @@ export class SelectExample extends React.PureComponent this.state.disableItems && film.year < 2000; - private renderGroupedItemList = (listProps: ItemListRendererProps) => { + private renderGroupedItemList = (listProps: ItemListRendererProps) => { const initialContent = this.getInitialContent(); const noResults = ; @@ -231,7 +231,7 @@ export class SelectExample extends React.PureComponent, + listProps: ItemListRendererProps, noResults?: React.ReactNode, initialContent?: React.ReactNode | null, ) => { diff --git a/packages/docs-app/src/examples/select-examples/suggestExample.tsx b/packages/docs-app/src/examples/select-examples/suggestExample.tsx index 835ae1411a..4e6d3941ad 100644 --- a/packages/docs-app/src/examples/select-examples/suggestExample.tsx +++ b/packages/docs-app/src/examples/select-examples/suggestExample.tsx @@ -34,10 +34,10 @@ import { export interface SuggestExampleState { allowCreate: boolean; closeOnSelect: boolean; - createdItems: Film[]; + createdItems: readonly Film[]; disabled: boolean; fill: boolean; - items: Film[]; + items: readonly Film[]; matchTargetWidth: boolean; minimal: boolean; openOnKeyDown: boolean; @@ -92,7 +92,7 @@ export class SuggestExample extends React.PureComponent - + , + SelectProps, | "createNewItemFromQuery" | "createNewItemRenderer" | "itemPredicate" @@ -49,8 +49,8 @@ type FilmSelectProps = Omit< }; export function FilmSelect({ allowCreate = false, fill, ...restProps }: FilmSelectProps) { - const [items, setItems] = React.useState([...TOP_100_FILMS]); - const [createdItems, setCreatedItems] = React.useState([]); + const [items, setItems] = React.useState([...TOP_100_FILMS]); + const [createdItems, setCreatedItems] = React.useState([]); const [selectedFilm, setSelectedFilm] = React.useState(undefined); const handleItemSelect = React.useCallback( (newFilm: Film) => { @@ -82,7 +82,7 @@ export function FilmSelect({ allowCreate = false, fill, ...restProps }: FilmSele ); return ( - + ", () => { testsContainerElement?.remove(); }); - selectComponentSuite, SelectState>(props => + selectComponentSuite, SelectState>(props => mount(", () => { assert.isTrue(wrapper.find(Popover).prop("isOpen")); }); - function select(props: Partial> = {}, query?: string) { + function select(props: Partial> = {}, query?: string) { const wrapper = mount( - {...defaultProps} {...handlers} {...props}> + , { attachTo: testsContainerElement }, diff --git a/packages/select/test/suggestTests.tsx b/packages/select/test/suggestTests.tsx index 2002c8dc29..64d58e3c23 100644 --- a/packages/select/test/suggestTests.tsx +++ b/packages/select/test/suggestTests.tsx @@ -57,7 +57,7 @@ describe("Suggest", () => { testsContainerElement?.remove(); }); - selectComponentSuite, SuggestState>(props => + selectComponentSuite, SuggestState>(props => mount( { }); }); - function suggest(props: Partial> = {}) { - return mount>( {...defaultProps} {...handlers} {...props} />, { + function suggest(props: Partial> = {}) { + return mount>(, { attachTo: testsContainerElement, }); } From 0dde82dc288d5b716b2428f348d49cc2c999904b Mon Sep 17 00:00:00 2001 From: Ryan Nono Date: Mon, 4 Nov 2024 14:48:48 -0500 Subject: [PATCH 018/121] Add arrows-arc (#7038) Co-authored-by: Gregory Douglas --- packages/icons/icons.json | 7 +++++++ resources/icons/16px/arrows-arc.svg | 3 +++ resources/icons/20px/arrows-arc.svg | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 resources/icons/16px/arrows-arc.svg create mode 100644 resources/icons/20px/arrows-arc.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index be73887cd1..26466e7bb2 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4520,5 +4520,12 @@ "tags": "currency, money", "group": "miscellaneous", "codepoint": 62342 + }, + { + "displayName": "Arrows: arc", + "iconName": "arrows-arc", + "tags": "rotate cursor, curved, arrows", + "group": "interface", + "codepoint": 62343 } ] diff --git a/resources/icons/16px/arrows-arc.svg b/resources/icons/16px/arrows-arc.svg new file mode 100644 index 0000000000..4602809fe6 --- /dev/null +++ b/resources/icons/16px/arrows-arc.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/20px/arrows-arc.svg b/resources/icons/20px/arrows-arc.svg new file mode 100644 index 0000000000..de25323940 --- /dev/null +++ b/resources/icons/20px/arrows-arc.svg @@ -0,0 +1,3 @@ + + + From 0e51e6cb592a14963d4e3c232d5440bbdf0567f1 Mon Sep 17 00:00:00 2001 From: Ryan Nono Date: Mon, 4 Nov 2024 17:00:57 -0500 Subject: [PATCH 019/121] Add rotation icons (#7039) Co-authored-by: Gregory Douglas --- packages/icons/icons.json | 14 ++++++++++++++ resources/icons/16px/rotate-ccw.svg | 4 ++++ resources/icons/16px/rotate-cw.svg | 4 ++++ resources/icons/20px/rotate-ccw.svg | 4 ++++ resources/icons/20px/rotate-cw.svg | 4 ++++ 5 files changed, 30 insertions(+) create mode 100644 resources/icons/16px/rotate-ccw.svg create mode 100644 resources/icons/16px/rotate-cw.svg create mode 100644 resources/icons/20px/rotate-ccw.svg create mode 100644 resources/icons/20px/rotate-cw.svg diff --git a/packages/icons/icons.json b/packages/icons/icons.json index 26466e7bb2..bd3f12eef0 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4527,5 +4527,19 @@ "tags": "rotate cursor, curved, arrows", "group": "interface", "codepoint": 62343 + }, + { + "displayName": "Rotate clockwise", + "iconName": "rotate-cw", + "tags": "arrow, box, object, clockwise, cw, right", + "group": "interface", + "codepoint": 62344 + }, + { + "displayName": "Rotate counterclockwise", + "iconName": "rotate-ccw", + "tags": "arrow, box, object, counter-clockwise, ccw, left", + "group": "interface", + "codepoint": 62345 } ] diff --git a/resources/icons/16px/rotate-ccw.svg b/resources/icons/16px/rotate-ccw.svg new file mode 100644 index 0000000000..36ad3dab29 --- /dev/null +++ b/resources/icons/16px/rotate-ccw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/16px/rotate-cw.svg b/resources/icons/16px/rotate-cw.svg new file mode 100644 index 0000000000..2c098f2d67 --- /dev/null +++ b/resources/icons/16px/rotate-cw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/rotate-ccw.svg b/resources/icons/20px/rotate-ccw.svg new file mode 100644 index 0000000000..598251d4ce --- /dev/null +++ b/resources/icons/20px/rotate-ccw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/rotate-cw.svg b/resources/icons/20px/rotate-cw.svg new file mode 100644 index 0000000000..4d03373429 --- /dev/null +++ b/resources/icons/20px/rotate-cw.svg @@ -0,0 +1,4 @@ + + + + From e40e976de5ff9902c54d13db54a3e16eea3835ad Mon Sep 17 00:00:00 2001 From: AaronJRubin Date: Wed, 6 Nov 2024 00:00:58 +0900 Subject: [PATCH 020/121] Fix IME bug in QueryList (#7028) --- .../changelog/@unreleased/pr-7028.v2.yml | 5 +++++ .../src/components/query-list/queryList.tsx | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 packages/select/changelog/@unreleased/pr-7028.v2.yml diff --git a/packages/select/changelog/@unreleased/pr-7028.v2.yml b/packages/select/changelog/@unreleased/pr-7028.v2.yml new file mode 100644 index 0000000000..13b1242c3c --- /dev/null +++ b/packages/select/changelog/@unreleased/pr-7028.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: Fix buggy behavior with IME input in QueryList component + links: + - https://github.com/palantir/blueprint/pull/7028 diff --git a/packages/select/src/components/query-list/queryList.tsx b/packages/select/src/components/query-list/queryList.tsx index 20b0974f60..168a4a3a42 100644 --- a/packages/select/src/components/query-list/queryList.tsx +++ b/packages/select/src/components/query-list/queryList.tsx @@ -522,16 +522,18 @@ export class QueryList extends AbstractComponen }; private handleKeyDown = (event: React.KeyboardEvent) => { - const { key } = event; - const direction = Utils.getArrowKeyDirection(event, ["ArrowUp"], ["ArrowDown"]); - if (direction !== undefined) { - event.preventDefault(); - const nextActiveItem = this.getNextActiveItem(direction); - if (nextActiveItem != null) { - this.setActiveItem(nextActiveItem); + if (!event.nativeEvent.isComposing) { + const { key } = event; + const direction = Utils.getArrowKeyDirection(event, ["ArrowUp"], ["ArrowDown"]); + if (direction !== undefined) { + event.preventDefault(); + const nextActiveItem = this.getNextActiveItem(direction); + if (nextActiveItem != null) { + this.setActiveItem(nextActiveItem); + } + } else if (key === "Enter") { + this.isEnterKeyPressed = true; } - } else if (key === "Enter") { - this.isEnterKeyPressed = true; } this.props.onKeyDown?.(event); From 8dd6c3a002e71d35e2be246128888711efccd311 Mon Sep 17 00:00:00 2001 From: Evan Johnson Date: Tue, 5 Nov 2024 10:12:16 -0500 Subject: [PATCH 021/121] Document text-muted and text-disabled classes wrt minimum contrast (#7045) --- packages/core/src/_typography.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/_typography.scss b/packages/core/src/_typography.scss index 99a15c0074..3201bea163 100644 --- a/packages/core/src/_typography.scss +++ b/packages/core/src/_typography.scss @@ -69,8 +69,8 @@ Markup: .#{$ns}-running-text - Increase line height ideal for longform text. See [Running text](#core/typography.running-text) below for additional features. .#{$ns}-text-large - Use a larger font size. .#{$ns}-text-small - Use a smaller font size. -.#{$ns}-text-muted - Change text color to a gentler gray. -.#{$ns}-text-disabled - Change text color to a transparent, faded gray. +.#{$ns}-text-muted - Change text color to a gentler gray. This text color meets [minimum contrast standards of WCAG 2.1](https://www.w3.org/TR/WCAG21/#contrast-minimum) for $white through $light-gray4 in light theme, and $black through $dark-gray4 in dark theme. +.#{$ns}-text-disabled - Change text color to a transparent, faded gray. This text color will not meet minimum contrast standards and should only be used on "incidental" text as defined by [WCAG 2.1 Contrast Minimum](https://www.w3.org/TR/WCAG21/#contrast-minimum), either purely decorative, or text of a disabled UI element. .#{$ns}-text-overflow-ellipsis - Truncate a single line of text with an ellipsis if it overflows its container. Styleguide ui-text From 265df1a5c9bf037281e67cf2a430c47fe851d90d Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Tue, 5 Nov 2024 11:01:03 -0500 Subject: [PATCH 022/121] chore: Publish new release - @blueprintjs/colors@5.1.3 - @blueprintjs/core@5.14.0 - @blueprintjs/datetime@5.3.12 - @blueprintjs/datetime2@2.3.12 - @blueprintjs/demo-app@5.14.0 - @blueprintjs/docs-app@5.14.0 - @blueprintjs/docs-data@5.14.0 - @blueprintjs/docs-theme@5.3.12 - @blueprintjs/icons@5.14.0 - @blueprintjs/landing-app@5.14.0 - @blueprintjs/monaco-editor-theme@1.0.14 - @blueprintjs/popover2@2.1.12 - @blueprintjs/select@5.3.0 - @blueprintjs/stylelint-plugin@4.1.12 - @blueprintjs/table-dev-app@5.2.3 - @blueprintjs/table@5.2.3 --- packages/colors/package.json | 2 +- packages/core/package.json | 2 +- packages/datetime/package.json | 2 +- packages/datetime2/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/docs-app/package.json | 2 +- packages/docs-data/package.json | 2 +- packages/docs-theme/package.json | 2 +- packages/icons/package.json | 2 +- packages/landing-app/package.json | 2 +- packages/monaco-editor-theme/package.json | 2 +- packages/popover2/package.json | 2 +- packages/select/package.json | 2 +- packages/stylelint-plugin/package.json | 2 +- packages/table-dev-app/package.json | 2 +- packages/table/package.json | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/colors/package.json b/packages/colors/package.json index bf4d74343c..81d78ef985 100644 --- a/packages/colors/package.json +++ b/packages/colors/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/colors", - "version": "5.1.2", + "version": "5.1.3", "description": "Blueprint color definitions", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index f393e0064c..101152a517 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/core", - "version": "5.13.1", + "version": "5.14.0", "description": "Core styles & components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime/package.json b/packages/datetime/package.json index 37116b9ab2..3f2481f1c0 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime", - "version": "5.3.11", + "version": "5.3.12", "description": "Components for interacting with dates and times", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime2/package.json b/packages/datetime2/package.json index 627e8c85c1..5ce09789a1 100644 --- a/packages/datetime2/package.json +++ b/packages/datetime2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime2", - "version": "2.3.11", + "version": "2.3.12", "description": "Re-exports of @blueprintjs/datetime APIs", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index ecd245bc7f..a1dfcb3f01 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/demo-app", - "version": "5.13.1", + "version": "5.14.0", "description": "Blueprint Demo App", "private": true, "scripts": { diff --git a/packages/docs-app/package.json b/packages/docs-app/package.json index 5aa934507b..377b779f51 100644 --- a/packages/docs-app/package.json +++ b/packages/docs-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-app", - "version": "5.13.1", + "version": "5.14.0", "description": "Blueprint Documentation Site", "private": true, "scripts": { diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index ef9225cabd..a1518243df 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-data", - "version": "5.13.1", + "version": "5.14.0", "main": "src/index.js", "types": "src/index.d.ts", "private": true, diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index befa90c567..1b63cdf236 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-theme", - "version": "5.3.11", + "version": "5.3.12", "description": "Blueprint theme for documentalist", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/icons/package.json b/packages/icons/package.json index 01ca5a9eee..2e64211531 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/icons", - "version": "5.13.0", + "version": "5.14.0", "description": "Components, fonts, icons, and css files for creating and displaying icons.", "main": "lib/cjs/generated/index.js", "module": "lib/esm/generated/index.js", diff --git a/packages/landing-app/package.json b/packages/landing-app/package.json index b74009601c..23fbdc267b 100644 --- a/packages/landing-app/package.json +++ b/packages/landing-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/landing-app", - "version": "5.13.1", + "version": "5.14.0", "description": "Blueprint landing page", "private": true, "scripts": { diff --git a/packages/monaco-editor-theme/package.json b/packages/monaco-editor-theme/package.json index 3fe099ee8f..94b1e36273 100644 --- a/packages/monaco-editor-theme/package.json +++ b/packages/monaco-editor-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/monaco-editor-theme", - "version": "1.0.13", + "version": "1.0.14", "description": "Blueprint theme for monaco-editor", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/popover2/package.json b/packages/popover2/package.json index 3c4c93925d..629fc9c596 100644 --- a/packages/popover2/package.json +++ b/packages/popover2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/popover2", - "version": "2.1.11", + "version": "2.1.12", "description": "Re-exports of popover-related components from @blueprintjs/core", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/select/package.json b/packages/select/package.json index 7c6eb8b148..0d4052f917 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/select", - "version": "5.2.5", + "version": "5.3.0", "description": "Components related to selecting items from a list", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/stylelint-plugin/package.json b/packages/stylelint-plugin/package.json index 2ede069873..5e041a71e9 100644 --- a/packages/stylelint-plugin/package.json +++ b/packages/stylelint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/stylelint-plugin", - "version": "4.1.11", + "version": "4.1.12", "description": "Stylelint rules for use with @blueprintjs packages", "main": "lib/index.js", "scripts": { diff --git a/packages/table-dev-app/package.json b/packages/table-dev-app/package.json index f51ae6f6e1..f1978f4c23 100644 --- a/packages/table-dev-app/package.json +++ b/packages/table-dev-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table-dev-app", - "version": "5.2.2", + "version": "5.2.3", "description": "Dev application for @blueprintjs/table", "private": true, "scripts": { diff --git a/packages/table/package.json b/packages/table/package.json index 2a5adb6e5d..b013f405a8 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table", - "version": "5.2.2", + "version": "5.2.3", "description": "Scalable interactive table component", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From 5de0a287ea08a3f8671977261b5626ce8563c36d Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Tue, 5 Nov 2024 17:18:10 -0500 Subject: [PATCH 023/121] Revert "Use generics for Select arrays (#7036)" This reverts commit 4d1297a857c3fa82787d5e927e12057641dd2f84. --- .../select-examples/multiSelectExample.tsx | 22 ++++---- .../select-examples/omnibarExample.tsx | 2 +- .../select-examples/selectExample.tsx | 10 ++-- .../select-examples/suggestExample.tsx | 6 +- .../select/src/__examples__/filmSelect.tsx | 8 +-- packages/select/src/__examples__/films.tsx | 22 ++++---- .../select/src/common/itemListRenderer.ts | 14 ++--- packages/select/src/common/listItemsProps.ts | 12 ++-- packages/select/src/common/predicate.ts | 2 +- .../components/multi-select/multiSelect.tsx | 29 +++++----- .../select/src/components/omnibar/omnibar.tsx | 8 +-- .../src/components/query-list/queryList.tsx | 55 +++++++------------ .../select/src/components/select/select.tsx | 16 +++--- .../select/src/components/suggest/suggest.tsx | 21 +++---- packages/select/test/multiSelectTests.tsx | 12 ++-- packages/select/test/queryListTests.tsx | 4 +- packages/select/test/selectComponentSuite.tsx | 4 +- packages/select/test/selectTests.tsx | 6 +- packages/select/test/suggestTests.tsx | 6 +- 19 files changed, 116 insertions(+), 143 deletions(-) diff --git a/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx b/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx index 5006a5ac08..ff5d0129c2 100644 --- a/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx +++ b/packages/docs-app/src/examples/select-examples/multiSelectExample.tsx @@ -40,13 +40,13 @@ const INTENTS = [Intent.NONE, Intent.PRIMARY, Intent.SUCCESS, Intent.DANGER, Int export interface MultiSelectExampleState { allowCreate: boolean; - createdItems: readonly Film[]; + createdItems: Film[]; disabled: boolean; fill: boolean; - films: readonly Film[]; + films: Film[]; hasInitialContent: boolean; intent: boolean; - items: readonly Film[]; + items: Film[]; matchTargetWidth: boolean; openOnKeyDown: boolean; popoverMinimal: boolean; @@ -124,7 +124,7 @@ export class MultiSelectExample extends React.PureComponent - {...flags} createNewItemFromQuery={allowCreate ? createFilms : undefined} createNewItemRenderer={allowCreate ? renderCreateFilmsMenuItem : null} @@ -251,9 +251,7 @@ export class MultiSelectExample extends React.PureComponent ( - - ); + private renderCustomTarget = (selectedItems: Film[]) => ; private renderTag = (film: Film) => film.title; @@ -289,11 +287,11 @@ export class MultiSelectExample extends React.PureComponent { - let nextCreatedItems = createdItems; - let nextFilms = films; - let nextItems = items; + let nextCreatedItems = createdItems.slice(); + let nextFilms = films.slice(); + let nextItems = items.slice(); filmsToSelect.forEach(film => { const results = maybeAddCreatedFilmToArrays(nextItems, nextCreatedItems, film); @@ -338,7 +336,7 @@ export class MultiSelectExample extends React.PureComponent { + private handleFilmsPaste = (films: Film[]) => { // On paste, don't bother with deselecting already selected values, just // add the new ones. this.selectFilms(films); diff --git a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx index b30c197123..19d4596ef0 100644 --- a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx +++ b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx @@ -92,7 +92,7 @@ export class OmnibarExample extends React.PureComponent - {...this.state} createNewItemFromQuery={maybeCreateNewItemFromQuery} createNewItemRenderer={maybeCreateNewItemRenderer} diff --git a/packages/docs-app/src/examples/select-examples/selectExample.tsx b/packages/docs-app/src/examples/select-examples/selectExample.tsx index 2b0a74b313..18759c67f5 100644 --- a/packages/docs-app/src/examples/select-examples/selectExample.tsx +++ b/packages/docs-app/src/examples/select-examples/selectExample.tsx @@ -24,7 +24,7 @@ import { type Film, FilmSelect, filterFilm, TOP_100_FILMS } from "@blueprintjs/s export interface SelectExampleState { allowCreate: boolean; createFirst: boolean; - createdItems: readonly Film[]; + createdItems: Film[]; disableItems: boolean; disabled: boolean; fill: boolean; @@ -169,7 +169,7 @@ export class SelectExample extends React.PureComponent { + private getGroupedItems = (filteredItems: Film[]) => { return filteredItems.reduce>( (acc, item, index) => { const group = this.getGroup(item); @@ -193,7 +193,7 @@ export class SelectExample extends React.PureComponent { + private groupedItemListPredicate = (query: string, items: Film[]) => { return items .filter((item, index) => filterFilm(query, item, index)) .sort((a, b) => this.getGroup(a).localeCompare(this.getGroup(b))); @@ -208,7 +208,7 @@ export class SelectExample extends React.PureComponent this.state.disableItems && film.year < 2000; - private renderGroupedItemList = (listProps: ItemListRendererProps) => { + private renderGroupedItemList = (listProps: ItemListRendererProps) => { const initialContent = this.getInitialContent(); const noResults = ; @@ -231,7 +231,7 @@ export class SelectExample extends React.PureComponent, + listProps: ItemListRendererProps, noResults?: React.ReactNode, initialContent?: React.ReactNode | null, ) => { diff --git a/packages/docs-app/src/examples/select-examples/suggestExample.tsx b/packages/docs-app/src/examples/select-examples/suggestExample.tsx index 4e6d3941ad..835ae1411a 100644 --- a/packages/docs-app/src/examples/select-examples/suggestExample.tsx +++ b/packages/docs-app/src/examples/select-examples/suggestExample.tsx @@ -34,10 +34,10 @@ import { export interface SuggestExampleState { allowCreate: boolean; closeOnSelect: boolean; - createdItems: readonly Film[]; + createdItems: Film[]; disabled: boolean; fill: boolean; - items: readonly Film[]; + items: Film[]; matchTargetWidth: boolean; minimal: boolean; openOnKeyDown: boolean; @@ -92,7 +92,7 @@ export class SuggestExample extends React.PureComponent - {...flags} createNewItemFromQuery={maybeCreateNewItemFromQuery} createNewItemRenderer={maybeCreateNewItemRenderer} diff --git a/packages/select/src/__examples__/filmSelect.tsx b/packages/select/src/__examples__/filmSelect.tsx index 92b9708a7c..c3735a37ab 100644 --- a/packages/select/src/__examples__/filmSelect.tsx +++ b/packages/select/src/__examples__/filmSelect.tsx @@ -35,7 +35,7 @@ import { } from "./films"; type FilmSelectProps = Omit< - SelectProps, + SelectProps, | "createNewItemFromQuery" | "createNewItemRenderer" | "itemPredicate" @@ -49,8 +49,8 @@ type FilmSelectProps = Omit< }; export function FilmSelect({ allowCreate = false, fill, ...restProps }: FilmSelectProps) { - const [items, setItems] = React.useState([...TOP_100_FILMS]); - const [createdItems, setCreatedItems] = React.useState([]); + const [items, setItems] = React.useState([...TOP_100_FILMS]); + const [createdItems, setCreatedItems] = React.useState([]); const [selectedFilm, setSelectedFilm] = React.useState(undefined); const handleItemSelect = React.useCallback( (newFilm: Film) => { @@ -82,7 +82,7 @@ export function FilmSelect({ allowCreate = false, fill, ...restProps }: FilmSele ); return ( - ", () => { testsContainerElement?.remove(); }); - selectComponentSuite, SelectState>(props => + selectComponentSuite, SelectState>(props => mount(", () => { assert.isTrue(wrapper.find(Popover).prop("isOpen")); }); - function select(props: Partial> = {}, query?: string) { + function select(props: Partial> = {}, query?: string) { const wrapper = mount( - , { attachTo: testsContainerElement }, diff --git a/packages/select/test/suggestTests.tsx b/packages/select/test/suggestTests.tsx index 64d58e3c23..2002c8dc29 100644 --- a/packages/select/test/suggestTests.tsx +++ b/packages/select/test/suggestTests.tsx @@ -57,7 +57,7 @@ describe("Suggest", () => { testsContainerElement?.remove(); }); - selectComponentSuite, SuggestState>(props => + selectComponentSuite, SuggestState>(props => mount( { }); }); - function suggest(props: Partial> = {}) { - return mount>(, { + function suggest(props: Partial> = {}) { + return mount>( {...defaultProps} {...handlers} {...props} />, { attachTo: testsContainerElement, }); } From ae69e8db3b5894c470b6e04a458cf235a9c04667 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Wed, 6 Nov 2024 08:57:35 -0500 Subject: [PATCH 024/121] chore: Publish new release - @blueprintjs/datetime@5.3.13 - @blueprintjs/datetime2@2.3.13 - @blueprintjs/demo-app@5.14.1 - @blueprintjs/docs-app@5.14.1 - @blueprintjs/docs-data@5.14.1 - @blueprintjs/docs-theme@5.3.13 - @blueprintjs/select@5.3.1 --- packages/datetime/package.json | 2 +- packages/datetime2/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/docs-app/package.json | 2 +- packages/docs-data/package.json | 2 +- packages/docs-theme/package.json | 2 +- packages/select/package.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/datetime/package.json b/packages/datetime/package.json index 3f2481f1c0..08513aa635 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime", - "version": "5.3.12", + "version": "5.3.13", "description": "Components for interacting with dates and times", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime2/package.json b/packages/datetime2/package.json index 5ce09789a1..34f062da0c 100644 --- a/packages/datetime2/package.json +++ b/packages/datetime2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime2", - "version": "2.3.12", + "version": "2.3.13", "description": "Re-exports of @blueprintjs/datetime APIs", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index a1dfcb3f01..583158defb 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/demo-app", - "version": "5.14.0", + "version": "5.14.1", "description": "Blueprint Demo App", "private": true, "scripts": { diff --git a/packages/docs-app/package.json b/packages/docs-app/package.json index 377b779f51..1f759d0912 100644 --- a/packages/docs-app/package.json +++ b/packages/docs-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-app", - "version": "5.14.0", + "version": "5.14.1", "description": "Blueprint Documentation Site", "private": true, "scripts": { diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index a1518243df..2062eefb38 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-data", - "version": "5.14.0", + "version": "5.14.1", "main": "src/index.js", "types": "src/index.d.ts", "private": true, diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index 1b63cdf236..b81ce718d2 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-theme", - "version": "5.3.12", + "version": "5.3.13", "description": "Blueprint theme for documentalist", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/select/package.json b/packages/select/package.json index 0d4052f917..94f1d0a223 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/select", - "version": "5.3.0", + "version": "5.3.1", "description": "Components related to selecting items from a list", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From bf2161e94b1f9da2bb5ac8d343e0f21af2a50b80 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 7 Nov 2024 13:10:19 -0500 Subject: [PATCH 025/121] fix(OverlayToaster): Don't dismiss excess toasts when updating a toast while maxToasts is set (#7048) Co-authored-by: svc-changelog --- .../core/changelog/@unreleased/pr-7048.v2.yml | 6 ++++ .../src/components/toast/overlayToaster.tsx | 30 ++++++++++++++----- .../core/test/toast/overlayToasterTests.tsx | 8 +++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 packages/core/changelog/@unreleased/pr-7048.v2.yml diff --git a/packages/core/changelog/@unreleased/pr-7048.v2.yml b/packages/core/changelog/@unreleased/pr-7048.v2.yml new file mode 100644 index 0000000000..ea615faded --- /dev/null +++ b/packages/core/changelog/@unreleased/pr-7048.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: 'fix(OverlayToaster): Don''t dismiss excess toasts when updating a + toast while maxToasts is set' + links: + - https://github.com/palantir/blueprint/pull/7048 diff --git a/packages/core/src/components/toast/overlayToaster.tsx b/packages/core/src/components/toast/overlayToaster.tsx index 508d368b06..d03644379e 100644 --- a/packages/core/src/components/toast/overlayToaster.tsx +++ b/packages/core/src/components/toast/overlayToaster.tsx @@ -146,21 +146,35 @@ export class OverlayToaster extends AbstractPureComponent [options, ...toasts]); + return options.key; + } + + private maybeUpdateExistingToast(options: ToastOptions, key: string | undefined) { + if (key == null || this.isNewToastKey(key)) { + return false; + } + + this.updateToastsInState(toasts => toasts.map(t => (t.key === key ? options : t))); + return true; + } + + private updateToastsInState(getNewToasts: (toasts: ToastOptions[]) => ToastOptions[]) { this.setState(prevState => { - const toasts = - key === undefined || this.isNewToastKey(key) - ? // prepend a new toast - [options, ...prevState.toasts] - : // update a specific toast - prevState.toasts.map(t => (t.key === key ? options : t)); + const toasts = getNewToasts(prevState.toasts); return { toasts, toastRefs: this.getToastRefs(toasts) }; }); - return options.key; } public dismiss(key: string, timeoutExpired = false) { diff --git a/packages/core/test/toast/overlayToasterTests.tsx b/packages/core/test/toast/overlayToasterTests.tsx index 843045f29f..21ac88e3f5 100644 --- a/packages/core/test/toast/overlayToasterTests.tsx +++ b/packages/core/test/toast/overlayToasterTests.tsx @@ -221,6 +221,14 @@ describe("OverlayToaster", () => { toaster.show({ message: "oh no" }); assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); }); + + it("does not dismiss toasts when updating an existing toast at the limit", () => { + toaster.show({ message: "one" }); + toaster.show({ message: "two" }); + toaster.show({ message: "three" }, "3"); + toaster.show({ message: "three updated" }, "3"); + assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); + }); }); describe("with autoFocus set to true", () => { From 08c037643e40be4929c28a2ee1f3edea7dc3f97c Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 7 Nov 2024 17:40:08 -0500 Subject: [PATCH 026/121] fix(OverlayToaster): Fix toasts being cut off if show() called too quickly (#7049) --- .../core/changelog/@unreleased/pr-7049.v2.yml | 6 ++ .../src/components/toast/overlayToaster.tsx | 75 ++++++++++++++++--- .../core/test/toast/overlayToasterTests.tsx | 54 ++++++++++++- 3 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 packages/core/changelog/@unreleased/pr-7049.v2.yml diff --git a/packages/core/changelog/@unreleased/pr-7049.v2.yml b/packages/core/changelog/@unreleased/pr-7049.v2.yml new file mode 100644 index 0000000000..4dbeba1795 --- /dev/null +++ b/packages/core/changelog/@unreleased/pr-7049.v2.yml @@ -0,0 +1,6 @@ +type: fix +fix: + description: 'fix(OverlayToaster): Fix toasts being cut off if show() called too + quickly' + links: + - https://github.com/palantir/blueprint/pull/7049 diff --git a/packages/core/src/components/toast/overlayToaster.tsx b/packages/core/src/components/toast/overlayToaster.tsx index d03644379e..e5f293f810 100644 --- a/packages/core/src/components/toast/overlayToaster.tsx +++ b/packages/core/src/components/toast/overlayToaster.tsx @@ -43,6 +43,14 @@ export interface OverlayToasterState { export type OverlayToasterCreateOptions = DOMMountOptions; +interface OverlayToasterQueueState { + cancel: (() => void) | undefined; + isRunning: boolean; + toasts: ToastOptions[]; +} + +export const OVERLAY_TOASTER_DELAY_MS = 50; + /** * OverlayToaster component. * @@ -132,6 +140,14 @@ export class OverlayToaster extends AbstractPureComponent [options, ...toasts]); return options.key; } private maybeUpdateExistingToast(options: ToastOptions, key: string | undefined) { - if (key == null || this.isNewToastKey(key)) { + if (key == null) { return false; } - this.updateToastsInState(toasts => toasts.map(t => (t.key === key ? options : t))); - return true; + const isExistingQueuedToast = this.queue.toasts.some(toast => toast.key === key); + if (isExistingQueuedToast) { + this.queue.toasts = this.queue.toasts.map(t => (t.key === key ? options : t)); + return true; + } + + const isExistingShownToast = this.state.toasts.some(toast => toast.key === key); + if (isExistingShownToast) { + this.updateToastsInState(toasts => toasts.map(t => (t.key === key ? options : t))); + return true; + } + + return false; } + private immediatelyShowToast(options: ToastOptions) { + if (this.props.maxToasts) { + // check if active number of toasts are at the maxToasts limit + this.dismissIfAtLimit(); + } + + this.updateToastsInState(toasts => [options, ...toasts]); + } + + private startQueueTimeout() { + this.queue.isRunning = true; + this.queue.cancel = this.setTimeout(this.handleQueueTimeout, OVERLAY_TOASTER_DELAY_MS); + } + + private handleQueueTimeout = () => { + const nextToast = this.queue.toasts.shift(); + if (nextToast != null) { + this.immediatelyShowToast(nextToast); + this.startQueueTimeout(); + } else { + this.queue.isRunning = false; + } + }; + private updateToastsInState(getNewToasts: (toasts: ToastOptions[]) => ToastOptions[]) { this.setState(prevState => { const toasts = getNewToasts(prevState.toasts); @@ -191,6 +246,8 @@ export class OverlayToaster extends AbstractPureComponent t.onDismiss?.(false)); this.setState({ toasts: [], toastRefs: {} }); } @@ -252,10 +309,6 @@ export class OverlayToaster extends AbstractPureComponent toast.key !== key); - } - private dismissIfAtLimit() { if (this.state.toasts.length === this.props.maxToasts) { // dismiss the oldest toast to stay within the maxToasts limit diff --git a/packages/core/test/toast/overlayToasterTests.tsx b/packages/core/test/toast/overlayToasterTests.tsx index 21ac88e3f5..9c5eff8718 100644 --- a/packages/core/test/toast/overlayToasterTests.tsx +++ b/packages/core/test/toast/overlayToasterTests.tsx @@ -24,6 +24,7 @@ import { expectPropValidationError } from "@blueprintjs/test-commons"; import { Classes, OverlayToaster, type OverlayToasterProps, type Toaster } from "../../src"; import { TOASTER_CREATE_NULL, TOASTER_MAX_TOASTS_INVALID } from "../../src/common/errors"; +import { OVERLAY_TOASTER_DELAY_MS } from "../../src/components/toast/overlayToaster"; const SPECS = [ { @@ -64,6 +65,7 @@ function unmountReact16Toaster(containerElement: HTMLElement) { } describe("OverlayToaster", () => { + let clock: sinon.SinonFakeTimers; let testsContainerElement: HTMLElement; let toaster: Toaster; @@ -75,7 +77,12 @@ describe("OverlayToaster", () => { toaster = await spec.create({}, testsContainerElement); }); + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + afterEach(() => { + clock.restore(); toaster.clear(); }); @@ -93,11 +100,11 @@ describe("OverlayToaster", () => { }); it("show() renders toast on next tick", done => { + clock.restore(); toaster.show({ message: "Hello world", }); assert.lengthOf(toaster.getToasts(), 1, "expected 1 toast"); - // setState needs a tick to flush DOM updates setTimeout(() => { assert.isNotNull( @@ -112,9 +119,27 @@ describe("OverlayToaster", () => { toaster.show({ message: "one" }); toaster.show({ message: "two" }); toaster.show({ message: "six" }); + clock.tick(3 * OVERLAY_TOASTER_DELAY_MS); assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); }); + it("multiple shows() get queued if provided too quickly", () => { + toaster.show({ message: "one" }); + toaster.show({ message: "two" }); + toaster.show({ message: "three" }); + assert.lengthOf(toaster.getToasts(), 1, "expected 1 toast"); + clock.tick(3 * OVERLAY_TOASTER_DELAY_MS); + assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts after delay"); + }); + + it("show() immediately displays a toast when waiting after the previous show()", () => { + toaster.show({ message: "one" }); + assert.lengthOf(toaster.getToasts(), 1, "expected 1 toast"); + clock.tick(2 * OVERLAY_TOASTER_DELAY_MS); + toaster.show({ message: "two" }); + assert.lengthOf(toaster.getToasts(), 2, "expected 2 toasts"); + }); + it("show() updates existing toast", () => { const key = toaster.show({ message: "one" }); assert.deepEqual(toaster.getToasts()[0].message, "one"); @@ -123,10 +148,20 @@ describe("OverlayToaster", () => { assert.deepEqual(toaster.getToasts()[0].message, "two"); }); + it("show() updates existing toast in queue", () => { + toaster.show({ message: "one" }); + const key = toaster.show({ message: "two" }); + toaster.show({ message: "two updated" }, key); + clock.tick(2 * OVERLAY_TOASTER_DELAY_MS); + assert.lengthOf(toaster.getToasts(), 2, "expected 2 toasts"); + assert.deepEqual(toaster.getToasts()[0].message, "two updated"); + }); + it("dismiss() removes just the toast in question", () => { toaster.show({ message: "one" }); const key = toaster.show({ message: "two" }); toaster.show({ message: "six" }); + clock.tick(3 * OVERLAY_TOASTER_DELAY_MS); toaster.dismiss(key); assert.deepEqual( toaster.getToasts().map(t => t.message), @@ -138,9 +173,13 @@ describe("OverlayToaster", () => { toaster.show({ message: "one" }); toaster.show({ message: "two" }); toaster.show({ message: "six" }); - assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); + clock.tick(OVERLAY_TOASTER_DELAY_MS); + assert.lengthOf(toaster.getToasts(), 2, "expected 2 toasts"); toaster.clear(); assert.lengthOf(toaster.getToasts(), 0, "expected 0 toasts"); + // Ensure the queue is cleared + clock.tick(2 * OVERLAY_TOASTER_DELAY_MS); + assert.lengthOf(toaster.getToasts(), 0, "expected 0 toasts"); }); it("action onClick callback invoked when action clicked", () => { @@ -209,24 +248,33 @@ describe("OverlayToaster", () => { toaster = await spec.create({ maxToasts: 3 }, testsContainerElement); }); + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + after(() => { unmountReact16Toaster(testsContainerElement); document.documentElement.removeChild(testsContainerElement); }); + afterEach(() => { + clock.restore(); + }); it("does not exceed the maximum toast limit set", () => { toaster.show({ message: "one" }); toaster.show({ message: "two" }); toaster.show({ message: "three" }); toaster.show({ message: "oh no" }); + clock.tick(4 * OVERLAY_TOASTER_DELAY_MS); assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); }); - it("does not dismiss toasts when updating an existing toast at the limit", () => { + it("does not dismiss toasts when updating an existing toast at the limit", async () => { toaster.show({ message: "one" }); toaster.show({ message: "two" }); toaster.show({ message: "three" }, "3"); toaster.show({ message: "three updated" }, "3"); + clock.tick(4 * OVERLAY_TOASTER_DELAY_MS); assert.lengthOf(toaster.getToasts(), 3, "expected 3 toasts"); }); }); From 96f1ae4a6b7696e87ada94e64c6032cc2d880bf1 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Wed, 13 Nov 2024 13:46:55 -0500 Subject: [PATCH 027/121] fix(KeyComboTag): Style regression (#7063) --- packages/core/src/components/hotkeys/_hotkeys.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/components/hotkeys/_hotkeys.scss b/packages/core/src/components/hotkeys/_hotkeys.scss index 9b0c4f3ea5..d42937d7da 100644 --- a/packages/core/src/components/hotkeys/_hotkeys.scss +++ b/packages/core/src/components/hotkeys/_hotkeys.scss @@ -9,6 +9,10 @@ &:not(.#{$ns}-minimal) { @include pt-flex-container(row, $pt-grid-size * 0.5); } + + &.#{$ns}-minimal { + @include pt-flex-container(row); + } } .#{$ns}-hotkey-dialog { From b7b44d0b4af8b5e0be26b49b93df9161b38b904e Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Wed, 13 Nov 2024 15:16:24 -0500 Subject: [PATCH 028/121] fix(Tag): Add keyboard accessibility (#7060) Co-authored-by: svc-changelog --- .../core/changelog/@unreleased/pr-7060.v2.yml | 5 ++ .../accessibility/useInteractiveAttributes.ts | 88 +++++++++++++++++++ .../core/src/components/button/buttons.tsx | 75 ++-------------- packages/core/src/components/tag/tag.tsx | 8 +- 4 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 packages/core/changelog/@unreleased/pr-7060.v2.yml create mode 100644 packages/core/src/accessibility/useInteractiveAttributes.ts diff --git a/packages/core/changelog/@unreleased/pr-7060.v2.yml b/packages/core/changelog/@unreleased/pr-7060.v2.yml new file mode 100644 index 0000000000..d12e967caa --- /dev/null +++ b/packages/core/changelog/@unreleased/pr-7060.v2.yml @@ -0,0 +1,5 @@ +type: fix +fix: + description: 'fix(Tag): Add keyboard accessibility' + links: + - https://github.com/palantir/blueprint/pull/7060 diff --git a/packages/core/src/accessibility/useInteractiveAttributes.ts b/packages/core/src/accessibility/useInteractiveAttributes.ts new file mode 100644 index 0000000000..d2022e51b2 --- /dev/null +++ b/packages/core/src/accessibility/useInteractiveAttributes.ts @@ -0,0 +1,88 @@ +/* ! + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + */ + +import * as React from "react"; + +import { mergeRefs, Utils } from "../common"; + +type InteractiveHTMLAttributes = Pick< + React.HTMLAttributes, + "onBlur" | "onClick" | "onFocus" | "onKeyDown" | "onKeyUp" | "tabIndex" +>; + +interface InteractiveComponentProps extends InteractiveHTMLAttributes { + active?: boolean | undefined; +} + +interface InteractiveAttributes extends InteractiveHTMLAttributes { + ref: React.Ref; +} + +export function useInteractiveAttributes( + interactive: boolean, + props: InteractiveComponentProps, + ref: React.Ref, + defaultTabIndex?: number, +): [active: boolean, interactiveProps: InteractiveAttributes] { + const { active, onClick, onFocus, onKeyDown, onKeyUp, onBlur, tabIndex = defaultTabIndex } = props; + // the current key being pressed + const [currentKeyPressed, setCurrentKeyPressed] = React.useState(); + // whether the button is in "active" state + const [isActive, setIsActive] = React.useState(false); + // our local ref for the interactive element, merged with the consumer's own ref in this hook's return value + const elementRef = React.useRef(null); + + const handleBlur = React.useCallback( + (e: React.FocusEvent) => { + if (isActive) { + setIsActive(false); + } + + onBlur?.(e); + }, + [isActive, onBlur], + ); + + const handleKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (Utils.isKeyboardClick(e)) { + e.preventDefault(); + if (e.key !== currentKeyPressed) { + setIsActive(true); + } + } + + setCurrentKeyPressed(e.key); + onKeyDown?.(e); + }, + [currentKeyPressed, onKeyDown], + ); + + const handleKeyUp = React.useCallback( + (e: React.KeyboardEvent) => { + if (Utils.isKeyboardClick(e)) { + setIsActive(false); + elementRef.current?.click(); + } + setCurrentKeyPressed(undefined); + onKeyUp?.(e); + }, + [onKeyUp, elementRef], + ); + + const resolvedActive = interactive && (active || isActive); + + return [ + resolvedActive, + { + onBlur: handleBlur, + onClick: interactive ? onClick : undefined, + onFocus: interactive ? onFocus : undefined, + onKeyDown: handleKeyDown, + onKeyUp: handleKeyUp, + ref: mergeRefs(elementRef, ref), + tabIndex: interactive ? tabIndex : -1, + }, + ]; +} diff --git a/packages/core/src/components/button/buttons.tsx b/packages/core/src/components/button/buttons.tsx index 4ddeae7c5f..226b97d57c 100644 --- a/packages/core/src/components/button/buttons.tsx +++ b/packages/core/src/components/button/buttons.tsx @@ -17,9 +17,9 @@ import classNames from "classnames"; import * as React from "react"; +import { useInteractiveAttributes } from "../../accessibility/useInteractiveAttributes"; import { Classes, Utils } from "../../common"; import { DISPLAYNAME_PREFIX, removeNonHTMLProps } from "../../common/props"; -import { mergeRefs } from "../../common/refs"; import { Icon } from "../icon/icon"; import { Spinner, SpinnerSize } from "../spinner/spinner"; import { Text } from "../text/text"; @@ -49,7 +49,7 @@ Button.displayName = `${DISPLAYNAME_PREFIX}.Button`; */ export const AnchorButton: React.FC = React.forwardRef( (props, ref) => { - const { href, tabIndex = 0 } = props; + const { href } = props; const commonProps = useSharedButtonAttributes(props, ref); return ( @@ -59,7 +59,6 @@ export const AnchorButton: React.FC = React.forwardRef {renderButtonContents(props)} @@ -75,67 +74,15 @@ function useSharedButtonAttributes, ) { - const { - active = false, - alignText, - fill, - large, - loading = false, - minimal, - onBlur, - onKeyDown, - onKeyUp, - outlined, - small, - tabIndex, - } = props; + const { alignText, fill, large, loading = false, minimal, outlined, small } = props; const disabled = props.disabled || loading; - // the current key being pressed - const [currentKeyPressed, setCurrentKeyPressed] = React.useState(); - // whether the button is in "active" state - const [isActive, setIsActive] = React.useState(false); - // our local ref for the button element, merged with the consumer's own ref (if supplied) in this hook's return value - const buttonRef = React.useRef(null); - - const handleBlur = React.useCallback( - (e: React.FocusEvent) => { - if (isActive) { - setIsActive(false); - } - onBlur?.(e); - }, - [isActive, onBlur], - ); - const handleKeyDown = React.useCallback( - (e: React.KeyboardEvent) => { - if (Utils.isKeyboardClick(e)) { - e.preventDefault(); - if (e.key !== currentKeyPressed) { - setIsActive(true); - } - } - setCurrentKeyPressed(e.key); - onKeyDown?.(e); - }, - [currentKeyPressed, onKeyDown], - ); - const handleKeyUp = React.useCallback( - (e: React.KeyboardEvent) => { - if (Utils.isKeyboardClick(e)) { - setIsActive(false); - buttonRef.current?.click(); - } - setCurrentKeyPressed(undefined); - onKeyUp?.(e); - }, - [onKeyUp], - ); + const [active, interactiveProps] = useInteractiveAttributes(!disabled, props, ref); const className = classNames( Classes.BUTTON, { - [Classes.ACTIVE]: !disabled && (active || isActive), + [Classes.ACTIVE]: active, [Classes.DISABLED]: disabled, [Classes.FILL]: fill, [Classes.LARGE]: large, @@ -150,15 +97,9 @@ function useSharedButtonAttributes( {text} {children} - // - // {text} - // {children} - // )} diff --git a/packages/core/src/components/tag/tag.tsx b/packages/core/src/components/tag/tag.tsx index 9d0cac42d7..144cb7adf0 100644 --- a/packages/core/src/components/tag/tag.tsx +++ b/packages/core/src/components/tag/tag.tsx @@ -19,6 +19,7 @@ import * as React from "react"; import type { IconName } from "@blueprintjs/icons"; +import { useInteractiveAttributes } from "../../accessibility/useInteractiveAttributes"; import { Classes, DISPLAYNAME_PREFIX, type IntentProps, type MaybeElement, type Props, Utils } from "../../common"; import { isReactNodeEmpty } from "../../common/utils"; import { Icon } from "../icon/icon"; @@ -72,13 +73,12 @@ export interface TagProps */ export const Tag: React.FC = React.forwardRef((props, ref) => { const { - active, children, className, fill, icon, intent, - interactive, + interactive = false, large, minimal, multiline, @@ -92,6 +92,8 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { const isRemovable = Utils.isFunction(onRemove); + const [active, interactiveProps] = useInteractiveAttributes(interactive, props, ref, 0); + const tagClasses = classNames( Classes.TAG, Classes.intentClass(intent), @@ -107,7 +109,7 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { ); return ( - + {!isReactNodeEmpty(children) && ( From 2a48a033951833fc33c9268c92386e0653421baa Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Wed, 13 Nov 2024 15:22:33 -0500 Subject: [PATCH 029/121] chore: Publish new release - @blueprintjs/core@5.14.1 - @blueprintjs/datetime@5.3.14 - @blueprintjs/datetime2@2.3.14 - @blueprintjs/demo-app@5.14.2 - @blueprintjs/docs-app@5.14.2 - @blueprintjs/docs-data@5.14.2 - @blueprintjs/docs-theme@5.3.14 - @blueprintjs/landing-app@5.14.1 - @blueprintjs/popover2@2.1.13 - @blueprintjs/select@5.3.2 - @blueprintjs/table-dev-app@5.2.4 - @blueprintjs/table@5.2.4 --- packages/core/package.json | 2 +- packages/datetime/package.json | 2 +- packages/datetime2/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/docs-app/package.json | 2 +- packages/docs-data/package.json | 2 +- packages/docs-theme/package.json | 2 +- packages/landing-app/package.json | 2 +- packages/popover2/package.json | 2 +- packages/select/package.json | 2 +- packages/table-dev-app/package.json | 2 +- packages/table/package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 101152a517..f7c6ce60f2 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/core", - "version": "5.14.0", + "version": "5.14.1", "description": "Core styles & components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime/package.json b/packages/datetime/package.json index 08513aa635..ae833bacad 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime", - "version": "5.3.13", + "version": "5.3.14", "description": "Components for interacting with dates and times", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime2/package.json b/packages/datetime2/package.json index 34f062da0c..a4767de312 100644 --- a/packages/datetime2/package.json +++ b/packages/datetime2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime2", - "version": "2.3.13", + "version": "2.3.14", "description": "Re-exports of @blueprintjs/datetime APIs", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index 583158defb..1937675f05 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/demo-app", - "version": "5.14.1", + "version": "5.14.2", "description": "Blueprint Demo App", "private": true, "scripts": { diff --git a/packages/docs-app/package.json b/packages/docs-app/package.json index 1f759d0912..3fd3fbe8c0 100644 --- a/packages/docs-app/package.json +++ b/packages/docs-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-app", - "version": "5.14.1", + "version": "5.14.2", "description": "Blueprint Documentation Site", "private": true, "scripts": { diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index 2062eefb38..28aad766f5 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-data", - "version": "5.14.1", + "version": "5.14.2", "main": "src/index.js", "types": "src/index.d.ts", "private": true, diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index b81ce718d2..0855822d31 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-theme", - "version": "5.3.13", + "version": "5.3.14", "description": "Blueprint theme for documentalist", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/landing-app/package.json b/packages/landing-app/package.json index 23fbdc267b..e0b6ee905d 100644 --- a/packages/landing-app/package.json +++ b/packages/landing-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/landing-app", - "version": "5.14.0", + "version": "5.14.1", "description": "Blueprint landing page", "private": true, "scripts": { diff --git a/packages/popover2/package.json b/packages/popover2/package.json index 629fc9c596..a34343e2b4 100644 --- a/packages/popover2/package.json +++ b/packages/popover2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/popover2", - "version": "2.1.12", + "version": "2.1.13", "description": "Re-exports of popover-related components from @blueprintjs/core", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/select/package.json b/packages/select/package.json index 94f1d0a223..f36e7a9333 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/select", - "version": "5.3.1", + "version": "5.3.2", "description": "Components related to selecting items from a list", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/table-dev-app/package.json b/packages/table-dev-app/package.json index f1978f4c23..80621ff0e6 100644 --- a/packages/table-dev-app/package.json +++ b/packages/table-dev-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table-dev-app", - "version": "5.2.3", + "version": "5.2.4", "description": "Dev application for @blueprintjs/table", "private": true, "scripts": { diff --git a/packages/table/package.json b/packages/table/package.json index b013f405a8..a962889093 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table", - "version": "5.2.3", + "version": "5.2.4", "description": "Scalable interactive table component", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From d623a88770a7e59948e412453ffe44fa2826f48a Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 14 Nov 2024 10:32:02 -0500 Subject: [PATCH 030/121] fix(Tag) Ensure that non-interactive tags DOM props don't change (#7065) --- .../src/accessibility/useInteractiveAttributes.ts | 12 ++++++++++-- packages/core/src/components/tag/tag.tsx | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/core/src/accessibility/useInteractiveAttributes.ts b/packages/core/src/accessibility/useInteractiveAttributes.ts index d2022e51b2..7c3ef7149b 100644 --- a/packages/core/src/accessibility/useInteractiveAttributes.ts +++ b/packages/core/src/accessibility/useInteractiveAttributes.ts @@ -19,12 +19,20 @@ interface InteractiveAttributes extends InteractiveHTMLAt ref: React.Ref; } +interface UseInteractiveAttributesOptions { + defaultTabIndex: number | undefined; + disabledTabIndex: number | undefined; +} + +const DEFAULT_OPTIONS: UseInteractiveAttributesOptions = { defaultTabIndex: undefined, disabledTabIndex: -1 }; + export function useInteractiveAttributes( interactive: boolean, props: InteractiveComponentProps, ref: React.Ref, - defaultTabIndex?: number, + options: UseInteractiveAttributesOptions = DEFAULT_OPTIONS, ): [active: boolean, interactiveProps: InteractiveAttributes] { + const { defaultTabIndex, disabledTabIndex } = options; const { active, onClick, onFocus, onKeyDown, onKeyUp, onBlur, tabIndex = defaultTabIndex } = props; // the current key being pressed const [currentKeyPressed, setCurrentKeyPressed] = React.useState(); @@ -82,7 +90,7 @@ export function useInteractiveAttributes( onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, ref: mergeRefs(elementRef, ref), - tabIndex: interactive ? tabIndex : -1, + tabIndex: interactive ? tabIndex : disabledTabIndex, }, ]; } diff --git a/packages/core/src/components/tag/tag.tsx b/packages/core/src/components/tag/tag.tsx index 144cb7adf0..de2817c3a5 100644 --- a/packages/core/src/components/tag/tag.tsx +++ b/packages/core/src/components/tag/tag.tsx @@ -92,7 +92,10 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { const isRemovable = Utils.isFunction(onRemove); - const [active, interactiveProps] = useInteractiveAttributes(interactive, props, ref, 0); + const [active, interactiveProps] = useInteractiveAttributes(interactive, props, ref, { + defaultTabIndex: 0, + disabledTabIndex: undefined, + }); const tagClasses = classNames( Classes.TAG, From 4f0d37dd40e856e16686da157fd35450504fbe72 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 14 Nov 2024 10:39:24 -0500 Subject: [PATCH 031/121] chore: Publish new release - @blueprintjs/core@5.14.2 - @blueprintjs/datetime@5.3.15 - @blueprintjs/datetime2@2.3.15 - @blueprintjs/demo-app@5.14.3 - @blueprintjs/docs-app@5.14.3 - @blueprintjs/docs-data@5.14.3 - @blueprintjs/docs-theme@5.3.15 - @blueprintjs/landing-app@5.14.2 - @blueprintjs/popover2@2.1.14 - @blueprintjs/select@5.3.3 - @blueprintjs/table-dev-app@5.2.5 - @blueprintjs/table@5.2.5 --- packages/core/package.json | 2 +- packages/datetime/package.json | 2 +- packages/datetime2/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/docs-app/package.json | 2 +- packages/docs-data/package.json | 2 +- packages/docs-theme/package.json | 2 +- packages/landing-app/package.json | 2 +- packages/popover2/package.json | 2 +- packages/select/package.json | 2 +- packages/table-dev-app/package.json | 2 +- packages/table/package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index f7c6ce60f2..0c5b64ecc5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/core", - "version": "5.14.1", + "version": "5.14.2", "description": "Core styles & components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime/package.json b/packages/datetime/package.json index ae833bacad..9fded71858 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime", - "version": "5.3.14", + "version": "5.3.15", "description": "Components for interacting with dates and times", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime2/package.json b/packages/datetime2/package.json index a4767de312..633ec01984 100644 --- a/packages/datetime2/package.json +++ b/packages/datetime2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime2", - "version": "2.3.14", + "version": "2.3.15", "description": "Re-exports of @blueprintjs/datetime APIs", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index 1937675f05..97a143ef27 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/demo-app", - "version": "5.14.2", + "version": "5.14.3", "description": "Blueprint Demo App", "private": true, "scripts": { diff --git a/packages/docs-app/package.json b/packages/docs-app/package.json index 3fd3fbe8c0..ca82437dbe 100644 --- a/packages/docs-app/package.json +++ b/packages/docs-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-app", - "version": "5.14.2", + "version": "5.14.3", "description": "Blueprint Documentation Site", "private": true, "scripts": { diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index 28aad766f5..d2b0bdaf4b 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-data", - "version": "5.14.2", + "version": "5.14.3", "main": "src/index.js", "types": "src/index.d.ts", "private": true, diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index 0855822d31..da8067dbea 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-theme", - "version": "5.3.14", + "version": "5.3.15", "description": "Blueprint theme for documentalist", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/landing-app/package.json b/packages/landing-app/package.json index e0b6ee905d..220d5b89aa 100644 --- a/packages/landing-app/package.json +++ b/packages/landing-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/landing-app", - "version": "5.14.1", + "version": "5.14.2", "description": "Blueprint landing page", "private": true, "scripts": { diff --git a/packages/popover2/package.json b/packages/popover2/package.json index a34343e2b4..9224025610 100644 --- a/packages/popover2/package.json +++ b/packages/popover2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/popover2", - "version": "2.1.13", + "version": "2.1.14", "description": "Re-exports of popover-related components from @blueprintjs/core", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/select/package.json b/packages/select/package.json index f36e7a9333..71886514cb 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/select", - "version": "5.3.2", + "version": "5.3.3", "description": "Components related to selecting items from a list", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/table-dev-app/package.json b/packages/table-dev-app/package.json index 80621ff0e6..c29bbf42fc 100644 --- a/packages/table-dev-app/package.json +++ b/packages/table-dev-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table-dev-app", - "version": "5.2.4", + "version": "5.2.5", "description": "Dev application for @blueprintjs/table", "private": true, "scripts": { diff --git a/packages/table/package.json b/packages/table/package.json index a962889093..0108ec26b0 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table", - "version": "5.2.4", + "version": "5.2.5", "description": "Scalable interactive table component", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From 18e55c67918f2f16a4d806c9965d00d1a150b2b0 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Thu, 14 Nov 2024 11:55:51 -0500 Subject: [PATCH 032/121] Horizontally center map-marker icon (#7066) --- resources/icons/16px/map-marker.svg | 14 ++------------ resources/icons/20px/map-marker.svg | 13 ++----------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/resources/icons/16px/map-marker.svg b/resources/icons/16px/map-marker.svg index a369d7862f..5858d46ec5 100644 --- a/resources/icons/16px/map-marker.svg +++ b/resources/icons/16px/map-marker.svg @@ -1,13 +1,3 @@ - - - - - - - - - + + diff --git a/resources/icons/20px/map-marker.svg b/resources/icons/20px/map-marker.svg index 4e93c66b1e..fff3330bce 100644 --- a/resources/icons/20px/map-marker.svg +++ b/resources/icons/20px/map-marker.svg @@ -1,12 +1,3 @@ - - - - - - - - - + + From e1c83824d0aa617296df99647852cd3717466482 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Thu, 14 Nov 2024 13:10:28 -0500 Subject: [PATCH 033/121] Fix focus hang on MultiSelect/Suggest inputs in Firefox (#7068) --- packages/select/src/components/multi-select/multiSelect.tsx | 4 ++-- packages/select/src/components/suggest/suggest.tsx | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/select/src/components/multi-select/multiSelect.tsx b/packages/select/src/components/multi-select/multiSelect.tsx index 1d69815585..e247ce7cec 100644 --- a/packages/select/src/components/multi-select/multiSelect.tsx +++ b/packages/select/src/components/multi-select/multiSelect.tsx @@ -419,8 +419,8 @@ export class MultiSelect extends AbstractPureComponent, M if (e.key === "Escape" || e.key === "Tab") { // By default the escape key will not trigger a blur on the // input element. It must be done explicitly. - if (this.input != null) { - this.input.blur(); + if (e.key === "Escape") { + this.input?.blur(); } this.setState({ isOpen: false }); } else if (!(e.key === "Backspace" || e.key === "ArrowLeft" || e.key === "ArrowRight")) { diff --git a/packages/select/src/components/suggest/suggest.tsx b/packages/select/src/components/suggest/suggest.tsx index 9cc3b87173..30d8cdf025 100644 --- a/packages/select/src/components/suggest/suggest.tsx +++ b/packages/select/src/components/suggest/suggest.tsx @@ -365,7 +365,11 @@ export class Suggest extends AbstractPureComponent, SuggestSt ) => { return (e: React.KeyboardEvent) => { if (e.key === "Escape" || e.key === "Tab") { - this.inputElement?.blur(); + // By default the escape key will not trigger a blur on the + // input element. It must be done explicitly. + if (e.key === "Escape") { + this.inputElement?.blur(); + } this.setState({ isOpen: false }); } else if ( this.props.openOnKeyDown && From 3e95e15d2ab2f9413f7bb3d33b7a522acae950da Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Thu, 14 Nov 2024 14:53:50 -0500 Subject: [PATCH 034/121] Rewrite Button docs with more discrete examples (#7027) --- .../core/src/components/button/buttons.md | 135 ++++++++++---- .../examples/core-examples/buttonExamples.tsx | 175 ++++++++++++++++++ ...xample.tsx => buttonPlaygroundExample.tsx} | 6 +- .../core-examples/buttonsIconsExample.tsx | 37 ---- .../src/examples/core-examples/index.ts | 4 +- packages/docs-app/src/index.tsx | 9 +- packages/docs-app/src/styles/_examples.scss | 31 +++- .../docs-theme/src/components/codeExample.tsx | 31 ++++ packages/docs-theme/src/docs-theme.scss | 1 + packages/docs-theme/src/index.ts | 1 + .../docs-theme/src/styles/_code-example.scss | 20 ++ packages/docs-theme/src/tags/index.ts | 1 + .../docs-theme/src/tags/reactCodeExample.tsx | 24 +++ 13 files changed, 391 insertions(+), 84 deletions(-) create mode 100644 packages/docs-app/src/examples/core-examples/buttonExamples.tsx rename packages/docs-app/src/examples/core-examples/{buttonsExample.tsx => buttonPlaygroundExample.tsx} (97%) delete mode 100644 packages/docs-app/src/examples/core-examples/buttonsIconsExample.tsx create mode 100644 packages/docs-theme/src/components/codeExample.tsx create mode 100644 packages/docs-theme/src/styles/_code-example.scss create mode 100644 packages/docs-theme/src/tags/reactCodeExample.tsx diff --git a/packages/core/src/components/button/buttons.md b/packages/core/src/components/button/buttons.md index 34b5b9c65b..6f111fb4ea 100644 --- a/packages/core/src/components/button/buttons.md +++ b/packages/core/src/components/button/buttons.md @@ -1,55 +1,110 @@ @# Buttons -Buttons trigger actions when clicked. You may render a button as either a `
-
+The `intent` prop is used to visually communicate the purpose or importance of the action associated with a button. Blueprint provides several intent options to convey meaning through color: -Disabled __Button__ elements prevent all interaction -
+- **Primary**: Indicates the main action and is usually styled more prominently. +- **Success**: Represents a positive outcome or confirmation. +- **Warning**: Used to alert users to potentially dangerous actions. +- **Danger**: Signifies a destructive or critical action. + +@reactCodeExample ButtonIntentExample + +@## Minimal + +The `minimal` prop offers a button without borders or background, ideal for subtle actions or secondary options that shouldn't draw too much attention. + +@reactCodeExample ButtonMinimalExample + +@## Outlined + +The `outlined` prop provides a button with an outline, creating a middle ground between a prominent default button and a subtle minimal button. + +@reactCodeExample ButtonOutlinedExample + +@## Size + +The `small` and `large` props allow for adjusting the size of a button to fit different use cases. + +@reactCodeExample ButtonSizeExample + +@## Fill + +The `fill` prop allows a button to expand and fill the available space in its container. + +@reactCodeExample ButtonFillExample + +@## Aligned text + +The `alignText` prop controls the horizontal alignment of a button's text and icons. + +@reactCodeExample ButtonAlignTextExample + +@## Ellipsized text + +The `ellipsizeText` prop allows text within a button to be truncated with an ellipsis if it exceeds the available space. This is useful for cases where the button needs to remain compact without overflowing, especially when the text content is dynamic or potentially lengthy. + +@reactCodeExample ButtonEllipsizeTextExample + +@## Icons with text + +Buttons can include icons alongside text for extra context or visual cues. Icons can be added to either the left or right side of text/children with the `icon` and `rightIcon` props respectively. These icons can either be specified as string identifiers (e.g. `"arrow-right"`), dynamically-loaded [`` components](#core/components/icon), [static icon components](#core/components/icon.static-components) (e.g. ``), or any custom JSX element. + +@reactCodeExample ButtonIconWithTextExample + +@## Icon buttons + +Icon buttons display only an icon without any accompanying text. Icon buttons are used when an action can be clearly conveyed through a visual symbol, making the interface more compact and visually appealing. They are ideal for toolbars or areas with limited space. + +@reactCodeExample ButtonIconExample + +@## Button states + +Buttons have different states to show their interaction status. The `active`, `disabled`, and `loading` props provide visual feedback to help users understand available actions and when to wait. + +- **Active**: Indicates that the button is currently being pressed or interacted with. +- **Disabled**: Shows that the button is non-interactive. +- **Loading**: Displays a loading spinner to indicate that an action is in progress. + +@reactCodeExample ButtonStatesExample + +@## AnchorButton + +The **AnchorButton** component behaves like an anchor (`` tag) and is useful for navigation actions. AnchorButton accepts all props of both a standard button and an anchor tag, making it flexible for use as a styled link. + +@reactCodeExample ButtonAnchorButtonExample + + -@## Adding icons +@reactCodeExample ButtonDisabledButtonTooltipExample -__Button__ and __AnchorButton__ support `icon` and `rightIcon` props to place an icon on either end of their text/children. -These icons can either be specified as string identifiers (e.g. `"arrow-right"`), dynamically-loaded -[`` components](https://blueprintjs.com/docs/#core/components/icon), -[static icon components](#core/components/icon.static-components) (e.g. ``), or any custom JSX element. +@## Interactive Playground -@reactExample ButtonsIconsExample +@reactExample ButtonPlaygroundExample @## Props interface @@ -76,12 +131,12 @@ often fall out of sync as the design system is updated. You should use the React Use the `@ns-button` class to access button styles. You should implement buttons using the ``; + return ( + + + + ); +}; + +export const ButtonIconExample: React.FC = props => { + const code = ` - - ); - } -} diff --git a/packages/docs-app/src/examples/core-examples/index.ts b/packages/docs-app/src/examples/core-examples/index.ts index a925c20840..d5c6b0e820 100644 --- a/packages/docs-app/src/examples/core-examples/index.ts +++ b/packages/docs-app/src/examples/core-examples/index.ts @@ -16,8 +16,8 @@ export * from "./alertExample"; export * from "./breadcrumbsExample"; -export * from "./buttonsExample"; -export * from "./buttonsIconsExample"; +export * from "./buttonExamples"; +export * from "./buttonPlaygroundExample"; export * from "./buttonGroupExample"; export * from "./buttonGroupPopoverExample"; export * from "./calloutExample"; diff --git a/packages/docs-app/src/index.tsx b/packages/docs-app/src/index.tsx index cdb0d06109..bd8effeab9 100644 --- a/packages/docs-app/src/index.tsx +++ b/packages/docs-app/src/index.tsx @@ -17,7 +17,12 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import { docsData } from "@blueprintjs/docs-data"; -import { createDefaultRenderers, ReactDocsTagRenderer, ReactExampleTagRenderer } from "@blueprintjs/docs-theme"; +import { + createDefaultRenderers, + ReactCodeExampleTagRenderer, + ReactDocsTagRenderer, + ReactExampleTagRenderer, +} from "@blueprintjs/docs-theme"; import { Icons } from "@blueprintjs/icons"; import { BlueprintDocs } from "./components/blueprintDocs"; @@ -27,11 +32,13 @@ import { reactExamples } from "./tags/reactExamples"; // load all icons up front so that they do not experience a flash of unstyled content (but we don't need to block on this promise) Icons.loadAll(); +const reactCodeExample = new ReactCodeExampleTagRenderer(reactExamples); const reactDocs = new ReactDocsTagRenderer(ReactDocs as any); const reactExample = new ReactExampleTagRenderer(reactExamples); const tagRenderers = { ...createDefaultRenderers(), + reactCodeExample: reactCodeExample.render, reactDocs: reactDocs.render, reactExample: reactExample.render, }; diff --git a/packages/docs-app/src/styles/_examples.scss b/packages/docs-app/src/styles/_examples.scss index d8a7ab487f..c4698a08aa 100644 --- a/packages/docs-app/src/styles/_examples.scss +++ b/packages/docs-app/src/styles/_examples.scss @@ -45,7 +45,7 @@ width: $pt-grid-size * 35; } -#{example("Buttons")} { +#{example("ButtonPlayground")} { .docs-example { flex-direction: column; gap: $pt-grid-size * 3; @@ -68,6 +68,35 @@ } } +#{example("ButtonIntent")}, +#{example("ButtonOutlined")}, +#{example("ButtonSize")}, +#{example("ButtonIconWithText")}, +#{example("ButtonIcon")}, +#{example("ButtonStates")} { + .docs-code-example { + gap: $pt-grid-size; + } +} + +#{example("ButtonAlignText")} { + .docs-code-example { + flex-direction: column; + gap: $pt-grid-size; + } + + .#{$ns}-button { + max-width: 300px; + width: 100%; + } +} + +#{example("ButtonEllipsizeText")} { + .#{$ns}-button { + max-width: 250px; + } +} + #{example("CardList")} { .docs-example > div { flex-grow: 1; diff --git a/packages/docs-theme/src/components/codeExample.tsx b/packages/docs-theme/src/components/codeExample.tsx new file mode 100644 index 0000000000..d090f59c67 --- /dev/null +++ b/packages/docs-theme/src/components/codeExample.tsx @@ -0,0 +1,31 @@ +/* ! + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + */ + +import classNames from "classnames"; +import * as React from "react"; + +import { Pre } from "@blueprintjs/core"; + +import { DOCS_CODE_BLOCK } from "../common/classes"; + +export interface CodeExampleProps { + children?: React.ReactNode; + className?: string; + code: string; + id: string; +} + +export const CodeExample: React.FC = props => { + const { children, className, code, id, ...rest } = props; + const classes = classNames("docs-code-example-frame", className); + + return ( +
+
{children}
+
+                {code}
+            
+
+ ); +}; diff --git a/packages/docs-theme/src/docs-theme.scss b/packages/docs-theme/src/docs-theme.scss index 333f93ede5..80fb772cd1 100644 --- a/packages/docs-theme/src/docs-theme.scss +++ b/packages/docs-theme/src/docs-theme.scss @@ -8,6 +8,7 @@ Licensed under the Apache License, Version 2.0. @import "styles/api"; @import "styles/banner"; @import "styles/code-block"; +@import "styles/code-example"; @import "styles/content"; @import "styles/examples"; @import "styles/layout"; diff --git a/packages/docs-theme/src/index.ts b/packages/docs-theme/src/index.ts index b18b675a23..2b2af5a7c0 100644 --- a/packages/docs-theme/src/index.ts +++ b/packages/docs-theme/src/index.ts @@ -17,6 +17,7 @@ export * from "./components/banner"; export * from "./components/documentation"; export * from "./components/example"; +export * from "./components/codeExample"; export * from "./components/navMenuItem"; export * from "./components/navButton"; export * from "./common"; diff --git a/packages/docs-theme/src/styles/_code-example.scss b/packages/docs-theme/src/styles/_code-example.scss new file mode 100644 index 0000000000..a09df1d425 --- /dev/null +++ b/packages/docs-theme/src/styles/_code-example.scss @@ -0,0 +1,20 @@ +.docs-code-example-frame { + display: flex; + flex-direction: column; + margin-top: $pt-grid-size * 2; + position: relative; + width: 100%; +} + +.docs-code-example { + align-items: center; + background: $pt-app-secondary-background-color; + border-radius: $pt-border-radius * 2; + display: flex; + justify-content: center; + padding: $pt-grid-size * 2; + + .#{$ns}-dark & { + background: $pt-dark-app-secondary-background-color; + } +} diff --git a/packages/docs-theme/src/tags/index.ts b/packages/docs-theme/src/tags/index.ts index 5e9c53c782..6cfc28ca66 100644 --- a/packages/docs-theme/src/tags/index.ts +++ b/packages/docs-theme/src/tags/index.ts @@ -24,6 +24,7 @@ export * from "./css"; export * from "./defaults"; export * from "./heading"; export * from "./method"; +export * from "./reactCodeExample"; export * from "./reactDocs"; export * from "./reactExample"; export * from "./see"; diff --git a/packages/docs-theme/src/tags/reactCodeExample.tsx b/packages/docs-theme/src/tags/reactCodeExample.tsx new file mode 100644 index 0000000000..d0ada2dcdd --- /dev/null +++ b/packages/docs-theme/src/tags/reactCodeExample.tsx @@ -0,0 +1,24 @@ +/* ! + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + */ + +import type { Tag } from "@documentalist/client"; +import type * as React from "react"; + +import type { ExampleMap } from "./reactExample"; + +export class ReactCodeExampleTagRenderer { + constructor(private examples: ExampleMap) {} + + public render: React.FC = ({ value: exampleName }) => { + if (exampleName == null) { + return null; + } + + const example = this.examples[exampleName]; + if (example == null) { + throw new Error(`Unknown @example component: ${exampleName}`); + } + return example.render({ id: exampleName }) ?? null; + }; +} From fbfcaca029a1ebccd44e03bfe22ff700f0451ab8 Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Thu, 14 Nov 2024 15:50:45 -0500 Subject: [PATCH 035/121] fix(Tag) No more accidentally passing active as html prop (#7069) --- packages/core/src/components/tag/tag.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/tag/tag.tsx b/packages/core/src/components/tag/tag.tsx index de2817c3a5..262127d3c1 100644 --- a/packages/core/src/components/tag/tag.tsx +++ b/packages/core/src/components/tag/tag.tsx @@ -20,7 +20,15 @@ import * as React from "react"; import type { IconName } from "@blueprintjs/icons"; import { useInteractiveAttributes } from "../../accessibility/useInteractiveAttributes"; -import { Classes, DISPLAYNAME_PREFIX, type IntentProps, type MaybeElement, type Props, Utils } from "../../common"; +import { + Classes, + DISPLAYNAME_PREFIX, + type IntentProps, + type MaybeElement, + type Props, + removeNonHTMLProps, + Utils, +} from "../../common"; import { isReactNodeEmpty } from "../../common/utils"; import { Icon } from "../icon/icon"; import { Text } from "../text/text"; @@ -112,7 +120,7 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { ); return ( - + {!isReactNodeEmpty(children) && ( From 1e6aef29644c30af483bd085ceb9310cf27fc91f Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Fri, 15 Nov 2024 16:06:50 -0500 Subject: [PATCH 036/121] fix(Tag): Mark tag as interactive when onClick is set (#7073) --- packages/core/src/components/tag/tag.tsx | 18 ++++++++++++++---- packages/core/test/tag/tagTests.tsx | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/tag/tag.tsx b/packages/core/src/components/tag/tag.tsx index 262127d3c1..376926f6bf 100644 --- a/packages/core/src/components/tag/tag.tsx +++ b/packages/core/src/components/tag/tag.tsx @@ -52,6 +52,16 @@ export interface TagProps */ htmlTitle?: string; + /** + * Whether the tag should visually respond to user interactions. If set to `true`, hovering over the + * tag will change its color and mouse cursor. + * + * Tags will be marked as interactive automatically if an onClick handler is provided and this prop is not. + * + * @default false + */ + interactive?: boolean; + /** * Name of a Blueprint UI icon (or an icon element) to render on the left side of the tag, * before the child nodes. @@ -86,7 +96,7 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { fill, icon, intent, - interactive = false, + interactive, large, minimal, multiline, @@ -99,8 +109,9 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { } = props; const isRemovable = Utils.isFunction(onRemove); + const isInteractive = interactive ?? htmlProps.onClick != null; - const [active, interactiveProps] = useInteractiveAttributes(interactive, props, ref, { + const [active, interactiveProps] = useInteractiveAttributes(isInteractive, props, ref, { defaultTabIndex: 0, disabledTabIndex: undefined, }); @@ -111,7 +122,7 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { { [Classes.ACTIVE]: active, [Classes.FILL]: fill, - [Classes.INTERACTIVE]: interactive, + [Classes.INTERACTIVE]: isInteractive, [Classes.LARGE]: large, [Classes.MINIMAL]: minimal, [Classes.ROUND]: round, @@ -135,7 +146,6 @@ export const Tag: React.FC = React.forwardRef((props, ref) => { Tag.defaultProps = { active: false, fill: false, - interactive: false, large: false, minimal: false, round: false, diff --git a/packages/core/test/tag/tagTests.tsx b/packages/core/test/tag/tagTests.tsx index 3ba2e79082..0fe76332b1 100644 --- a/packages/core/test/tag/tagTests.tsx +++ b/packages/core/test/tag/tagTests.tsx @@ -57,6 +57,20 @@ describe("", () => { assert.isTrue(handleRemove.calledOnce); }); + it("should be interactive when onClick is provided", () => { + const wrapper = mount(Hello); + assert.lengthOf(wrapper.find(`.${Classes.INTERACTIVE}`), 1); + }); + + it("should not be interactive when interactive={false}", () => { + const wrapper = mount( + + Hello + , + ); + assert.lengthOf(wrapper.find(`.${Classes.INTERACTIVE}`), 0); + }); + it(`passes other props onto .${Classes.TAG} element`, () => { const element = shallow(Hello).find("." + Classes.TAG); assert.deepEqual(element.prop("title"), "baz qux"); From a7815e64f77f196dae41c3cd71a1ac807368b41c Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Mon, 18 Nov 2024 15:52:58 -0500 Subject: [PATCH 037/121] [core] feat: darken dark theme colors (#6711) Co-authored-by: jessperrin <144474750+jessperrin@users.noreply.github.com> Co-authored-by: Gregory Douglas <2968519+ggdouglas@users.noreply.github.com> Co-authored-by: Gregory Douglas --- packages/core/src/common/_color-aliases.scss | 8 +- .../core/src/common/_typography-colors.scss | 6 +- packages/core/src/common/classes.ts | 6 +- .../core/src/components/button/_common.scss | 8 +- .../src/components/card-list/card-list.scss | 4 +- .../core/src/components/drawer/_drawer.scss | 2 +- .../components/panel-stack2/panelView2.tsx | 2 +- .../segmented-control/_segmented-control.scss | 2 +- packages/core/src/components/tag/_common.scss | 30 --- .../src/components/tag/_compound-tag.scss | 7 +- .../core/src/components/toast/_toast.scss | 2 +- packages/demo-app/src/styles/_examples.scss | 12 +- .../core-examples/buttonPlaygroundExample.tsx | 4 +- .../core-examples/checkboxExample.tsx | 6 +- .../core-examples/formGroupExample.tsx | 54 +++-- .../core-examples/progressExample.tsx | 6 +- .../examples/core-examples/radioExample.tsx | 26 +-- .../examples/core-examples/sectionExample.tsx | 97 ++++----- .../examples/core-examples/sliderExample.tsx | 70 +++---- .../examples/core-examples/spinnerExample.tsx | 16 +- .../examples/core-examples/switchExample.tsx | 6 +- .../examples/core-examples/tabsExample.tsx | 191 +++++++++--------- .../examples/core-examples/treeExample.tsx | 30 ++- .../select-examples/omnibarExample.tsx | 14 +- packages/docs-app/src/styles/_examples.scss | 31 ++- .../src/components/documentation.tsx | 3 + .../docs-theme/src/components/navigator.tsx | 8 +- .../docs-theme/src/styles/_variables.scss | 2 +- .../src/components/omnibar/_omnibar.scss | 2 +- 29 files changed, 357 insertions(+), 298 deletions(-) diff --git a/packages/core/src/common/_color-aliases.scss b/packages/core/src/common/_color-aliases.scss index 2f38acc488..427cadcc33 100644 --- a/packages/core/src/common/_color-aliases.scss +++ b/packages/core/src/common/_color-aliases.scss @@ -16,7 +16,13 @@ $pt-intent-warning: $orange3 !default; $pt-intent-danger: $red3 !default; $pt-app-background-color: $light-gray5 !default; -$pt-dark-app-background-color: $dark-gray2 !default; +$pt-dark-app-background-color: $dark-gray1 !default; + +$pt-app-secondary-background-color: $white !default; +$pt-dark-app-secondary-background-color: $black !default; + +$pt-app-elevated-background-color: $light-gray4 !default; +$pt-dark-app-elevated-background-color: $dark-gray2 !default; $pt-app-secondary-background-color: $white !default; $pt-dark-app-secondary-background-color: $dark-gray1 !default; diff --git a/packages/core/src/common/_typography-colors.scss b/packages/core/src/common/_typography-colors.scss index ffc38e8880..ab584742f3 100644 --- a/packages/core/src/common/_typography-colors.scss +++ b/packages/core/src/common/_typography-colors.scss @@ -123,7 +123,7 @@ .#{$ns}-code, .#{$ns}-running-text code { background: $pt-dark-code-background-color; - box-shadow: inset border-shadow(0.4); + box-shadow: inset border-shadow(0.2, $white); color: $pt-dark-code-text-color; a > & { @@ -134,7 +134,7 @@ .#{$ns}-code-block, .#{$ns}-running-text pre { background: $pt-dark-code-background-color; - box-shadow: inset 0 0 0 1px $pt-dark-divider-black; + box-shadow: inset border-shadow(0.2, $white); color: $pt-dark-text-color; > code { @@ -146,7 +146,7 @@ .#{$ns}-key, .#{$ns}-running-text kbd { - background: $dark-gray4; + background: $dark-gray3; box-shadow: $pt-dark-elevation-shadow-1; color: $pt-dark-text-color-muted; } diff --git a/packages/core/src/common/classes.ts b/packages/core/src/common/classes.ts index 4998736d6c..94047288bb 100644 --- a/packages/core/src/common/classes.ts +++ b/packages/core/src/common/classes.ts @@ -280,9 +280,9 @@ export const PANEL_STACK_HEADER_BACK = `${PANEL_STACK}-header-back`; export const PANEL_STACK_VIEW = `${PANEL_STACK}-view`; export const PANEL_STACK2 = `${NS}-panel-stack2`; -export const PANEL_STACK2_HEADER = `${PANEL_STACK}-header`; -export const PANEL_STACK2_HEADER_BACK = `${PANEL_STACK}-header-back`; -export const PANEL_STACK2_VIEW = `${PANEL_STACK}-view`; +export const PANEL_STACK2_HEADER = `${PANEL_STACK2}-header`; +export const PANEL_STACK2_HEADER_BACK = `${PANEL_STACK2}-header-back`; +export const PANEL_STACK2_VIEW = `${PANEL_STACK2}-view`; export const POPOVER = `${NS}-popover`; export const POPOVER_ARROW = `${POPOVER}-arrow`; diff --git a/packages/core/src/components/button/_common.scss b/packages/core/src/components/button/_common.scss index d7f717f38c..1c5a0d186e 100644 --- a/packages/core/src/components/button/_common.scss +++ b/packages/core/src/components/button/_common.scss @@ -44,11 +44,11 @@ $button-background-color-disabled: rgba($light-gray1, 0.5) !default; $button-background-color-active-disabled: rgba($light-gray1, 0.7) !default; $button-intent-color-disabled: rgba($white, 0.6); $dark-button-color-disabled: $pt-dark-text-color-disabled !default; -$dark-button-background-color: $dark-gray4 !default; -$dark-button-background-color-hover: $dark-gray3 !default; +$dark-button-background-color: $dark-gray3 !default; +$dark-button-background-color-hover: $dark-gray2 !default; $dark-button-background-color-active: $dark-gray1 !default; -$dark-button-background-color-disabled: rgba($dark-gray5, 0.5) !default; -$dark-button-background-color-active-disabled: rgba($dark-gray5, 0.7) !default; +$dark-button-background-color-disabled: rgba($dark-gray3, 0.15) !default; +$dark-button-background-color-active-disabled: rgba($dark-gray3, 0.7) !default; $dark-button-intent-color-disabled: rgba($white, 0.3); $minimal-button-divider-width: 1px !default; diff --git a/packages/core/src/components/card-list/card-list.scss b/packages/core/src/components/card-list/card-list.scss index 1720ad369b..eb6cf5dad8 100644 --- a/packages/core/src/components/card-list/card-list.scss +++ b/packages/core/src/components/card-list/card-list.scss @@ -22,7 +22,7 @@ box-shadow: none; .#{$ns}-dark & { - background-color: $dark-gray4; + background-color: $dark-gray3; } } @@ -31,7 +31,7 @@ box-shadow: none; .#{$ns}-dark & { - background-color: $dark-gray5; + background-color: $dark-gray4; box-shadow: none; } } diff --git a/packages/core/src/components/drawer/_drawer.scss b/packages/core/src/components/drawer/_drawer.scss index 436e2b9826..315ba70905 100644 --- a/packages/core/src/components/drawer/_drawer.scss +++ b/packages/core/src/components/drawer/_drawer.scss @@ -12,7 +12,7 @@ $drawer-padding: $pt-grid-size * 2 !default; $drawer-default-size: 50%; $drawer-background-color: $white !default; -$dark-drawer-background-color: $dark-gray4 !default; +$dark-drawer-background-color: $dark-gray3 !default; .#{$ns}-drawer { background: $drawer-background-color; diff --git a/packages/core/src/components/panel-stack2/panelView2.tsx b/packages/core/src/components/panel-stack2/panelView2.tsx index 808d98c116..f3afb007cc 100644 --- a/packages/core/src/components/panel-stack2/panelView2.tsx +++ b/packages/core/src/components/panel-stack2/panelView2.tsx @@ -72,7 +72,7 @@ export const PanelView2: PanelView2Component = >({ previousPanel === undefined ? null : (
+ ); } diff --git a/packages/docs-app/src/examples/core-examples/formGroupExample.tsx b/packages/docs-app/src/examples/core-examples/formGroupExample.tsx index dd6b67b710..5869e4c647 100644 --- a/packages/docs-app/src/examples/core-examples/formGroupExample.tsx +++ b/packages/docs-app/src/examples/core-examples/formGroupExample.tsx @@ -16,7 +16,19 @@ import * as React from "react"; -import { Classes, Code, Divider, FormGroup, H5, Icon, InputGroup, Intent, Switch, Tooltip } from "@blueprintjs/core"; +import { + Card, + Classes, + Code, + Divider, + FormGroup, + H5, + Icon, + InputGroup, + Intent, + Switch, + Tooltip, +} from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; import { IntentSelect } from "./common/intentSelect"; @@ -69,25 +81,27 @@ export const FormGroupExample: React.FC = props => { return ( - - - - - - - + + + + + + + + + ); }; diff --git a/packages/docs-app/src/examples/core-examples/progressExample.tsx b/packages/docs-app/src/examples/core-examples/progressExample.tsx index 210b01678c..ba33ce3b98 100644 --- a/packages/docs-app/src/examples/core-examples/progressExample.tsx +++ b/packages/docs-app/src/examples/core-examples/progressExample.tsx @@ -16,7 +16,7 @@ import * as React from "react"; -import { H5, type Intent, ProgressBar, Slider, Switch } from "@blueprintjs/core"; +import { Card, H5, type Intent, ProgressBar, Slider, Switch } from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; import { IntentSelect } from "./common/intentSelect"; @@ -62,7 +62,9 @@ export class ProgressExample extends React.PureComponent - + + + ); } diff --git a/packages/docs-app/src/examples/core-examples/radioExample.tsx b/packages/docs-app/src/examples/core-examples/radioExample.tsx index 787fe5948f..65dcac0853 100644 --- a/packages/docs-app/src/examples/core-examples/radioExample.tsx +++ b/packages/docs-app/src/examples/core-examples/radioExample.tsx @@ -16,7 +16,7 @@ import * as React from "react"; -import { Radio, RadioGroup } from "@blueprintjs/core"; +import { Card, Radio, RadioGroup } from "@blueprintjs/core"; import { Example, handleStringChange } from "@blueprintjs/docs-theme"; import { CheckboxExample } from "./checkboxExample"; @@ -28,17 +28,19 @@ export class RadioExample extends CheckboxExample { protected renderExample() { return ( - - - - - + + + + + + + ); } diff --git a/packages/docs-app/src/examples/core-examples/sectionExample.tsx b/packages/docs-app/src/examples/core-examples/sectionExample.tsx index 997e9c2921..8673dbc08b 100644 --- a/packages/docs-app/src/examples/core-examples/sectionExample.tsx +++ b/packages/docs-app/src/examples/core-examples/sectionExample.tsx @@ -86,52 +86,57 @@ export class SectionExample extends React.PureComponent -
Section Props
- - - - - - - Elevation - +
Section Props
+ + + + + + + + + + +
+
Collapse Props
+ + + - - -
Collapse Props
- - - - -
Children
- - -
SectionCard Props
- +
+ +
+
Children
+ + +
SectionCard Props
+ +
); @@ -163,7 +168,7 @@ export class SectionExample extends React.PureComponent +
- - - + + + + + ); } diff --git a/packages/docs-app/src/examples/core-examples/spinnerExample.tsx b/packages/docs-app/src/examples/core-examples/spinnerExample.tsx index b3c56b55b6..bfabf11efa 100644 --- a/packages/docs-app/src/examples/core-examples/spinnerExample.tsx +++ b/packages/docs-app/src/examples/core-examples/spinnerExample.tsx @@ -16,7 +16,7 @@ import * as React from "react"; -import { H5, type Intent, Label, Slider, Spinner, SpinnerSize, Switch } from "@blueprintjs/core"; +import { Card, H5, type Intent, Label, Slider, Spinner, SpinnerSize, Switch } from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; import { IntentSelect } from "./common/intentSelect"; @@ -43,12 +43,14 @@ export class SpinnerExample extends React.PureComponent - + + + ); } diff --git a/packages/docs-app/src/examples/core-examples/switchExample.tsx b/packages/docs-app/src/examples/core-examples/switchExample.tsx index e8b4e30190..654e2c8fce 100644 --- a/packages/docs-app/src/examples/core-examples/switchExample.tsx +++ b/packages/docs-app/src/examples/core-examples/switchExample.tsx @@ -16,7 +16,7 @@ import * as React from "react"; -import { Code, FormGroup, Switch } from "@blueprintjs/core"; +import { Card, Code, FormGroup, Switch } from "@blueprintjs/core"; import { CheckboxExample } from "./checkboxExample"; @@ -24,7 +24,7 @@ export class SwitchExample extends CheckboxExample { // See CheckboxExample for options protected renderExample() { return ( - <> + Enabled} /> Public} /> @@ -34,7 +34,7 @@ export class SwitchExample extends CheckboxExample { This example uses labelElement to demonstrate JSX labels. - + ); } } diff --git a/packages/docs-app/src/examples/core-examples/tabsExample.tsx b/packages/docs-app/src/examples/core-examples/tabsExample.tsx index 4480a12b37..ba12a3219f 100644 --- a/packages/docs-app/src/examples/core-examples/tabsExample.tsx +++ b/packages/docs-app/src/examples/core-examples/tabsExample.tsx @@ -18,8 +18,8 @@ import * as React from "react"; import { Alignment, + Card, Classes, - Divider, H4, H5, InputGroup, @@ -79,103 +79,114 @@ export class TabsExample extends React.PureComponent -
Appearance props
- - -
Behavior props
- -
Tab content props
- - - - - - - +
+
Appearance props
+ + +
Behavior props
- +
+
+
Tab content props
+ + + + + + + + + +
); const NAVBAR_PARENT_ID = "navbar"; return ( - -

Tabs with passed panels, uncontrolled mode

- - - } /> - } - tagContent={this.state.showTags ? 10 : undefined} - tagProps={{ round: this.state.useRoundTags }} - /> - } panelClassName="ember-panel" /> - } /> - - - - -

Tabs with separately rendered panels, controlled mode

- -
- - - - Page: {this.state.navbarTabId} - - - - - - - - - - - -

Example panel: {this.state.navbarTabId}

-

The current panel is: "{this.state.navbarTabId}"

- - } - /> -
+ + +
Tabs with passed panels, uncontrolled mode
+ + + } /> + } + tagContent={this.state.showTags ? 10 : undefined} + tagProps={{ round: this.state.useRoundTags }} + /> + } panelClassName="ember-panel" /> + } /> + + + +
+ +
Tabs with separately rendered panels, controlled mode
+ +
+ + + + Page: {this.state.navbarTabId} + + + + + + + + + + + +

Example panel: {this.state.navbarTabId}

+

The current panel is: "{this.state.navbarTabId}"

+ + } + /> +
+
); } diff --git a/packages/docs-app/src/examples/core-examples/treeExample.tsx b/packages/docs-app/src/examples/core-examples/treeExample.tsx index 7b408f599a..5fe78d368c 100644 --- a/packages/docs-app/src/examples/core-examples/treeExample.tsx +++ b/packages/docs-app/src/examples/core-examples/treeExample.tsx @@ -17,7 +17,18 @@ import cloneDeep from "lodash/cloneDeep"; import * as React from "react"; -import { Classes, ContextMenu, H5, Icon, Intent, Switch, Tooltip, Tree, type TreeNodeInfo } from "@blueprintjs/core"; +import { + Card, + Classes, + ContextMenu, + H5, + Icon, + Intent, + Switch, + Tooltip, + Tree, + type TreeNodeInfo, +} from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; type NodePath = number[]; @@ -102,14 +113,15 @@ export const TreeExample: React.FC = props => { return ( - + + + ); }; diff --git a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx index 19d4596ef0..cd33016fc5 100644 --- a/packages/docs-app/src/examples/select-examples/omnibarExample.tsx +++ b/packages/docs-app/src/examples/select-examples/omnibarExample.tsx @@ -12,10 +12,12 @@ * limitations under the License. */ +import classNames from "classnames"; import * as React from "react"; import { Button, + Classes, H5, HotkeysTarget2, KeyComboTag, @@ -37,6 +39,8 @@ import { TOP_100_FILMS, } from "@blueprintjs/select/examples"; +import type { BlueprintExampleData } from "../../tags/types"; + export interface OmnibarExampleState { allowCreate: boolean; isOpen: boolean; @@ -44,7 +48,7 @@ export interface OmnibarExampleState { resetOnSelect: boolean; } -export class OmnibarExample extends React.PureComponent { +export class OmnibarExample extends React.PureComponent, OmnibarExampleState> { public state: OmnibarExampleState = { allowCreate: false, isOpen: false, @@ -71,6 +75,7 @@ export class OmnibarExample extends React.PureComponent {...this.state} + className={classNames({ [Classes.DARK]: useDarkTheme })} createNewItemFromQuery={maybeCreateNewItemFromQuery} createNewItemRenderer={maybeCreateNewItemRenderer} itemPredicate={filterFilm} @@ -105,7 +111,11 @@ export class OmnibarExample extends React.PureComponent - +
); diff --git a/packages/docs-app/src/styles/_examples.scss b/packages/docs-app/src/styles/_examples.scss index c4698a08aa..b57e745ae4 100644 --- a/packages/docs-app/src/styles/_examples.scss +++ b/packages/docs-app/src/styles/_examples.scss @@ -28,6 +28,10 @@ margin-bottom: $pt-grid-size; } +.docs-example .#{$ns}-form-group:last-child { + margin-bottom: 0; +} + // Example-specific customizations // @@ -152,8 +156,10 @@ } #{example("FormGroup")} { - .docs-example { + .docs-example .#{$ns}-card { + display: flex; flex-direction: column; + gap: $pt-grid-size; } // this example has a "fill" prop option, which doesn't work nicely with the default margin @@ -167,6 +173,15 @@ } } +#{example("Slider")} { + .docs-example .#{$ns}-card { + display: flex; + justify-content: space-around; + padding-inline: $pt-grid-size * 4; + width: 100%; + } +} + #{example("Menu")} .#{$ns}-menu { max-width: 280px; } @@ -221,6 +236,10 @@ &.docs-context-menu-open { box-shadow: $pt-elevation-shadow-2, 0 0 0 4px $orange4; } + + .#{$ns}-dark & { + background-color: $blue2; + } } } @@ -454,6 +473,10 @@ $docs-hotkey-piano-height: 510px; box-shadow: $pt-elevation-shadow-0; height: 240px; width: 300px; + + .#{$ns}-dark & { + box-shadow: border-shadow(0.2, $white); + } } .docs-panel-stack-contents-example { @@ -648,8 +671,10 @@ $docs-hotkey-piano-height: 510px; } #{example("Tabs")} { - .docs-example .#{$ns}-heading { - margin-bottom: 0; + .docs-example .#{$ns}-card { + display: flex; + flex-direction: column; + gap: $pt-grid-size; } } diff --git a/packages/docs-theme/src/components/documentation.tsx b/packages/docs-theme/src/components/documentation.tsx index 29040cb71c..74ee351d1a 100644 --- a/packages/docs-theme/src/components/documentation.tsx +++ b/packages/docs-theme/src/components/documentation.tsx @@ -178,6 +178,8 @@ export class Documentation extends React.PureComponent diff --git a/packages/docs-theme/src/components/navigator.tsx b/packages/docs-theme/src/components/navigator.tsx index c6e69faf93..acc746a8db 100644 --- a/packages/docs-theme/src/components/navigator.tsx +++ b/packages/docs-theme/src/components/navigator.tsx @@ -15,6 +15,7 @@ */ import type { HeadingNode, PageNode } from "@documentalist/client"; +import classNames from "classnames"; import { filter } from "fuzzaldrin-plus"; import * as React from "react"; @@ -39,6 +40,11 @@ export interface NavigatorProps { * updating browser `location` directly. */ onClose: () => void; + + /** + * Whether to use dark theme. + */ + useDarkTheme?: boolean; } export interface NavigationSection { @@ -70,7 +76,7 @@ export class Navigator extends React.PureComponent { return ( - className="docs-navigator-menu" + className={classNames("docs-navigator-menu", { [Classes.DARK]: this.props.useDarkTheme })} inputProps={{ placeholder: "Search documentation pages and sections..." }} itemListPredicate={this.filterMatches} isOpen={this.props.isOpen} diff --git a/packages/docs-theme/src/styles/_variables.scss b/packages/docs-theme/src/styles/_variables.scss index 7af6dee348..37974a0cd8 100644 --- a/packages/docs-theme/src/styles/_variables.scss +++ b/packages/docs-theme/src/styles/_variables.scss @@ -11,7 +11,7 @@ $container-padding: $pt-grid-size * 0.5; $sidebar-width: $pt-grid-size * 27; $sidebar-padding: $pt-grid-size * 1.5; $sidebar-background-color: $white; -$dark-sidebar-background-color: $dark-gray4; +$dark-sidebar-background-color: $dark-gray3; $content-width: $container-width - $sidebar-width; $content-padding: $pt-grid-size * 2; diff --git a/packages/select/src/components/omnibar/_omnibar.scss b/packages/select/src/components/omnibar/_omnibar.scss index 73872b6893..fa8ff97ace 100644 --- a/packages/select/src/components/omnibar/_omnibar.scss +++ b/packages/select/src/components/omnibar/_omnibar.scss @@ -57,7 +57,7 @@ $omnibar-input-height: $pt-grid-size * 4 !default; .#{$ns}-dark &, &.#{$ns}-dark { - background-color: $dark-gray4; + background-color: $dark-gray3; box-shadow: $pt-dark-elevation-shadow-4; } } From d82cc7097bb47a8e2cbd950fb6d20d09b7fcfa7f Mon Sep 17 00:00:00 2001 From: Gregory Douglas Date: Mon, 18 Nov 2024 16:20:57 -0500 Subject: [PATCH 038/121] chore: Publish new release - @blueprintjs/core@5.15.0 - @blueprintjs/datetime@5.3.16 - @blueprintjs/datetime2@2.3.16 - @blueprintjs/demo-app@5.15.0 - @blueprintjs/docs-app@5.15.0 - @blueprintjs/docs-data@5.15.0 - @blueprintjs/docs-theme@5.3.16 - @blueprintjs/landing-app@5.15.0 - @blueprintjs/popover2@2.1.15 - @blueprintjs/select@5.3.4 - @blueprintjs/table-dev-app@5.2.6 - @blueprintjs/table@5.2.6 --- packages/core/package.json | 2 +- packages/datetime/package.json | 2 +- packages/datetime2/package.json | 2 +- packages/demo-app/package.json | 2 +- packages/docs-app/package.json | 2 +- packages/docs-data/package.json | 2 +- packages/docs-theme/package.json | 2 +- packages/landing-app/package.json | 2 +- packages/popover2/package.json | 2 +- packages/select/package.json | 2 +- packages/table-dev-app/package.json | 2 +- packages/table/package.json | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 0c5b64ecc5..189806afb7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/core", - "version": "5.14.2", + "version": "5.15.0", "description": "Core styles & components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime/package.json b/packages/datetime/package.json index 9fded71858..d8f46f9624 100644 --- a/packages/datetime/package.json +++ b/packages/datetime/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime", - "version": "5.3.15", + "version": "5.3.16", "description": "Components for interacting with dates and times", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/datetime2/package.json b/packages/datetime2/package.json index 633ec01984..9de72dbd98 100644 --- a/packages/datetime2/package.json +++ b/packages/datetime2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/datetime2", - "version": "2.3.15", + "version": "2.3.16", "description": "Re-exports of @blueprintjs/datetime APIs", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index 97a143ef27..9f62ec28bb 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/demo-app", - "version": "5.14.3", + "version": "5.15.0", "description": "Blueprint Demo App", "private": true, "scripts": { diff --git a/packages/docs-app/package.json b/packages/docs-app/package.json index ca82437dbe..ded1fe2635 100644 --- a/packages/docs-app/package.json +++ b/packages/docs-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-app", - "version": "5.14.3", + "version": "5.15.0", "description": "Blueprint Documentation Site", "private": true, "scripts": { diff --git a/packages/docs-data/package.json b/packages/docs-data/package.json index d2b0bdaf4b..b8d18570eb 100644 --- a/packages/docs-data/package.json +++ b/packages/docs-data/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-data", - "version": "5.14.3", + "version": "5.15.0", "main": "src/index.js", "types": "src/index.d.ts", "private": true, diff --git a/packages/docs-theme/package.json b/packages/docs-theme/package.json index da8067dbea..d4d896864b 100644 --- a/packages/docs-theme/package.json +++ b/packages/docs-theme/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/docs-theme", - "version": "5.3.15", + "version": "5.3.16", "description": "Blueprint theme for documentalist", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/landing-app/package.json b/packages/landing-app/package.json index 220d5b89aa..e831ba3d4f 100644 --- a/packages/landing-app/package.json +++ b/packages/landing-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/landing-app", - "version": "5.14.2", + "version": "5.15.0", "description": "Blueprint landing page", "private": true, "scripts": { diff --git a/packages/popover2/package.json b/packages/popover2/package.json index 9224025610..e7529b5564 100644 --- a/packages/popover2/package.json +++ b/packages/popover2/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/popover2", - "version": "2.1.14", + "version": "2.1.15", "description": "Re-exports of popover-related components from @blueprintjs/core", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/select/package.json b/packages/select/package.json index 71886514cb..8e36a6f708 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/select", - "version": "5.3.3", + "version": "5.3.4", "description": "Components related to selecting items from a list", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/table-dev-app/package.json b/packages/table-dev-app/package.json index c29bbf42fc..7f19d7be9b 100644 --- a/packages/table-dev-app/package.json +++ b/packages/table-dev-app/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table-dev-app", - "version": "5.2.5", + "version": "5.2.6", "description": "Dev application for @blueprintjs/table", "private": true, "scripts": { diff --git a/packages/table/package.json b/packages/table/package.json index 0108ec26b0..8721224428 100644 --- a/packages/table/package.json +++ b/packages/table/package.json @@ -1,6 +1,6 @@ { "name": "@blueprintjs/table", - "version": "5.2.5", + "version": "5.2.6", "description": "Scalable interactive table component", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From f22b3d8475fdea4154dd97e60bac727c78188c3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:34:41 -0500 Subject: [PATCH 039/121] chore(deps): bump cross-spawn from 7.0.3 to 7.0.5 (#7077) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 59fee94b00..99129731a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5670,26 +5670,26 @@ __metadata: linkType: hard "cross-spawn@npm:^6.0.5": - version: 6.0.5 - resolution: "cross-spawn@npm:6.0.5" + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" dependencies: nice-try: "npm:^1.0.4" path-key: "npm:^2.0.1" semver: "npm:^5.5.0" shebang-command: "npm:^1.2.0" which: "npm:^1.2.9" - checksum: e05544722e9d7189b4292c66e42b7abeb21db0d07c91b785f4ae5fefceb1f89e626da2703744657b287e86dcd4af57b54567cef75159957ff7a8a761d9055012 + checksum: bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d languageName: node linkType: hard "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + checksum: 053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 languageName: node linkType: hard From 7e5903dca42c763d3654e66691ffbd5702c7a558 Mon Sep 17 00:00:00 2001 From: "Blake V." <87083504+bvandercar-vt@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:38:41 -0700 Subject: [PATCH 040/121] feat(a11y): give RadioGroup aria "radiogroup" role (#6940) --- packages/core/src/common/props.ts | 1 + .../core/src/components/forms/radioGroup.tsx | 34 +++++++++++++++---- .../segmented-control/segmentedControl.tsx | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/core/src/common/props.ts b/packages/core/src/common/props.ts index 36ad7a8ebf..492966e0ff 100644 --- a/packages/core/src/common/props.ts +++ b/packages/core/src/common/props.ts @@ -155,6 +155,7 @@ const INVALID_PROPS = [ "rightElement", "rightIcon", "round", + "selectedValue", "size", "small", "tagName", diff --git a/packages/core/src/components/forms/radioGroup.tsx b/packages/core/src/components/forms/radioGroup.tsx index 5ea46d9e40..068d79e827 100644 --- a/packages/core/src/components/forms/radioGroup.tsx +++ b/packages/core/src/components/forms/radioGroup.tsx @@ -17,17 +17,27 @@ import classNames from "classnames"; import * as React from "react"; -import { AbstractPureComponent, Classes, DISPLAYNAME_PREFIX, type OptionProps, type Props } from "../../common"; +import { + AbstractPureComponent, + Classes, + DISPLAYNAME_PREFIX, + type HTMLDivProps, + type OptionProps, + type Props, + removeNonHTMLProps, +} from "../../common"; import * as Errors from "../../common/errors"; -import { isElementOfType } from "../../common/utils"; +import { isElementOfType, uniqueId } from "../../common/utils"; import { RadioCard } from "../control-card/radioCard"; import type { ControlProps } from "./controlProps"; import { Radio, type RadioProps } from "./controls"; -export interface RadioGroupProps extends Props { +export interface RadioGroupProps extends Props, HTMLDivProps { /** * Radio elements. This prop is mutually exclusive with `options`. + * If passing custom children, ensure options have `role="radio"` or + * `input` with `type="radio"`. */ children?: React.ReactNode; @@ -86,11 +96,21 @@ export class RadioGroup extends AbstractPureComponent { private autoGroupName = nextName(); public render() { - const { label } = this.props; + const { disabled, label, options, className, children, name, onChange, ...htmlProps } = this.props; + const labelId = uniqueId("label"); return ( -
- {label == null ? null : } - {Array.isArray(this.props.options) ? this.renderOptions() : this.renderChildren()} +
+ {label && ( + + )} + {Array.isArray(options) ? this.renderOptions() : this.renderChildren()}
); } diff --git a/packages/core/src/components/segmented-control/segmentedControl.tsx b/packages/core/src/components/segmented-control/segmentedControl.tsx index ee48c4d4bb..a893f1d08f 100644 --- a/packages/core/src/components/segmented-control/segmentedControl.tsx +++ b/packages/core/src/components/segmented-control/segmentedControl.tsx @@ -122,7 +122,7 @@ export const SegmentedControl: React.FC = React.forwardRe if (role === "radiogroup") { // in a `radiogroup`, arrow keys select next item, not tab key. const direction = Utils.getArrowKeyDirection(e, ["ArrowLeft", "ArrowUp"], ["ArrowRight", "ArrowDown"]); - const { current: outerElement } = outerRef; + const outerElement = outerRef.current; if (direction === undefined || !outerElement) return; const focusedElement = Utils.getActiveElement(outerElement)?.closest("button"); From 46d824a03f3a3607adc22607ba80c9e8f1b36bba Mon Sep 17 00:00:00 2001 From: Rowan <37123234+Browun@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:37:41 +0000 Subject: [PATCH 041/121] New MMDP icons (#7067) Co-authored-by: svc-changelog --- .../changelog/@unreleased/pr-7067.v2.yml | 5 ++++ packages/icons/icons.json | 28 +++++++++++++++++++ resources/icons/16px/th-add.svg | 4 +++ resources/icons/16px/th-list-add.svg | 3 ++ resources/icons/16px/th-virtual-add.svg | 3 ++ resources/icons/16px/th-virtual.svg | 4 +++ resources/icons/20px/th-add.svg | 4 +++ resources/icons/20px/th-list-add.svg | 4 +++ resources/icons/20px/th-virtual-add.svg | 5 ++++ resources/icons/20px/th-virtual.svg | 4 +++ 10 files changed, 64 insertions(+) create mode 100644 packages/icons/changelog/@unreleased/pr-7067.v2.yml create mode 100644 resources/icons/16px/th-add.svg create mode 100644 resources/icons/16px/th-list-add.svg create mode 100644 resources/icons/16px/th-virtual-add.svg create mode 100644 resources/icons/16px/th-virtual.svg create mode 100644 resources/icons/20px/th-add.svg create mode 100644 resources/icons/20px/th-list-add.svg create mode 100644 resources/icons/20px/th-virtual-add.svg create mode 100644 resources/icons/20px/th-virtual.svg diff --git a/packages/icons/changelog/@unreleased/pr-7067.v2.yml b/packages/icons/changelog/@unreleased/pr-7067.v2.yml new file mode 100644 index 0000000000..c763016a00 --- /dev/null +++ b/packages/icons/changelog/@unreleased/pr-7067.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: New MMDP icons + links: + - https://github.com/palantir/blueprint/pull/7067 diff --git a/packages/icons/icons.json b/packages/icons/icons.json index bd3f12eef0..a76a997d37 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4541,5 +4541,33 @@ "tags": "arrow, box, object, counter-clockwise, ccw, left", "group": "interface", "codepoint": 62345 + }, + { + "displayName": "Table: add", + "iconName": "th-add", + "tags": "index, rows, columns, agenda, list, spreadsheet, table, sheet, entry, new, create, plus", + "group": "table", + "codepoint": 62346 + }, + { + "displayName": "Table: list add", + "iconName": "th-list-add", + "tags": "index, rows, columns, agenda, list, order, series, spreadsheet, table, sheet, entry, new, create, plus", + "group": "table", + "codepoint": 62347 + }, + { + "displayName": "Table: virtual", + "iconName": "th-virtual", + "tags": "table, index, row, rows, columns, agenda, list, order, series, spreadsheet, table, sheet, entry, provenance", + "group": "table", + "codepoint": 62348 + }, + { + "displayName": "Table: virtual add", + "iconName": "th-virtual-add", + "tags": "table, index, row, rows, columns, agenda, list, order, series, spreadsheet, table, sheet, entry, new, create, plus, provenance", + "group": "table", + "codepoint": 62349 } ] diff --git a/resources/icons/16px/th-add.svg b/resources/icons/16px/th-add.svg new file mode 100644 index 0000000000..b0b80a07cb --- /dev/null +++ b/resources/icons/16px/th-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/16px/th-list-add.svg b/resources/icons/16px/th-list-add.svg new file mode 100644 index 0000000000..186a85a092 --- /dev/null +++ b/resources/icons/16px/th-list-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/16px/th-virtual-add.svg b/resources/icons/16px/th-virtual-add.svg new file mode 100644 index 0000000000..36078896ab --- /dev/null +++ b/resources/icons/16px/th-virtual-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/16px/th-virtual.svg b/resources/icons/16px/th-virtual.svg new file mode 100644 index 0000000000..e412ce6963 --- /dev/null +++ b/resources/icons/16px/th-virtual.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/th-add.svg b/resources/icons/20px/th-add.svg new file mode 100644 index 0000000000..6e0d84abe0 --- /dev/null +++ b/resources/icons/20px/th-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/th-list-add.svg b/resources/icons/20px/th-list-add.svg new file mode 100644 index 0000000000..85f4523004 --- /dev/null +++ b/resources/icons/20px/th-list-add.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/th-virtual-add.svg b/resources/icons/20px/th-virtual-add.svg new file mode 100644 index 0000000000..6f8e8bb607 --- /dev/null +++ b/resources/icons/20px/th-virtual-add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/20px/th-virtual.svg b/resources/icons/20px/th-virtual.svg new file mode 100644 index 0000000000..98dc72bbd5 --- /dev/null +++ b/resources/icons/20px/th-virtual.svg @@ -0,0 +1,4 @@ + + + + From 1d1579d5134212ddf9b6bd97fe607698e55fcbec Mon Sep 17 00:00:00 2001 From: Dan Kim Date: Thu, 21 Nov 2024 18:35:23 -0500 Subject: [PATCH 042/121] Add filter-sort-asc and filter-sort-desc Icons (#7083) Co-authored-by: svc-changelog --- .../icons/changelog/@unreleased/pr-7083.v2.yml | 5 +++++ packages/icons/icons.json | 14 ++++++++++++++ resources/icons/16px/filter-sort-asc.svg | 4 ++++ resources/icons/16px/filter-sort-desc.svg | 4 ++++ resources/icons/20px/filter-sort-asc.svg | 4 ++++ resources/icons/20px/filter-sort-desc.svg | 4 ++++ 6 files changed, 35 insertions(+) create mode 100644 packages/icons/changelog/@unreleased/pr-7083.v2.yml create mode 100644 resources/icons/16px/filter-sort-asc.svg create mode 100644 resources/icons/16px/filter-sort-desc.svg create mode 100644 resources/icons/20px/filter-sort-asc.svg create mode 100644 resources/icons/20px/filter-sort-desc.svg diff --git a/packages/icons/changelog/@unreleased/pr-7083.v2.yml b/packages/icons/changelog/@unreleased/pr-7083.v2.yml new file mode 100644 index 0000000000..249827c8e4 --- /dev/null +++ b/packages/icons/changelog/@unreleased/pr-7083.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: Add filter-sort-asc and filter-sort-desc Icons + links: + - https://github.com/palantir/blueprint/pull/7083 diff --git a/packages/icons/icons.json b/packages/icons/icons.json index a76a997d37..b00900ae43 100644 --- a/packages/icons/icons.json +++ b/packages/icons/icons.json @@ -4569,5 +4569,19 @@ "tags": "table, index, row, rows, columns, agenda, list, order, series, spreadsheet, table, sheet, entry, new, create, plus, provenance", "group": "table", "codepoint": 62349 + }, + { + "displayName": "Filter sort: ascending", + "iconName": "filter-sort-asc", + "tags": "sort, filter, ascending", + "group": "interface", + "codepoint": 62350 + }, + { + "displayName": "Filter sort: descending", + "iconName": "filter-sort-desc", + "tags": "sort, filter, descending", + "group": "interface", + "codepoint": 62351 } ] diff --git a/resources/icons/16px/filter-sort-asc.svg b/resources/icons/16px/filter-sort-asc.svg new file mode 100644 index 0000000000..18b1749de9 --- /dev/null +++ b/resources/icons/16px/filter-sort-asc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/16px/filter-sort-desc.svg b/resources/icons/16px/filter-sort-desc.svg new file mode 100644 index 0000000000..c68f4fe19d --- /dev/null +++ b/resources/icons/16px/filter-sort-desc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/filter-sort-asc.svg b/resources/icons/20px/filter-sort-asc.svg new file mode 100644 index 0000000000..3ba239b79e --- /dev/null +++ b/resources/icons/20px/filter-sort-asc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/icons/20px/filter-sort-desc.svg b/resources/icons/20px/filter-sort-desc.svg new file mode 100644 index 0000000000..38444d8e48 --- /dev/null +++ b/resources/icons/20px/filter-sort-desc.svg @@ -0,0 +1,4 @@ + + + + From dc7c104c70ec292533becefd0699618537dab3ad Mon Sep 17 00:00:00 2001 From: Jonah Scheinerman Date: Fri, 22 Nov 2024 09:41:55 -0500 Subject: [PATCH 043/121] feat(DateRangeInput3) Add keyboard accessibility (#7080) --- .../date-range-input3/dateRangeInput3.tsx | 95 +++++- .../date-range-input3/dateRangeInputUilts.ts | 52 +++ .../contiguousDayRangePicker.tsx | 6 +- .../test/components/dateRangeInput3Tests.tsx | 310 +++++++++++++++++- 4 files changed, 454 insertions(+), 9 deletions(-) create mode 100644 packages/datetime2/src/components/date-range-input3/dateRangeInputUilts.ts diff --git a/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx b/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx index 655112a80b..653d7edc39 100644 --- a/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx +++ b/packages/datetime2/src/components/date-range-input3/dateRangeInput3.tsx @@ -52,6 +52,13 @@ import type { DateRangeInput3PropsWithDefaults, } from "./dateRangeInput3Props"; import type { DateRangeInput3State } from "./dateRangeInput3State"; +import { + clampDate, + getTodayAtMidnight, + isEntireInputSelected, + shiftDateByArrowKey, + shiftDateByDays, +} from "./dateRangeInputUilts"; export type { DateRangeInput3Props }; @@ -465,7 +472,7 @@ export class DateRangeInput3 extends DateFnsLocalizedComponent; - this.handleInputKeyDown(e); + this.handleInputKeyDown(e, boundary); inputProps?.onKeyDown?.(e); break; case "mousedown": @@ -481,7 +488,9 @@ export class DateRangeInput3 extends DateFnsLocalizedComponent) => { + private handleInputKeyDown = (e: React.KeyboardEvent, boundary: Boundary) => { + const isArrowKeyPresssed = + e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"; const isTabPressed = e.key === "Tab"; const isEnterPressed = e.key === "Enter"; const isEscapeKeyPressed = e.key === "Escape"; @@ -489,6 +498,11 @@ export class DateRangeInput3 extends DateFnsLocalizedComponent, boundary: Boundary) => { + const { minDate, maxDate } = this.props; + const { isOpen } = this.state; + const inputElement = boundary === Boundary.START ? this.startInputElement : this.endInputElement; + + if (!isOpen || !isEntireInputSelected(inputElement)) { + return; + } + + const shiftedDate = + this.getNextDateForArrowKeyNavigation(e.key, boundary) ?? + this.getDefaultDateForArrowKeyNavigation(e.key, boundary); + + if (shiftedDate == null) { + return; + } + + // We've commited to moving the selection, prevent the default arrow key interactions + e.preventDefault(); + + const clampedDate = clampDate(shiftedDate, minDate, maxDate); + const { keys } = this.getStateKeysAndValuesForBoundary(boundary); + const nextState: Partial = { + [keys.inputString]: this.formatDate(clampedDate), + shouldSelectAfterUpdate: true, + }; + + if (!this.isControlled()) { + nextState[keys.selectedValue] = clampedDate; + } + + this.props.onChange?.(this.getDateRangeForCallback(clampedDate, boundary)); + this.setState(nextState); + }; + + private getNextDateForArrowKeyNavigation(arrowKey: string, boundary: Boundary) { + const { allowSingleDayRange } = this.props; + const [selectedStart, selectedEnd] = this.getSelectedRange(); + const initialDate = boundary === Boundary.START ? selectedStart : selectedEnd; + if (initialDate == null) { + return undefined; + } + + const relativeDate = shiftDateByArrowKey(initialDate, arrowKey); + + // Ensure that we don't move onto a single day range selection if that is disallowed + const adjustedStart = + selectedStart == null || allowSingleDayRange ? selectedStart : shiftDateByDays(selectedStart, 1); + const adjustedEnd = selectedEnd == null || allowSingleDayRange ? selectedEnd : shiftDateByDays(selectedEnd, -1); + + return boundary === Boundary.START + ? clampDate(relativeDate, undefined, adjustedEnd) + : clampDate(relativeDate, adjustedStart, undefined); + } + + private getDefaultDateForArrowKeyNavigation(arrowKey: string, boundary: Boundary) { + const [selectedStart, selectedEnd] = this.getSelectedRange(); + const otherBoundary = boundary === Boundary.START ? selectedEnd : selectedStart; + + if (otherBoundary == null) { + return getTodayAtMidnight(); + } + + const isForwardArrowKey = arrowKey === "ArrowRight" || arrowKey === "ArrowDown"; + // If the arrow key direction is in the same direction as the boundary, then moving that way will not create an + // overlapping date range + if (isForwardArrowKey === (boundary === Boundary.END)) { + return shiftDateByArrowKey(otherBoundary, arrowKey); + } + + return undefined; + } + private handleInputMouseDown = () => { // clicking in the field constitutes an explicit focus change. we update // the flag on "mousedown" instead of on "click", because it needs to be @@ -692,7 +779,7 @@ export class DateRangeInput3 extends DateFnsLocalizedComponent maxDate) { + result = maxDate; + } + return result; +} + +export function isEntireInputSelected(element: HTMLInputElement | null) { + if (element == null) { + return false; + } + + return element.selectionStart === 0 && element.selectionEnd === element.value.length; +} diff --git a/packages/datetime2/src/components/date-range-picker3/contiguousDayRangePicker.tsx b/packages/datetime2/src/components/date-range-picker3/contiguousDayRangePicker.tsx index e50cfd1384..5f6e90897a 100644 --- a/packages/datetime2/src/components/date-range-picker3/contiguousDayRangePicker.tsx +++ b/packages/datetime2/src/components/date-range-picker3/contiguousDayRangePicker.tsx @@ -147,6 +147,7 @@ function useContiguousCalendarViews( const nextRangeStart = MonthAndYear.fromDate(selectedRange[0]); const nextRangeEnd = MonthAndYear.fromDate(selectedRange[1]); + const hasSelectionEndChanged = prevSelectedRange.current[0]?.valueOf() === selectedRange[0]?.valueOf(); if (nextRangeStart == null && nextRangeEnd != null) { // Only end date selected. @@ -175,8 +176,11 @@ function useContiguousCalendarViews( } else { newDisplayMonth = nextRangeStart; } + } else if (hasSelectionEndChanged) { + // If the selection end has changed, adjust the view to show the new end date + newDisplayMonth = singleMonthOnly ? nextRangeEnd : nextRangeEnd.getPreviousMonth(); } else { - // Different start and end date months, adjust display months. + // Otherwise, the selection start must have changed, show that newDisplayMonth = nextRangeStart; } } diff --git a/packages/datetime2/test/components/dateRangeInput3Tests.tsx b/packages/datetime2/test/components/dateRangeInput3Tests.tsx index c28e1c03ef..67331a0e24 100644 --- a/packages/datetime2/test/components/dateRangeInput3Tests.tsx +++ b/packages/datetime2/test/components/dateRangeInput3Tests.tsx @@ -93,18 +93,19 @@ describe("", () => { } }); + const YEAR = 2022; const START_DAY = 22; - const START_DATE = new Date(2022, Months.JANUARY, START_DAY); + const START_DATE = new Date(YEAR, Months.JANUARY, START_DAY); const START_STR = DATE_FORMAT.formatDate(START_DATE); const END_DAY = 24; - const END_DATE = new Date(2022, Months.JANUARY, END_DAY); + const END_DATE = new Date(YEAR, Months.JANUARY, END_DAY); const END_STR = DATE_FORMAT.formatDate(END_DATE); const DATE_RANGE = [START_DATE, END_DATE] as DateRange; - const START_DATE_2 = new Date(2022, Months.JANUARY, 1); + const START_DATE_2 = new Date(YEAR, Months.JANUARY, 1); const START_STR_2 = DATE_FORMAT.formatDate(START_DATE_2); const START_STR_2_ES_LOCALE = "1 de enero de 2022"; - const END_DATE_2 = new Date(2022, Months.JANUARY, 31); + const END_DATE_2 = new Date(YEAR, Months.JANUARY, 31); const END_STR_2 = DATE_FORMAT.formatDate(END_DATE_2); const END_STR_2_ES_LOCALE = "31 de enero de 2022"; const DATE_RANGE_2 = [START_DATE_2, END_DATE_2] as DateRange; @@ -1020,6 +1021,291 @@ describe("", () => { }); }); + describe("Arrow key navigation", () => { + it("Pressing an arrow key has no effect when the input is not fully selected", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getStartInput(root).simulate("keydown", { key: "ArrowDown" }); + getEndInput(root).simulate("keydown", { key: "ArrowDown" }); + expect(onChange.called).to.be.false; + }); + + it("Pressing the left arrow key moves the date back by a day", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedStartDate1 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY - 1)); + const expectedStartDate2 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY - 2)); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowLeft" }); + assertInputValueEquals(getStartInput(root), expectedStartDate1); + assertDateRangesEqual(onChange.getCall(0).args[0], [expectedStartDate1, END_STR]); + + getStartInput(root).simulate("keydown", { key: "ArrowLeft" }); + assertInputValueEquals(getStartInput(root), expectedStartDate2); + assertDateRangesEqual(onChange.getCall(1).args[0], [expectedStartDate2, END_STR]); + }); + + it("Pressing the right arrow key moves the date forward by a day", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedEndDate1 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, END_DAY + 1)); + const expectedEndDate2 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, END_DAY + 2)); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowRight" }); + assertInputValueEquals(getEndInput(root), expectedEndDate1); + assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, expectedEndDate1]); + + getEndInput(root).simulate("keydown", { key: "ArrowRight" }); + assertInputValueEquals(getEndInput(root), expectedEndDate2); + assertDateRangesEqual(onChange.getCall(1).args[0], [START_STR, expectedEndDate2]); + }); + + it("Pressing the up arrow key moves the date back by a week", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedStartDate1 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY - 7)); + const expectedStartDate2 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY - 14)); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getStartInput(root), expectedStartDate1); + assertDateRangesEqual(onChange.getCall(0).args[0], [expectedStartDate1, END_STR]); + + getStartInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getStartInput(root), expectedStartDate2); + assertDateRangesEqual(onChange.getCall(1).args[0], [expectedStartDate2, END_STR]); + }); + + it("Pressing the down arrow key moves the date forward by a week", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedEndDate1 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, END_DAY + 7)); + const expectedEndDate2 = DATE_FORMAT.formatDate(new Date(YEAR, Months.FEBRUARY, 7)); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getEndInput(root), expectedEndDate1); + assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, expectedEndDate1]); + + getEndInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getEndInput(root), expectedEndDate2); + assertDateRangesEqual(onChange.getCall(1).args[0], [START_STR, expectedEndDate2]); + }); + + it("Will not move past the end boundary", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedStartDate = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, END_DAY - 1)); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getStartInput(root), expectedStartDate); + assertDateRangesEqual(onChange.getCall(0).args[0], [expectedStartDate, END_STR]); + }); + + it("Will not move past the end boundary when allowSingleDayRange={true}", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getStartInput(root), END_STR); + assertDateRangesEqual(onChange.getCall(0).args[0], [END_STR, END_STR]); + }); + + it("Will not move past the start boundary", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedEndDate = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY + 1)); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getEndInput(root), expectedEndDate); + assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, expectedEndDate]); + }); + + it("Will not move past the start boundary when allowSingleDayRange={true}", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getEndInput(root), START_STR); + assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, START_STR]); + }); + + it("Will not move past the min date", () => { + const minDate = new Date(YEAR, Months.JANUARY, START_DAY - 3); + const minDateStr = DATE_FORMAT.formatDate(minDate); + + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getStartInput(root), minDateStr); + assertDateRangesEqual(onChange.getCall(0).args[0], [minDateStr, END_STR]); + }); + + it("Will not move past the max date", () => { + const maxDate = new Date(YEAR, Months.JANUARY, END_DAY + 3); + const maxDateStr = DATE_FORMAT.formatDate(maxDate); + + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getEndInput(root), maxDateStr); + assertDateRangesEqual(onChange.getCall(0).args[0], [START_STR, maxDateStr]); + }); + + it("Will select today's date by default", () => { + const onChange = sinon.spy(); + const { root } = wrap(); + + const today = DATE_FORMAT.formatDate(new Date()); + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getStartInput(root), today); + }); + + it("Will choose a reasonable end date when only the start is selected", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedEndDate = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY + 1)); + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowRight" }); + assertInputValueEquals(getEndInput(root), expectedEndDate); + }); + + it("Will choose a reasonable start date when only the end is selected", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedEndDate = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, END_DAY - 7)); + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getStartInput(root), expectedEndDate); + }); + + it("Will not make a selection when trying to move backward and only the start is selected", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getEndInput(root).simulate("focus"); + getEndInput(root).simulate("keydown", { key: "ArrowLeft" }); + getEndInput(root).simulate("keydown", { key: "ArrowUp" }); + assertInputValueEquals(getEndInput(root), ""); + expect(onChange.called).to.be.false; + }); + + it("Will not make a selection when trying to move forward and only the end is selected", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowRight" }); + getStartInput(root).simulate("keydown", { key: "ArrowDown" }); + assertInputValueEquals(getStartInput(root), ""); + expect(onChange.called).to.be.false; + }); + }); + describe("Hovering over dates", () => { // define new constants to clarify chronological ordering of dates // TODO: rename all date constants in this file to use a similar @@ -2591,6 +2877,22 @@ describe("", () => { }); }); + describe("Arrow key navigation", () => { + it("Pressing the left arrow key moves the date back by a day", () => { + const onChange = sinon.spy(); + const { root } = wrap( + , + ); + + const expectedStartDate1 = DATE_FORMAT.formatDate(new Date(YEAR, Months.JANUARY, START_DAY - 1)); + + getStartInput(root).simulate("focus"); + getStartInput(root).simulate("keydown", { key: "ArrowLeft" }); + assertInputValueEquals(getStartInput(root), expectedStartDate1); + assertDateRangesEqual(onChange.getCall(0).args[0], [expectedStartDate1, END_STR]); + }); + }); + it("Clearing the dates in the picker invokes onChange with [null, null] and updates input fields", () => { const onChange = sinon.spy(); const value = [START_DATE, null] as DateRange; From 2f978c8954041cc0cf5aa735a72be2706941ac2c Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Fri, 22 Nov 2024 09:45:10 -0500 Subject: [PATCH 044/121] [docs] Update AlertExample (#7087) --- .../examples/core-examples/alertExample.tsx | 245 ++++++++++-------- 1 file changed, 138 insertions(+), 107 deletions(-) diff --git a/packages/docs-app/src/examples/core-examples/alertExample.tsx b/packages/docs-app/src/examples/core-examples/alertExample.tsx index 1a5446c6a2..11432404c3 100644 --- a/packages/docs-app/src/examples/core-examples/alertExample.tsx +++ b/packages/docs-app/src/examples/core-examples/alertExample.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2015 Palantir Technologies, Inc. All rights reserved. + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,130 +16,161 @@ import * as React from "react"; -import { Alert, Button, H5, Intent, OverlayToaster, Switch, type Toaster } from "@blueprintjs/core"; +import { Alert, Button, H5, Intent, OverlayToaster, Switch } from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; +import { IconNames } from "@blueprintjs/icons"; import type { BlueprintExampleData } from "../../tags/types"; -export interface AlertExampleState { +export const AlertExample: React.FC> = props => { + const [canEscapeKeyCancel, setCanEscapeKeyCancel] = React.useState(false); + const [canOutsideClickCancel, setCanOutsideClickCancel] = React.useState(false); + const [willLoad, setWillLoad] = React.useState(false); + + const options = ( + <> +
Props
+ + + + + ); + + return ( + + + + + ); +}; + +interface AlertExampleProps { canEscapeKeyCancel: boolean; canOutsideClickCancel: boolean; - isLoading: boolean; - isOpen: boolean; - isOpenError: boolean; + themeName: BlueprintExampleData["themeName"]; willLoad: boolean; } -export class AlertExample extends React.PureComponent, AlertExampleState> { - public state: AlertExampleState = { - canEscapeKeyCancel: false, - canOutsideClickCancel: false, - isLoading: false, - isOpen: false, - isOpenError: false, - willLoad: false, - }; - - private toaster: Toaster; - - private handleEscapeKeyChange = handleBooleanChange(canEscapeKeyCancel => this.setState({ canEscapeKeyCancel })); - - private handleOutsideClickChange = handleBooleanChange(click => this.setState({ canOutsideClickCancel: click })); - - private handleWillLoadChange = handleBooleanChange(click => this.setState({ willLoad: click })); - - public render() { - const { isLoading, isOpen, isOpenError, ...alertProps } = this.state; - const options = ( - <> -
Props
- - - - - ); - return ( - -
-
-

- AnchorButton -

- } - rightIcon="share" - target="_blank" - text={this.duplicateButtonText} - small={size === "small"} - large={size === "large"} - {...buttonProps} - /> -
- - ); - } - - private beginWiggling = () => { - window.clearTimeout(this.wiggleTimeoutId); - this.setState({ wiggling: true }); - this.wiggleTimeoutId = window.setTimeout(() => this.setState({ wiggling: false }), 300); - }; -} +export const ButtonPlaygroundExample: React.FC = props => { + const [active, setActive] = React.useState(false); + const [alignText, setAlignText] = React.useState(undefined); + const [disabled, setDisabled] = React.useState(false); + const [ellipsizeText, setEllipsizeText] = React.useState(false); + const [fill, setFill] = React.useState(false); + const [iconOnly, setIconOnly] = React.useState(false); + const [intent, setIntent] = React.useState(Intent.NONE); + const [loading, setLoading] = React.useState(false); + const [longText, setLongText] = React.useState(false); + const [minimal, setMinimal] = React.useState(false); + const [outlined, setOutlined] = React.useState(false); + const [size, setSize] = React.useState("regular"); + const [wiggling, setWiggling] = React.useState(false); + + const wiggleTimeoutId = React.useRef(); + + React.useEffect(() => { + return () => window.clearTimeout(wiggleTimeoutId.current); + }, []); + + const beginWiggling = React.useCallback(() => { + window.clearTimeout(wiggleTimeoutId.current); + setWiggling(true); + wiggleTimeoutId.current = window.setTimeout(() => setWiggling(false), 300); + }, []); + + const wiggleButtonText = iconOnly + ? undefined + : longText + ? "Click to trigger a whimsical wiggling animation" + : "Click to wiggle"; + + const duplicateButtonText = iconOnly + ? undefined + : longText + ? "Duplicate this web page in a new browser tab" + : "Duplicate this page"; + + const options = ( + <> +
Props
+ + + + + + + + + + + + + +
Example
+ + + + ); + + return ( + +
+

+ Button +

+
+
+

+ AnchorButton +

+ +
+
+ ); +}; From ee091b9c0f05914dd70ffd7722794f2cbab9b482 Mon Sep 17 00:00:00 2001 From: Greg Douglas Date: Mon, 25 Nov 2024 13:10:24 -0500 Subject: [PATCH 051/121] [docs] Update CalloutExample (#7093) --- .../examples/core-examples/calloutExample.tsx | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/docs-app/src/examples/core-examples/calloutExample.tsx b/packages/docs-app/src/examples/core-examples/calloutExample.tsx index a56737a068..65bcc4b9af 100644 --- a/packages/docs-app/src/examples/core-examples/calloutExample.tsx +++ b/packages/docs-app/src/examples/core-examples/calloutExample.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2017 Palantir Technologies, Inc. All rights reserved. + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,23 +26,10 @@ import { IntentSelect } from "./common/intentSelect"; export const CalloutExample: React.FC = props => { const [compact, setCompact] = React.useState(false); const [contentIndex, setContentIndex] = React.useState(0); - const [showTitle, setShowTitle] = React.useState(true); const [icon, setIcon] = React.useState(); const [intent, setIntent] = React.useState(); const [minimal, setMinimal] = React.useState(false); - - /* eslint-disable react/jsx-key */ - const children = [ - - Long-form information about the important content. This text is styled as{" "} -
"Running text", so it may contain things like headers, links, - lists, code etc. - , - "Long-form information about the important content", - - - Options - -``` +Wrap buttons in a **ButtonGroup** to arrange them together horizontally. -@## Props interface +@reactCodeExample ButtonGroupBasicExample -@interface ButtonGroupProps +@## Intent -@## Usage with popovers +Use the `intent` prop on individual buttons to convey purpose. For a consistent +visual style, it’s recommended to apply the same `intent` to all buttons within the same group. -__Button__ elements inside a __ButtonGroup__ can trivially be wrapped with a [__Popover__](#core/components/popover) to -create complex toolbars. +@reactCodeExample ButtonGroupIntentExample -@reactExample ButtonGroupPopoverExample +@## Outlined and minimal + +Most of **ButtonGroup**'s props are also supported by **Button** directly. +Setting these props on **ButtonGroup** will apply the same value to all buttons +in the group. Note that most modifiers, once enabled on the group, cannot be +overridden on child buttons (due to the cascading nature of CSS). + +@reactCodeExample ButtonGroupVariantsExample @## Flex layout -__ButtonGroup__ renders a CSS inline flex row (or column if vertical) and provides some modifer props for common -flexbox patterns: +**ButtonGroup** renders a CSS flex row (or column if `vertical` is enabled) and +includes modifier props for common flexbox patterns: -- Enable the `fill` prop on a button group to make all buttons expand equally to - fill the available space. - - Buttons will expand horizontally by default, or vertically if the `vertical` prop is enabled. - - Add the class `Classes.FIXED` to individual buttons to revert them to their initial sizes. +- Use the `fill` prop to make all buttons expand equally to fill the available space. + - Buttons will expand horizontally by default or vertically if `vertical` is enabled. + - Add the `Classes.FIXED` class to specific buttons to maintain their initial sizes. +- Alternatively, enable `fill` on specific buttons to selectively expand them while others retain their original size. -- Alternatively, enable the `fill` prop on specific buttons (instead of on the - group) to expand them equally to fill the available space while other - buttons retain their original sizes. +For precise size adjustments, use the `flex-basis` or `width` CSS properties on individual buttons. -You can adjust the specific size of a button with the `flex-basis` or `width` CSS properties. +@reactCodeExample ButtonGroupFlexExample @## Vertical layout -Buttons in a vertical group all have the same width as the widest button in the group. +Enable the `vertical` prop to stack buttons vertically. Buttons in a vertical +group automatically adjust to the width of the widest button in the group. + +Use the `alignText` prop to control text and icon alignment within the buttons. +Set it at the group level for uniform alignment or on individual buttons for specific adjustments. -Use the `alignText` prop to control icon and text alignment in the buttons. Set this prop on __ButtonGroup__ to affect -all buttons in the group, or set the prop on individual buttons directly. +@reactCodeExample ButtonGroupVerticalExample + +@## Usage with popovers + +**Button** elements inside a **ButtonGroup** can be wrapped with a +[**Popover**](#core/components/popover) to create complex toolbars. + +@reactExample ButtonGroupPopoverExample + +@## Interactive Playground + +@reactExample ButtonGroupPlaygroundExample + +@## Props interface + +@interface ButtonGroupProps @## CSS API @@ -60,6 +76,7 @@ all buttons in the group, or set the prop on individual buttons directly.
Deprecated API: use [``](#core/components/button-group) +
CSS APIs for Blueprint components are considered deprecated, as they are verbose, error-prone, and they diff --git a/packages/docs-app/src/examples/core-examples/buttonExamples.tsx b/packages/docs-app/src/examples/core-examples/buttonExamples.tsx index cff2a1db1d..d75bea8912 100644 --- a/packages/docs-app/src/examples/core-examples/buttonExamples.tsx +++ b/packages/docs-app/src/examples/core-examples/buttonExamples.tsx @@ -2,6 +2,7 @@ * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. */ +import dedent from "dedent"; import * as React from "react"; import { AnchorButton, Button, Icon, Tooltip } from "@blueprintjs/core"; @@ -17,10 +18,11 @@ export const ButtonBasicExample: React.FC = props => { }; export const ButtonIntentExample: React.FC = props => { - const code = ``; + const code = dedent` + `; return (