Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/honest-lemons-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Actualize the interface of Item component.
5 changes: 5 additions & 0 deletions .changeset/odd-wasps-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Make Panel placeSelf stretch by default.
5 changes: 5 additions & 0 deletions .changeset/perfect-dancers-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Fix Item interface for FilterPicker.
5 changes: 5 additions & 0 deletions .changeset/pretty-comics-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Add onClear callback for FilterPicker, Select, ComboBox and SearchInput.
5 changes: 5 additions & 0 deletions .changeset/pretty-turtles-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cube-dev/ui-kit": patch
---

Fix popover of FilterPicker to corretly flip on opening.
18 changes: 6 additions & 12 deletions src/components/Item.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import { ReactElement, ReactNode } from 'react';
import { ReactElement } from 'react';
import { Item, ItemProps } from 'react-stately';

import { Styles } from '../tasty';
import { CubeItemBaseProps } from './content/ItemBase/ItemBase';

export interface CubeItemProps<T> extends ItemProps<T> {
qa?: string;
description?: ReactNode;
descriptionPlacement?: 'inline' | 'block' | 'auto';
icon?: ReactNode | 'checkbox';
prefix?: ReactNode;
suffix?: ReactNode;
rightIcon?: ReactNode;
styles?: Styles;
export interface CubeItemProps<T>
extends ItemProps<T>,
Omit<CubeItemBaseProps, 'children'> {
onAction?: () => void;
wrapper?: (item: ReactElement) => ReactElement;
[key: string]: any;
}

const _Item = Item as unknown as <T>(props: CubeItemProps<T>) => ReactElement;
const _Item = Item as <T>(props: CubeItemProps<T>) => ReactElement;

export { _Item as Item };
4 changes: 4 additions & 0 deletions src/components/fields/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export interface CubeComboBoxProps<T>
allowsCustomValue?: boolean;
/** Whether the combo box is clearable using ESC keyboard button or clear button inside the input */
isClearable?: boolean;
/** Callback called when the clear button is pressed */
onClear?: () => void;
}

const PROP_STYLES = [...BASE_STYLES, ...OUTER_STYLES, ...COLOR_STYLES];
Expand Down Expand Up @@ -336,6 +338,8 @@ export const ComboBox = forwardRef(function ComboBox<T extends object>(
}
// Focus back to the input
inputRef.current?.focus();

props.onClear?.();
});

let comboBoxWidth = wrapperRef?.current?.offsetWidth;
Expand Down
33 changes: 24 additions & 9 deletions src/components/fields/FilterPicker/FilterPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
useState,
} from 'react';
import { FocusScope, Key, useKeyboard } from 'react-aria';
import { Section as BaseSection, Item, ListState } from 'react-stately';
import {
Section as BaseSection,
ListState,
Item as ReactAriaItem,
} from 'react-stately';

import { useEvent } from '../../../_internal';
import { useWarn } from '../../../_internal/hooks/use-warn';
Expand Down Expand Up @@ -119,6 +123,8 @@ export interface CubeFilterPickerProps<T>
mods?: Record<string, boolean>;
/** Whether the filter picker is clearable using a clear button in the rightIcon slot */
isClearable?: boolean;
/** Callback called when the clear button is pressed */
onClear?: () => void;
}

const PROP_STYLES = [...BASE_STYLES, ...OUTER_STYLES, ...COLOR_STYLES];
Expand Down Expand Up @@ -441,7 +447,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
if (!child || typeof child !== 'object') return;
const element = child as ReactElement;

if (element.type === Item) {
if (element.type === ReactAriaItem) {
const props = element.props as any;
const label =
props.textValue ||
Expand Down Expand Up @@ -523,7 +529,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
if (!child || typeof child !== 'object') return;
const element = child as ReactElement;

if (element.type === Item) {
if (element.type === ReactAriaItem) {
const childKey = String(element.key);
if (selectedSet.has(normalizeKeyValue(childKey))) {
const props = element.props as any;
Expand Down Expand Up @@ -698,7 +704,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
if (sectionChild && typeof sectionChild === 'object') {
const sectionElement = sectionChild as ReactElement;
if (
sectionElement.type === Item ||
sectionElement.type === ReactAriaItem ||
(sectionElement.type as any)?.displayName === 'Item'
) {
const clonedItem = cloneWithNormalizedKey(sectionElement);
Expand Down Expand Up @@ -895,7 +901,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
);
};

const [shouldUpdatePosition, setShouldUpdatePosition] = useState(false);
const [shouldUpdatePosition, setShouldUpdatePosition] = useState(true);

// The trigger is rendered as a function so we can access the dialog state
const renderTrigger = (state) => {
Expand Down Expand Up @@ -943,8 +949,15 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
});

useEffect(() => {
// Disable the update of the position while the popover is open (with a delay) to avoid jumping
setShouldUpdatePosition(!state.isOpen);
// Allow initial positioning & flipping when opening, then lock placement after transition
// Popover transition is ~120ms, give it a bit more time to finalize placement
if (state.isOpen) {
setShouldUpdatePosition(true);
const id = window.setTimeout(() => setShouldUpdatePosition(false), 160);
return () => window.clearTimeout(id);
} else {
setShouldUpdatePosition(true);
}
}, [state.isOpen]);

// Clear button logic
Expand All @@ -971,6 +984,8 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(

triggerRef?.current?.focus?.();

props.onClear?.();

return false;
});

Expand Down Expand Up @@ -1032,7 +1047,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
placement="bottom start"
styles={triggerStyles}
shouldUpdatePosition={shouldUpdatePosition}
shouldFlip={shouldFlip}
shouldFlip={shouldFlip && shouldUpdatePosition}
isDismissable={true}
shouldCloseOnInteractOutside={(el) => {
const menuTriggerEl = el.closest('[data-popover-trigger]');
Expand Down Expand Up @@ -1207,7 +1222,7 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
);
}) as unknown as (<T>(
props: CubeFilterPickerProps<T> & { ref?: ForwardedRef<HTMLElement> },
) => ReactElement) & { Item: typeof Item; Section: typeof BaseSection };
) => ReactElement) & { Item: typeof ListBox.Item; Section: typeof BaseSection };

FilterPicker.Item = ListBox.Item;

Expand Down
10 changes: 9 additions & 1 deletion src/components/fields/SearchInput/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface CubeSearchInputProps
SearchFieldProps {
/** Whether the search input is clearable using ESC keyboard button or clear button inside the input */
isClearable?: boolean;
/** Callback called when the clear button is pressed */
onClear?: () => void;
}

export const SearchInput = forwardRef(function SearchInput(
Expand All @@ -29,7 +31,7 @@ export const SearchInput = forwardRef(function SearchInput(
props = castNullableStringValue(props);
props = useProviderProps(props);

let { isClearable, validationState } = props;
let { isClearable, validationState, onClear } = props;

let inputRef = useRef(null);

Expand Down Expand Up @@ -57,6 +59,12 @@ export const SearchInput = forwardRef(function SearchInput(
type={validationState === 'invalid' ? 'clear' : 'neutral'}
theme={validationState === 'invalid' ? 'danger' : undefined}
{...ariaToCubeButtonProps(clearButtonProps)}
onPress={(e) => {
// Call the original clear functionality
clearButtonProps.onPress?.();
// Call the onClear callback
onClear?.();
}}
/>
)}
</>
Expand Down
4 changes: 4 additions & 0 deletions src/components/fields/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export interface CubeSelectBaseProps<T>
theme?: 'default' | 'special';
/** Whether the select is clearable using a clear button in the rightIcon slot */
isClearable?: boolean;
/** Callback called when the clear button is pressed */
onClear?: () => void;
}

export interface CubeSelectProps<T> extends CubeSelectBaseProps<T> {
Expand Down Expand Up @@ -351,6 +353,8 @@ function Select<T extends object>(
}
// Return focus to the trigger for better UX
triggerRef.current?.focus?.();

props.onClear?.();
});

let triggerWidth = triggerRef?.current?.offsetWidth;
Expand Down
1 change: 1 addition & 0 deletions src/components/layout/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const PanelElement = tasty({
card: '1bw',
},
flexGrow: 1,
placeSelf: 'stretch',
},
});

Expand Down
Loading