Skip to content

Commit 30ae458

Browse files
authored
Merge pull request #1098 from strapi/release/1.7.10
Release/1.7.10
2 parents a845d47 + 287f673 commit 30ae458

File tree

12 files changed

+168
-121
lines changed

12 files changed

+168
-121
lines changed

docs/public/second.png

139 KB
Loading

docs/public/third.png

93.6 KB
Loading

docs/stories/Carousel.stories.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ to another. It has an infinite loop. A set of actions is possible via Icon Butto
6666
<CarouselImage src={'/first.jpg'} alt="First" />
6767
</CarouselSlide>
6868
<CarouselSlide label="2 of 3 slides">
69-
<CarouselImage src={'/first.jpg'} alt="second" />
69+
<CarouselImage src={'/second.png'} alt="second" />
7070
</CarouselSlide>
7171
<CarouselSlide label="3 of 3 slides">
72-
<CarouselImage src={'/first.jpg'} alt="third" />
72+
<CarouselImage src={'/third.png'} alt="third" />
7373
</CarouselSlide>
7474
</CarouselInput>
7575
);

packages/primitives/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@radix-ui/react-use-callback-ref": "^1.0.0",
4141
"@radix-ui/react-use-controllable-state": "^1.0.0",
4242
"@radix-ui/react-use-layout-effect": "1.0.0",
43-
"@radix-ui/react-use-previous": "^1.0.0",
43+
"@radix-ui/react-use-previous": "^1.0.1",
4444
"@radix-ui/react-visually-hidden": "^1.0.2",
4545
"aria-hidden": "^1.2.3",
4646
"react-remove-scroll": "^2.5.5"

packages/primitives/src/components/Combobox/Combobox.tsx

+129-67
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ interface Data {
4545
value: string;
4646
disabled: boolean;
4747
textValue: string;
48+
isVisible?: boolean;
4849
}
4950

5051
interface OptionData extends Data {
@@ -406,8 +407,12 @@ const ComboxboxTextInput = React.forwardRef<ComboboxInputElement, TextInputProps
406407
ref={composedRefs}
407408
onKeyDown={composeEventHandlers(props.onKeyDown, (event) => {
408409
if (['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
410+
if (!context.open) {
411+
handleOpen();
412+
}
413+
409414
setTimeout(() => {
410-
const items = getItems().filter((item) => !item.disabled);
415+
const items = getItems().filter((item) => !item.disabled && item.isVisible);
411416
let candidateNodes = items.map((item) => item.ref.current!);
412417

413418
if (['ArrowUp', 'End'].includes(event.key)) {
@@ -491,10 +496,7 @@ const ComboxboxTextInput = React.forwardRef<ComboboxInputElement, TextInputProps
491496
}
492497
})}
493498
onKeyUp={composeEventHandlers(props.onKeyUp, (event) => {
494-
if (
495-
!context.open &&
496-
(isPrintableCharacter(event.key) || ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Backspace'].includes(event.key))
497-
) {
499+
if (!context.open && (isPrintableCharacter(event.key) || ['Backspace'].includes(event.key))) {
498500
handleOpen();
499501
}
500502

@@ -859,6 +861,7 @@ interface ComboboxItemContextValue {
859861
onTextValueChange(node: HTMLSpanElement | null): void;
860862
textId: string;
861863
isSelected: boolean;
864+
textValue: string;
862865
}
863866

864867
const [ComboboxItemProvider, useComboboxItemContext] = createContext<ComboboxItemContextValue>(ITEM_NAME);
@@ -867,52 +870,29 @@ const [ComboboxItemProvider, useComboboxItemContext] = createContext<ComboboxIte
867870
* ComboboxItem
868871
* -----------------------------------------------------------------------------------------------*/
869872

870-
type ComboboxItemElement = React.ElementRef<typeof Primitive.div>;
873+
type ComboboxItemElement = ComboboxItemImplElement;
871874

872-
interface ItemProps extends PrimitiveDivProps {
873-
value: string;
874-
disabled?: boolean;
875+
interface ItemProps extends ItemImplProps {
875876
textValue?: string;
876877
}
877878

878879
export const ComboboxItem = React.forwardRef<ComboboxItemElement, ItemProps>((props, forwardedRef) => {
879-
const { value, disabled = false, textValue: textValueProp, ...restProps } = props;
880-
const itemRef = React.useRef<HTMLDivElement>(null);
880+
const { value, disabled = false, textValue: textValueProp } = props;
881+
const [fragment, setFragment] = React.useState<DocumentFragment>();
881882

882-
const composedRefs = useComposedRefs(forwardedRef, itemRef);
883+
// setting the fragment in `useLayoutEffect` as `DocumentFragment` doesn't exist on the server
884+
useLayoutEffect(() => {
885+
setFragment(new DocumentFragment());
886+
}, []);
883887

884-
const { getItems } = useCollection(undefined);
885-
const {
886-
onTextValueChange,
887-
textValue: contextTextValue,
888-
visuallyFocussedItem,
889-
...context
890-
} = useComboboxContext(ITEM_NAME);
888+
const { onTextValueChange, textValue: contextTextValue, ...context } = useComboboxContext(ITEM_NAME);
891889

892890
const textId = useId();
893891

894892
const [textValue, setTextValue] = React.useState(textValueProp ?? '');
895893

896-
const isFocused = React.useMemo(() => {
897-
return visuallyFocussedItem === getItems().find((item) => item.ref.current === itemRef.current)?.ref.current;
898-
}, [getItems, visuallyFocussedItem]);
899-
900894
const isSelected = context.value === value;
901895

902-
const handleSelect = () => {
903-
if (!disabled) {
904-
context.onValueChange(value);
905-
onTextValueChange(textValue);
906-
context.onOpenChange(false);
907-
908-
if (context.autocomplete === 'both') {
909-
context.onFilterValueChange(textValue);
910-
}
911-
912-
context.trigger?.focus({ preventScroll: true });
913-
}
914-
};
915-
916896
const { startsWith } = useFilter(context.locale, { sensitivity: 'base' });
917897

918898
const handleTextValueChange = React.useCallback((node: HTMLSpanElement | null) => {
@@ -931,44 +911,119 @@ export const ComboboxItem = React.forwardRef<ComboboxItemElement, ItemProps>((pr
931911
}
932912
}, [textValue, isSelected, contextTextValue, onTextValueChange]);
933913

934-
const id = useId();
935-
936-
if (context.autocomplete === 'list' && textValue && contextTextValue && !startsWith(textValue, contextTextValue)) {
937-
return null;
938-
}
939-
940914
if (
941-
context.autocomplete === 'both' &&
942-
textValue &&
943-
context.filterValue &&
944-
!startsWith(textValue, context.filterValue)
915+
(context.autocomplete === 'both' &&
916+
textValue &&
917+
context.filterValue &&
918+
!startsWith(textValue, context.filterValue)) ||
919+
(context.autocomplete === 'list' && textValue && contextTextValue && !startsWith(textValue, contextTextValue))
945920
) {
946-
return null;
921+
return fragment
922+
? ReactDOM.createPortal(
923+
<ComboboxItemProvider
924+
textId={textId}
925+
onTextValueChange={handleTextValueChange}
926+
isSelected={isSelected}
927+
textValue={textValue}
928+
>
929+
<Collection.ItemSlot
930+
scope={undefined}
931+
value={value}
932+
textValue={textValue}
933+
disabled={disabled}
934+
type="option"
935+
isVisible={false}
936+
>
937+
<ComboboxItemImpl ref={forwardedRef} {...props} />
938+
</Collection.ItemSlot>
939+
</ComboboxItemProvider>,
940+
fragment,
941+
)
942+
: null;
947943
}
948944

949945
return (
950-
<ComboboxItemProvider textId={textId} onTextValueChange={handleTextValueChange} isSelected={isSelected}>
951-
<Collection.ItemSlot scope={undefined} value={value} textValue={textValue} disabled={disabled} type="option">
952-
<Primitive.div
953-
role="option"
954-
aria-labelledby={textId}
955-
data-highlighted={isFocused ? '' : undefined}
956-
// `isFocused` caveat fixes stuttering in VoiceOver
957-
aria-selected={isSelected && isFocused}
958-
data-state={isSelected ? 'checked' : 'unchecked'}
959-
aria-disabled={disabled || undefined}
960-
data-disabled={disabled ? '' : undefined}
961-
tabIndex={disabled ? undefined : -1}
962-
{...restProps}
963-
id={id}
964-
ref={composedRefs}
965-
onPointerUp={composeEventHandlers(restProps.onPointerUp, handleSelect)}
966-
/>
946+
<ComboboxItemProvider
947+
textId={textId}
948+
onTextValueChange={handleTextValueChange}
949+
isSelected={isSelected}
950+
textValue={textValue}
951+
>
952+
<Collection.ItemSlot
953+
scope={undefined}
954+
value={value}
955+
textValue={textValue}
956+
disabled={disabled}
957+
type="option"
958+
isVisible
959+
>
960+
<ComboboxItemImpl ref={forwardedRef} {...props} />
967961
</Collection.ItemSlot>
968962
</ComboboxItemProvider>
969963
);
970964
});
971965

966+
/* -------------------------------------------------------------------------------------------------
967+
* ComboboxItemImpl
968+
* -----------------------------------------------------------------------------------------------*/
969+
970+
const ITEM_IMPL_NAME = 'ComboboxItemImpl';
971+
972+
type ComboboxItemImplElement = React.ElementRef<typeof Primitive.div>;
973+
974+
interface ItemImplProps extends PrimitiveDivProps {
975+
value: string;
976+
disabled?: boolean;
977+
}
978+
979+
const ComboboxItemImpl = React.forwardRef<ComboboxItemImplElement, ItemImplProps>((props, forwardedRef) => {
980+
const { value, disabled = false, ...restProps } = props;
981+
const itemRef = React.useRef<HTMLDivElement>(null);
982+
const composedRefs = useComposedRefs(forwardedRef, itemRef);
983+
984+
const { getItems } = useCollection(undefined);
985+
const { onTextValueChange, visuallyFocussedItem, ...context } = useComboboxContext(ITEM_NAME);
986+
const { isSelected, textValue, textId } = useComboboxItemContext(ITEM_IMPL_NAME);
987+
988+
const handleSelect = () => {
989+
if (!disabled) {
990+
context.onValueChange(value);
991+
onTextValueChange(textValue);
992+
context.onOpenChange(false);
993+
994+
if (context.autocomplete === 'both') {
995+
context.onFilterValueChange(textValue);
996+
}
997+
998+
context.trigger?.focus({ preventScroll: true });
999+
}
1000+
};
1001+
1002+
const isFocused = React.useMemo(() => {
1003+
return visuallyFocussedItem === getItems().find((item) => item.ref.current === itemRef.current)?.ref.current;
1004+
}, [getItems, visuallyFocussedItem]);
1005+
1006+
const id = useId();
1007+
1008+
return (
1009+
<Primitive.div
1010+
role="option"
1011+
aria-labelledby={textId}
1012+
data-highlighted={isFocused ? '' : undefined}
1013+
// `isFocused` caveat fixes stuttering in VoiceOver
1014+
aria-selected={isSelected && isFocused}
1015+
data-state={isSelected ? 'checked' : 'unchecked'}
1016+
aria-disabled={disabled || undefined}
1017+
data-disabled={disabled ? '' : undefined}
1018+
tabIndex={disabled ? undefined : -1}
1019+
{...restProps}
1020+
id={id}
1021+
ref={composedRefs}
1022+
onPointerUp={composeEventHandlers(restProps.onPointerUp, handleSelect)}
1023+
/>
1024+
);
1025+
});
1026+
9721027
/* -------------------------------------------------------------------------------------------------
9731028
* ComboboxItemText
9741029
* -----------------------------------------------------------------------------------------------*/
@@ -1009,7 +1064,7 @@ const NO_VALUE_FOUND_NAME = 'ComboboxNoValueFound';
10091064
interface NoValueFoundProps extends PrimitiveDivProps {}
10101065

10111066
const ComboboxNoValueFound = React.forwardRef<HTMLDivElement, NoValueFoundProps>((props, ref) => {
1012-
const { textValue = '', locale } = useComboboxContext(NO_VALUE_FOUND_NAME);
1067+
const { textValue = '', filterValue = '', locale, autocomplete } = useComboboxContext(NO_VALUE_FOUND_NAME);
10131068
const [items, setItems] = React.useState<CollectionData[]>([]);
10141069
const { subscribe } = useCollection(undefined);
10151070

@@ -1029,7 +1084,13 @@ const ComboboxNoValueFound = React.forwardRef<HTMLDivElement, NoValueFoundProps>
10291084
};
10301085
}, [subscribe]);
10311086

1032-
if (items.some((item) => startsWith(item.textValue, textValue))) {
1087+
if (items.length === 0) return null;
1088+
1089+
if (autocomplete === 'list' && items.some((item) => startsWith(item.textValue, textValue))) {
1090+
return null;
1091+
}
1092+
1093+
if (autocomplete === 'both' && items.some((item) => startsWith(item.textValue, filterValue))) {
10331094
return null;
10341095
}
10351096

@@ -1098,6 +1159,7 @@ const ComboboxCreateItem = React.forwardRef<ComboboxItemElement, CreateItemProps
10981159
value={textValue ?? ''}
10991160
textValue={textValue ?? ''}
11001161
disabled={disabled}
1162+
isVisible
11011163
type="create"
11021164
>
11031165
<Primitive.div

packages/primitives/src/components/Combobox/__tests__/index.test.tsx

-31
Original file line numberDiff line numberDiff line change
@@ -580,37 +580,6 @@ describe('Combobox', () => {
580580

581581
expect(onValueChange).toHaveBeenCalled();
582582
});
583-
584-
it('should correctly show the NoValue text as the textValue changes in different ways', async () => {
585-
const { getByRole, queryByText, user } = render({ hideCreatable: true });
586-
587-
await user.click(getByRole('combobox'));
588-
589-
/**
590-
* see note above
591-
*/
592-
getByRole('combobox').focus();
593-
594-
await user.keyboard('Option 1');
595-
596-
expect(getByRole('combobox')).toHaveValue('Option 1');
597-
expect(queryByText('No value found')).not.toBeInTheDocument();
598-
599-
await user.keyboard('2');
600-
601-
expect(getByRole('combobox')).toHaveValue('Option 12');
602-
expect(queryByText('No value found')).toBeInTheDocument();
603-
604-
await user.keyboard('[Backspace]');
605-
606-
expect(getByRole('combobox')).toHaveValue('Option 1');
607-
expect(queryByText('No value found')).not.toBeInTheDocument();
608-
609-
await user.clear(getByRole('combobox'));
610-
611-
expect(getByRole('combobox')).toHaveValue('');
612-
expect(queryByText('No value found')).not.toBeInTheDocument();
613-
});
614583
});
615584

616585
describe('Controlling the component', () => {

packages/strapi-design-system/src/Button/Button.tsx

+7-12
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ const rotation = keyframes`
1919
}
2020
`;
2121

22-
const LoadingWrapper = styled.div`
22+
const LoaderAnimated = styled(Loader)`
2323
animation: ${rotation} 2s infinite linear;
2424
will-change: transform;
2525
`;
2626

2727
export const ButtonWrapper = styled(BaseButton)<Required<Pick<ButtonProps, 'size' | 'variant'>>>`
2828
height: ${({ theme, size }) => theme.sizes.button[size]};
2929
30+
svg {
31+
height: ${12 / 16}rem;
32+
width: auto;
33+
}
34+
3035
&[aria-disabled='true'] {
3136
${getDisabledStyle}
3237
@@ -100,17 +105,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
100105
width={fullWidth ? '100%' : undefined}
101106
{...props}
102107
>
103-
{(startIcon || loading) && (
104-
<Box aria-hidden>
105-
{loading ? (
106-
<LoadingWrapper>
107-
<Loader />
108-
</LoadingWrapper>
109-
) : (
110-
startIcon
111-
)}
112-
</Box>
113-
)}
108+
{(startIcon || loading) && <Box aria-hidden>{loading ? <LoaderAnimated /> : startIcon}</Box>}
114109

115110
<Typography
116111
variant={size === 'S' ? 'pi' : undefined}

packages/strapi-design-system/src/CarouselInput/CarouselImage.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ export const CarouselImage = (props: CarouselImageProps) => {
2525
if (isError) {
2626
return (
2727
<Tooltip description={props.alt ?? ''}>
28-
<StyledImage as="img" height="100%" {...props} />
28+
<StyledImage as="img" height="100%" maxWidth="100%" {...props} />
2929
</Tooltip>
3030
);
3131
}
3232

33-
return <StyledImage as="img" height="100%" {...props} onError={handleImageError} />;
33+
return <StyledImage as="img" height="100%" maxWidth="100%" {...props} onError={handleImageError} />;
3434
};

0 commit comments

Comments
 (0)