@mrshmllw/smores-react 16.9.0 → 16.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ type ButtonElement = ReactElement<{
4
4
  'aria-describedby'?: string;
5
5
  }>;
6
6
  export type ButtonContainerOrientation = 'vertical' | 'horizontal';
7
+ export type ButtonContainerAlignment = 'center' | 'start' | 'end';
7
8
  export type ButtonContainerSupportingMessage = {
8
9
  /** Short plain text, e.g. reassurance copy. Required, so an icon never shows on its own */
9
10
  text: string;
@@ -20,6 +21,12 @@ export type ButtonContainerProps = {
20
21
  tertiaryButton?: ButtonElement;
21
22
  /** Stack the buttons vertically or lay them out side by side */
22
23
  orientation?: ButtonContainerOrientation;
24
+ /**
25
+ * Where the buttons and supporting text sit from 768px up. Use `start` or
26
+ * `end` to line them up with the edge of the page content. On mobile they
27
+ * fill the width.
28
+ */
29
+ align?: ButtonContainerAlignment;
23
30
  /** Optional message shown below the buttons, e.g. reassurance copy */
24
31
  supportingMessage?: ButtonContainerSupportingMessage;
25
32
  /**
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Fragment, cloneElement, useEffect, useId, useRef, useState, } from 'react';
3
3
  import styled, { css, useTheme } from 'styled-components';
4
4
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
- export const ButtonContainer = ({ className, primaryButton, secondaryButton, tertiaryButton, orientation = 'vertical', supportingMessage, pinned = true, }) => {
5
+ export const ButtonContainer = ({ className, primaryButton, secondaryButton, tertiaryButton, orientation = 'vertical', align = 'center', supportingMessage, pinned = true, }) => {
6
6
  const theme = useTheme();
7
7
  const supportingTextId = useId();
8
8
  const containerRef = useRef(null);
@@ -33,9 +33,14 @@ export const ButtonContainer = ({ className, primaryButton, secondaryButton, ter
33
33
  const buttons = orientation === 'horizontal'
34
34
  ? [tertiaryButton, secondaryButton, describedPrimaryButton]
35
35
  : [describedPrimaryButton, secondaryButton, tertiaryButton];
36
- return (_jsxs(_Fragment, { children: [pinned && _jsx(Spacer, { "aria-hidden": "true", $height: containerHeight }), _jsxs(Container, { ref: containerRef, className: className, $pinned: pinned, children: [_jsx(ButtonGroup, { $orientation: orientation, children: buttons.map((button, index) => button && _jsx(Fragment, { children: button }, index)) }), supportingMessage && hasSupportingMessage && (_jsxs(SupportingText, { id: supportingTextId, children: [supportingMessage.icon && (_jsx(SupportingIconContainer, { children: _jsx(FontAwesomeIcon, { icon: supportingMessage.icon, color: theme.color.icon.subtle }) })), supportingMessage.text] }))] })] }));
36
+ return (_jsxs(_Fragment, { children: [pinned && _jsx(Spacer, { "aria-hidden": "true", $height: containerHeight }), _jsxs(Container, { ref: containerRef, className: className, $pinned: pinned, children: [_jsx(ButtonGroup, { $orientation: orientation, $align: align, children: buttons.map((button, index) => button && _jsx(Fragment, { children: button }, index)) }), supportingMessage && hasSupportingMessage && (_jsxs(SupportingText, { id: supportingTextId, $align: align, children: [supportingMessage.icon && (_jsx(SupportingIconContainer, { children: _jsx(FontAwesomeIcon, { icon: supportingMessage.icon, color: theme.color.icon.subtle }) })), supportingMessage.text] }))] })] }));
37
37
  };
38
38
  const desktopMediaQuery = '@media (min-width: 768px)';
39
+ const horizontalMargins = {
40
+ center: '0 auto',
41
+ start: '0 auto 0 0',
42
+ end: '0 0 0 auto',
43
+ };
39
44
  const desktopVerticalButtonMaxWidth = '400px';
40
45
  const desktopHorizontalButtonMinWidth = '160px';
41
46
  const Spacer = styled.div(({ $height }) => css `
@@ -77,7 +82,7 @@ const Container = styled.div(({ theme, $pinned }) => css `
77
82
  `}
78
83
  `);
79
84
  // On desktop, horizontal buttons all match the width of the longest label
80
- const ButtonGroup = styled.div(({ theme, $orientation }) => css `
85
+ const ButtonGroup = styled.div(({ theme, $orientation, $align }) => css `
81
86
  display: grid;
82
87
  gap: ${theme.space[150]};
83
88
 
@@ -85,7 +90,7 @@ const ButtonGroup = styled.div(({ theme, $orientation }) => css `
85
90
  css `
86
91
  ${desktopMediaQuery} {
87
92
  grid-template-columns: minmax(0, ${desktopVerticalButtonMaxWidth});
88
- justify-content: center;
93
+ justify-content: ${$align};
89
94
  }
90
95
  `}
91
96
 
@@ -94,12 +99,12 @@ const ButtonGroup = styled.div(({ theme, $orientation }) => css `
94
99
  grid-auto-flow: column;
95
100
  grid-auto-columns: minmax(0, 1fr);
96
101
 
97
- // On desktop the buttons share the width of the longest label, centred
102
+ // On desktop the buttons share the width of the longest label
98
103
  ${desktopMediaQuery} {
99
104
  grid-auto-columns: minmax(${desktopHorizontalButtonMinWidth}, 1fr);
100
105
  width: fit-content;
101
106
  max-width: 100%;
102
- margin: 0 auto;
107
+ margin: ${horizontalMargins[$align]};
103
108
  }
104
109
  `}
105
110
  `);
@@ -120,12 +125,19 @@ const SupportingIconContainer = styled.span(({ theme }) => css `
120
125
  overflow: visible;
121
126
  }
122
127
  `);
123
- const SupportingText = styled.p(({ theme }) => css `
128
+ const SupportingText = styled.p(({ theme, $align }) => css `
124
129
  margin: 0;
125
130
  font-size: ${theme.font.body[200].fontSize};
126
131
  font-weight: ${theme.font.body[200].fontWeight};
127
132
  line-height: ${theme.font.body[200].lineHeight};
128
133
  color: ${theme.color.text.subtle};
129
134
  text-align: center;
135
+
136
+ ${$align !== 'center' &&
137
+ css `
138
+ ${desktopMediaQuery} {
139
+ text-align: ${$align};
140
+ }
141
+ `}
130
142
  `);
131
143
  //# sourceMappingURL=ButtonContainer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ButtonContainer.js","sourceRoot":"","sources":["../../src/ButtonContainer/ButtonContainer.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FC,\n Fragment,\n ReactElement,\n cloneElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport styled, { css, useTheme } from 'styled-components'\nimport { IconDefinition } from '@fortawesome/fontawesome-svg-core'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\ntype ButtonElement = ReactElement<{ 'aria-describedby'?: string }>\n\nexport type ButtonContainerOrientation = 'vertical' | 'horizontal'\n\nexport type ButtonContainerSupportingMessage = {\n /** Short plain text, e.g. reassurance copy. Required, so an icon never shows on its own */\n text: string\n /** Optional decorative icon shown before the text */\n icon?: IconDefinition\n}\n\nexport type ButtonContainerProps = {\n className?: string\n /** Main action. Shown first when vertical, last (right) when horizontal */\n primaryButton: ButtonElement\n /** Optional supporting action */\n secondaryButton?: ButtonElement\n /** Optional low-emphasis action. Shown last when vertical, first (left) when horizontal */\n tertiaryButton?: ButtonElement\n /** Stack the buttons vertically or lay them out side by side */\n orientation?: ButtonContainerOrientation\n /** Optional message shown below the buttons, e.g. reassurance copy */\n supportingMessage?: ButtonContainerSupportingMessage\n /**\n * Pin the container to the bottom of the viewport on mobile.\n * On larger screens it always sits in the page flow.\n */\n pinned?: boolean\n}\n\nexport const ButtonContainer: FC<ButtonContainerProps> = ({\n className,\n primaryButton,\n secondaryButton,\n tertiaryButton,\n orientation = 'vertical',\n supportingMessage,\n pinned = true,\n}) => {\n const theme = useTheme()\n const supportingTextId = useId()\n const containerRef = useRef<HTMLDivElement>(null)\n const [containerHeight, setContainerHeight] = useState(0)\n\n // Reserve the pinned container's height in the page flow so it never covers content\n useEffect(() => {\n const element = containerRef.current\n if (!pinned || !element || typeof ResizeObserver === 'undefined') return\n\n const observer = new ResizeObserver(() => {\n setContainerHeight(element.getBoundingClientRect().height)\n })\n observer.observe(element)\n\n return () => observer.disconnect()\n }, [pinned])\n\n const hasSupportingMessage = Boolean(supportingMessage?.text?.trim())\n\n const describedPrimaryButton = hasSupportingMessage\n ? cloneElement(primaryButton, {\n 'aria-describedby': [\n primaryButton.props['aria-describedby'],\n supportingTextId,\n ]\n .filter(Boolean)\n .join(' '),\n })\n : primaryButton\n\n // DOM order follows the visual order so keyboard focus moves the way the buttons read\n const buttons =\n orientation === 'horizontal'\n ? [tertiaryButton, secondaryButton, describedPrimaryButton]\n : [describedPrimaryButton, secondaryButton, tertiaryButton]\n\n return (\n <>\n {pinned && <Spacer aria-hidden=\"true\" $height={containerHeight} />}\n <Container ref={containerRef} className={className} $pinned={pinned}>\n <ButtonGroup $orientation={orientation}>\n {buttons.map(\n (button, index) =>\n button && <Fragment key={index}>{button}</Fragment>,\n )}\n </ButtonGroup>\n {supportingMessage && hasSupportingMessage && (\n <SupportingText id={supportingTextId}>\n {supportingMessage.icon && (\n <SupportingIconContainer>\n <FontAwesomeIcon\n icon={supportingMessage.icon}\n color={theme.color.icon.subtle}\n />\n </SupportingIconContainer>\n )}\n {supportingMessage.text}\n </SupportingText>\n )}\n </Container>\n </>\n )\n}\n\nconst desktopMediaQuery = '@media (min-width: 768px)'\nconst desktopVerticalButtonMaxWidth = '400px'\nconst desktopHorizontalButtonMinWidth = '160px'\n\nconst Spacer = styled.div<{ $height: number }>(\n ({ $height }) => css`\n height: ${$height}px;\n\n ${desktopMediaQuery} {\n display: none;\n }\n `,\n)\n\nconst Container = styled.div<{ $pinned: boolean }>(\n ({ theme, $pinned }) => css`\n display: flex;\n flex-direction: column;\n gap: ${theme.space[150]};\n width: 100%;\n // The library ships no CSS reset, so keep the pinned padding inside the viewport width\n box-sizing: border-box;\n\n ${\n $pinned &&\n css`\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: ${theme.elevation.floating};\n padding: ${theme.space[200]};\n padding-bottom: calc(\n ${theme.space[200]} + env(safe-area-inset-bottom, 0px)\n );\n background-color: ${theme.color.surface.base[200]};\n border-radius: ${theme.radius[200]} ${theme.radius[200]} 0 0;\n\n ${desktopMediaQuery} {\n position: static;\n z-index: auto;\n padding: 0;\n background-color: transparent;\n border-radius: 0;\n }\n `\n }\n `,\n)\n\n// On desktop, horizontal buttons all match the width of the longest label\nconst ButtonGroup = styled.div<{ $orientation: ButtonContainerOrientation }>(\n ({ theme, $orientation }) => css`\n display: grid;\n gap: ${theme.space[150]};\n\n ${\n $orientation === 'vertical' &&\n css`\n ${desktopMediaQuery} {\n grid-template-columns: minmax(0, ${desktopVerticalButtonMaxWidth});\n justify-content: center;\n }\n `\n }\n\n ${\n $orientation === 'horizontal' &&\n css`\n grid-auto-flow: column;\n grid-auto-columns: minmax(0, 1fr);\n\n // On desktop the buttons share the width of the longest label, centred\n ${desktopMediaQuery} {\n grid-auto-columns: minmax(${desktopHorizontalButtonMinWidth}, 1fr);\n width: fit-content;\n max-width: 100%;\n margin: 0 auto;\n }\n `\n }\n `,\n)\n\n// Figma icons use Font Awesome's square \"full\" bounding box (640 units),\n// in which the glyph's 512 unit em height takes up 80% of the box\nconst SupportingIconContainer = styled.span(\n ({ theme }) => css`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n margin-right: ${theme.space['050']};\n vertical-align: top;\n\n svg {\n width: 100%;\n height: 80%;\n overflow: visible;\n }\n `,\n)\n\nconst SupportingText = styled.p(\n ({ theme }) => css`\n margin: 0;\n font-size: ${theme.font.body[200].fontSize};\n font-weight: ${theme.font.body[200].fontWeight};\n line-height: ${theme.font.body[200].lineHeight};\n color: ${theme.color.text.subtle};\n text-align: center;\n `,\n)\n"]}
1
+ {"version":3,"file":"ButtonContainer.js","sourceRoot":"","sources":["../../src/ButtonContainer/ButtonContainer.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FC,\n Fragment,\n ReactElement,\n cloneElement,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport styled, { css, useTheme } from 'styled-components'\nimport { IconDefinition } from '@fortawesome/fontawesome-svg-core'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\n\ntype ButtonElement = ReactElement<{ 'aria-describedby'?: string }>\n\nexport type ButtonContainerOrientation = 'vertical' | 'horizontal'\n\nexport type ButtonContainerAlignment = 'center' | 'start' | 'end'\n\nexport type ButtonContainerSupportingMessage = {\n /** Short plain text, e.g. reassurance copy. Required, so an icon never shows on its own */\n text: string\n /** Optional decorative icon shown before the text */\n icon?: IconDefinition\n}\n\nexport type ButtonContainerProps = {\n className?: string\n /** Main action. Shown first when vertical, last (right) when horizontal */\n primaryButton: ButtonElement\n /** Optional supporting action */\n secondaryButton?: ButtonElement\n /** Optional low-emphasis action. Shown last when vertical, first (left) when horizontal */\n tertiaryButton?: ButtonElement\n /** Stack the buttons vertically or lay them out side by side */\n orientation?: ButtonContainerOrientation\n /**\n * Where the buttons and supporting text sit from 768px up. Use `start` or\n * `end` to line them up with the edge of the page content. On mobile they\n * fill the width.\n */\n align?: ButtonContainerAlignment\n /** Optional message shown below the buttons, e.g. reassurance copy */\n supportingMessage?: ButtonContainerSupportingMessage\n /**\n * Pin the container to the bottom of the viewport on mobile.\n * On larger screens it always sits in the page flow.\n */\n pinned?: boolean\n}\n\nexport const ButtonContainer: FC<ButtonContainerProps> = ({\n className,\n primaryButton,\n secondaryButton,\n tertiaryButton,\n orientation = 'vertical',\n align = 'center',\n supportingMessage,\n pinned = true,\n}) => {\n const theme = useTheme()\n const supportingTextId = useId()\n const containerRef = useRef<HTMLDivElement>(null)\n const [containerHeight, setContainerHeight] = useState(0)\n\n // Reserve the pinned container's height in the page flow so it never covers content\n useEffect(() => {\n const element = containerRef.current\n if (!pinned || !element || typeof ResizeObserver === 'undefined') return\n\n const observer = new ResizeObserver(() => {\n setContainerHeight(element.getBoundingClientRect().height)\n })\n observer.observe(element)\n\n return () => observer.disconnect()\n }, [pinned])\n\n const hasSupportingMessage = Boolean(supportingMessage?.text?.trim())\n\n const describedPrimaryButton = hasSupportingMessage\n ? cloneElement(primaryButton, {\n 'aria-describedby': [\n primaryButton.props['aria-describedby'],\n supportingTextId,\n ]\n .filter(Boolean)\n .join(' '),\n })\n : primaryButton\n\n // DOM order follows the visual order so keyboard focus moves the way the buttons read\n const buttons =\n orientation === 'horizontal'\n ? [tertiaryButton, secondaryButton, describedPrimaryButton]\n : [describedPrimaryButton, secondaryButton, tertiaryButton]\n\n return (\n <>\n {pinned && <Spacer aria-hidden=\"true\" $height={containerHeight} />}\n <Container ref={containerRef} className={className} $pinned={pinned}>\n <ButtonGroup $orientation={orientation} $align={align}>\n {buttons.map(\n (button, index) =>\n button && <Fragment key={index}>{button}</Fragment>,\n )}\n </ButtonGroup>\n {supportingMessage && hasSupportingMessage && (\n <SupportingText id={supportingTextId} $align={align}>\n {supportingMessage.icon && (\n <SupportingIconContainer>\n <FontAwesomeIcon\n icon={supportingMessage.icon}\n color={theme.color.icon.subtle}\n />\n </SupportingIconContainer>\n )}\n {supportingMessage.text}\n </SupportingText>\n )}\n </Container>\n </>\n )\n}\n\nconst desktopMediaQuery = '@media (min-width: 768px)'\n\nconst horizontalMargins: Record<ButtonContainerAlignment, string> = {\n center: '0 auto',\n start: '0 auto 0 0',\n end: '0 0 0 auto',\n}\nconst desktopVerticalButtonMaxWidth = '400px'\nconst desktopHorizontalButtonMinWidth = '160px'\n\nconst Spacer = styled.div<{ $height: number }>(\n ({ $height }) => css`\n height: ${$height}px;\n\n ${desktopMediaQuery} {\n display: none;\n }\n `,\n)\n\nconst Container = styled.div<{ $pinned: boolean }>(\n ({ theme, $pinned }) => css`\n display: flex;\n flex-direction: column;\n gap: ${theme.space[150]};\n width: 100%;\n // The library ships no CSS reset, so keep the pinned padding inside the viewport width\n box-sizing: border-box;\n\n ${\n $pinned &&\n css`\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: ${theme.elevation.floating};\n padding: ${theme.space[200]};\n padding-bottom: calc(\n ${theme.space[200]} + env(safe-area-inset-bottom, 0px)\n );\n background-color: ${theme.color.surface.base[200]};\n border-radius: ${theme.radius[200]} ${theme.radius[200]} 0 0;\n\n ${desktopMediaQuery} {\n position: static;\n z-index: auto;\n padding: 0;\n background-color: transparent;\n border-radius: 0;\n }\n `\n }\n `,\n)\n\n// On desktop, horizontal buttons all match the width of the longest label\nconst ButtonGroup = styled.div<{\n $orientation: ButtonContainerOrientation\n $align: ButtonContainerAlignment\n}>(\n ({ theme, $orientation, $align }) => css`\n display: grid;\n gap: ${theme.space[150]};\n\n ${\n $orientation === 'vertical' &&\n css`\n ${desktopMediaQuery} {\n grid-template-columns: minmax(0, ${desktopVerticalButtonMaxWidth});\n justify-content: ${$align};\n }\n `\n }\n\n ${\n $orientation === 'horizontal' &&\n css`\n grid-auto-flow: column;\n grid-auto-columns: minmax(0, 1fr);\n\n // On desktop the buttons share the width of the longest label\n ${desktopMediaQuery} {\n grid-auto-columns: minmax(${desktopHorizontalButtonMinWidth}, 1fr);\n width: fit-content;\n max-width: 100%;\n margin: ${horizontalMargins[$align]};\n }\n `\n }\n `,\n)\n\n// Figma icons use Font Awesome's square \"full\" bounding box (640 units),\n// in which the glyph's 512 unit em height takes up 80% of the box\nconst SupportingIconContainer = styled.span(\n ({ theme }) => css`\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n margin-right: ${theme.space['050']};\n vertical-align: top;\n\n svg {\n width: 100%;\n height: 80%;\n overflow: visible;\n }\n `,\n)\n\nconst SupportingText = styled.p<{ $align: ButtonContainerAlignment }>(\n ({ theme, $align }) => css`\n margin: 0;\n font-size: ${theme.font.body[200].fontSize};\n font-weight: ${theme.font.body[200].fontWeight};\n line-height: ${theme.font.body[200].lineHeight};\n color: ${theme.color.text.subtle};\n text-align: center;\n\n ${\n $align !== 'center' &&\n css`\n ${desktopMediaQuery} {\n text-align: ${$align};\n }\n `\n }\n `,\n)\n"]}
@@ -1,2 +1,2 @@
1
1
  export { ButtonContainer } from './ButtonContainer';
2
- export type { ButtonContainerOrientation, ButtonContainerProps, ButtonContainerSupportingMessage, } from './ButtonContainer';
2
+ export type { ButtonContainerAlignment, ButtonContainerOrientation, ButtonContainerProps, ButtonContainerSupportingMessage, } from './ButtonContainer';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ButtonContainer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA","sourcesContent":["export { ButtonContainer } from './ButtonContainer'\nexport type {\n ButtonContainerOrientation,\n ButtonContainerProps,\n ButtonContainerSupportingMessage,\n} from './ButtonContainer'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ButtonContainer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA","sourcesContent":["export { ButtonContainer } from './ButtonContainer'\nexport type {\n ButtonContainerAlignment,\n ButtonContainerOrientation,\n ButtonContainerProps,\n ButtonContainerSupportingMessage,\n} from './ButtonContainer'\n"]}
@@ -9,6 +9,7 @@ import { useOnClickOutside } from '../hooks';
9
9
  import { useUniqueId } from '../utils/id';
10
10
  import { useControllableState } from '../utils/useControlledState';
11
11
  import { SearchOptions } from './components/SearchOptions';
12
+ import { visuallyHidden } from '../utils/visuallyHidden';
12
13
  import { IconContainer } from '../sharedStyles/shared.styles';
13
14
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
14
15
  import { faXmark, faChevronDown, faSearch, } from '@awesome.me/kit-46ca99185c/icons/classic/regular';
@@ -40,20 +41,22 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
40
41
  });
41
42
  const [searchQuery, setSearchQuery] = useState(null);
42
43
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
44
+ const [emptyMessage, setEmptyMessage] = useState('');
43
45
  const selectedValueLabel = searchList.find((option) => option.label === selectedValue || option.value === selectedValue)?.label;
44
46
  const handleBlur = () => {
45
- if (selectedValue) {
46
- setSearchQuery(selectedValueLabel ?? null);
47
- }
48
- else if (!selectedValue) {
49
- setSearchQuery(null);
50
- }
47
+ setSearchQuery(null);
48
+ };
49
+ // Every way of closing the list goes through here, so a reopened list
50
+ // never starts with a stale highlighted option.
51
+ const closeOptions = () => {
52
+ setShowOptions(false);
53
+ setHighlightedIndex(-1);
51
54
  };
52
55
  useOnClickOutside({
53
56
  ref: wrapperRef,
54
57
  callback: () => {
55
58
  handleBlur();
56
- setShowOptions(false);
59
+ closeOptions();
57
60
  },
58
61
  });
59
62
  // oxlint-disable-next-line react/react-compiler
@@ -90,7 +93,7 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
90
93
  setHighlightedIndex(-1);
91
94
  if (query === null) {
92
95
  setSelectedValue(null);
93
- setShowOptions(false);
96
+ closeOptions();
94
97
  }
95
98
  else {
96
99
  setShowOptions(true);
@@ -102,10 +105,6 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
102
105
  updateSearchQuery(searchQuery);
103
106
  setShowOptions(true);
104
107
  }
105
- else if (selectedValue !== null && searchQuery === null) {
106
- setSearchQuery(selectedValueLabel || null);
107
- setShowOptions(true);
108
- }
109
108
  };
110
109
  const handleInputChange = (event) => {
111
110
  const nextValue = event.currentTarget.value;
@@ -122,35 +121,63 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
122
121
  onFound('');
123
122
  };
124
123
  const handleCaretClick = () => {
125
- setShowOptions(!showOptions);
124
+ if (showOptions) {
125
+ closeOptions();
126
+ }
127
+ else {
128
+ setShowOptions(true);
129
+ }
126
130
  };
131
+ const listboxId = `${id}-listbox`;
132
+ const getOptionId = (index) => `${id}-option-${index}`;
133
+ const hasResults = showOptions && filteredList.length > 0;
134
+ const hasHighlight = hasResults &&
135
+ highlightedIndex >= 0 &&
136
+ highlightedIndex < filteredList.length;
137
+ const noResults = showOptions && filteredList.length === 0;
127
138
  const handleKeyDown = (event) => {
128
- if (event.key === 'Enter' && highlightedIndex !== -1) {
139
+ if (event.key === 'Escape') {
140
+ if (!showOptions)
141
+ return;
129
142
  event.preventDefault();
130
- const focusedItem = filteredList[highlightedIndex];
131
- handleSelect(focusedItem);
143
+ event.stopPropagation();
144
+ closeOptions();
145
+ }
146
+ else if (event.key === 'Enter') {
147
+ if (hasHighlight) {
148
+ event.preventDefault();
149
+ handleSelect(filteredList[highlightedIndex]);
150
+ }
132
151
  }
133
152
  else if (event.key === 'ArrowDown') {
134
153
  event.preventDefault();
135
- const nextIndex = (highlightedIndex + 1) % filteredList.length;
136
- setHighlightedIndex(nextIndex);
154
+ if (!showOptions) {
155
+ setShowOptions(true);
156
+ }
157
+ else if (filteredList.length > 0) {
158
+ setHighlightedIndex((highlightedIndex + 1) % filteredList.length);
159
+ }
137
160
  }
138
161
  else if (event.key === 'ArrowUp') {
139
162
  event.preventDefault();
140
- const prevIndex = (highlightedIndex - 1 + filteredList.length) % filteredList.length;
141
- setHighlightedIndex(prevIndex);
163
+ if (showOptions && filteredList.length > 0) {
164
+ setHighlightedIndex((highlightedIndex - 1 + filteredList.length) % filteredList.length);
165
+ }
142
166
  }
143
167
  };
144
168
  const showClearSearchButton = !!clearSearch && (!!value || !!selectedValue || !!searchQuery);
145
- return (_jsx(Wrapper, { ref: wrapperRef, children: _jsxs(Field, { className: className, renderAsTitle: renderAsTitle, htmlFor: id, ...otherProps, children: [_jsxs(Box, { flex: true, alignItems: "center", justifyContent: "flex-start", children: [showIcon && (_jsx(InputLeadingIconContainer, { $size: 20, children: _jsx(FontAwesomeIcon, { icon: faSearch, color: theme.color.text.subtle }) })), _jsx(Input, { id: id, name: name, ref: ref, placeholder: placeholder, $error: otherProps.error, $frontIcon: showIcon, $fallbackStyle: fallbackStyle, autoComplete: "off", value: displayedInputText, onFocus: handleClick, onChange: handleInputChange, selected: isSelected, onClick: handleClick, onKeyDown: handleKeyDown, onBlur: (e) => {
146
- onBlur?.(e);
147
- } }), _jsxs(Icons, { flex: true, alignItems: "center", gap: "space.100", $clearSearch: showClearSearchButton, children: [showClearSearchButton && (_jsx(IconContainer, { title: "Clear search", onClick: handleClearSearch, type: "button", as: "button", $size: 20, style: { cursor: 'pointer' }, children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.illustration.neutral[400] }) })), _jsx(Line, {}), _jsx(IconContainer, { type: "button", title: "icon-button", as: "button", onClick: handleCaretClick, $size: 20, children: _jsx(FontAwesomeIcon, { style: {
148
- rotate: showOptions ? '180deg' : '0deg',
149
- }, icon: faChevronDown, color: theme.color.illustration.neutral[400] }) })] })] }), showOptions && (_jsx(SearchOptions, { displayedList: filteredList, selectedValue: selectedValue, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onKeyDown: handleKeyDown, searchTerm: searchQuery || '', onSelect: handleSelect, positionRelative: resultsRelativePosition, resultsBorder: resultsBorder, onNotFound: onNotFound, notFoundComponent: notFoundComponent?.(searchQuery ?? '') }))] }) }));
169
+ return (_jsxs(Wrapper, { ref: wrapperRef, children: [_jsxs(Field, { className: className, renderAsTitle: renderAsTitle, htmlFor: id, ...otherProps, children: [_jsxs(Box, { flex: true, alignItems: "center", justifyContent: "flex-start", children: [showIcon && (_jsx(InputLeadingIconContainer, { $size: 20, children: _jsx(FontAwesomeIcon, { icon: faSearch, color: theme.color.text.subtle }) })), _jsx(Input, { id: id, name: name, ref: ref, placeholder: placeholder, $error: otherProps.error, $frontIcon: showIcon, $fallbackStyle: fallbackStyle, autoComplete: "off", role: "combobox", "aria-autocomplete": "list", "aria-expanded": hasResults, "aria-controls": hasResults ? listboxId : undefined, "aria-activedescendant": hasHighlight ? getOptionId(highlightedIndex) : undefined, value: displayedInputText, onFocus: handleClick, onChange: handleInputChange, selected: isSelected, onClick: handleClick, onKeyDown: handleKeyDown, onBlur: (e) => {
170
+ onBlur?.(e);
171
+ } }), _jsxs(Icons, { flex: true, alignItems: "center", gap: "space.100", $clearSearch: showClearSearchButton, children: [showClearSearchButton && (_jsx(IconContainer, { title: "Clear search", onClick: handleClearSearch, type: "button", as: "button", $size: 20, style: { cursor: 'pointer' }, children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.illustration.neutral[400] }) })), _jsx(Line, {}), _jsx(IconContainer, { type: "button", title: "icon-button", as: "button", onClick: handleCaretClick, $size: 20, children: _jsx(FontAwesomeIcon, { style: {
172
+ rotate: showOptions ? '180deg' : '0deg',
173
+ }, icon: faChevronDown, color: theme.color.illustration.neutral[400] }) })] })] }), showOptions && (_jsx(SearchOptions, { displayedList: filteredList, selectedValue: selectedValue, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onKeyDown: handleKeyDown, listboxId: listboxId, listboxLabel: otherProps.label ?? placeholder ?? 'Search results', getOptionId: getOptionId, searchTerm: searchQuery || '', onSelect: handleSelect, positionRelative: resultsRelativePosition, resultsBorder: resultsBorder, onNotFound: onNotFound, onEmptyMessageChange: setEmptyMessage, notFoundComponent: notFoundComponent?.(searchQuery ?? '') }))] }), _jsx(LiveRegion, { role: "status", children: noResults ? emptyMessage : '' })] }));
150
174
  });
151
175
  const Wrapper = styled(Box) `
152
176
  position: relative;
153
177
  `;
178
+ const LiveRegion = styled.div `
179
+ ${visuallyHidden}
180
+ `;
154
181
  const Line = styled(Box) `
155
182
  background: ${({ theme }) => theme.color.border.subtle};
156
183
  height: 24px;
@@ -1 +1 @@
1
- {"version":3,"file":"SearchInput.js","sourceRoot":"","sources":["../../src/SearchInput/SearchInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import Fuse, { IFuseOptions } from 'fuse.js'\nimport {\n ChangeEvent,\n FocusEvent,\n ReactNode,\n forwardRef,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport styled, { useTheme } from 'styled-components'\nimport { Box } from '../Box'\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n Input,\n InputLeadingIconContainer,\n} from '../fields/components/CommonInput'\nimport { useOnClickOutside } from '../hooks'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { SearchOptions } from './components/SearchOptions'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faXmark,\n faChevronDown,\n faSearch,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nexport type SearchInputItem = {\n label: string\n value: string\n tags?: any[]\n}\n\nexport interface SearchInputProps extends CommonFieldProps {\n /** Optional input className */\n name?: string\n /** Optional placeholder text */\n placeholder?: string\n /** List of input items to search on*/\n searchList: SearchInputItem[]\n /** callback to handle found item click */\n onFound: (element: string) => void\n /** optional callback to run when no results found */\n onNotFound?: (searchTerm: string) => void\n /** optional Component to render when no results found */\n notFoundComponent?: (searchTerm: string) => ReactNode\n /** optional boolean to show search icon */\n showIcon?: boolean\n /** optional boolean to show a clear search button */\n clearSearch?: boolean\n /** Optional callback to run on blur */\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n /** Optional default value for input */\n value?: string\n /** Optional boolean to move results to a realtive position */\n resultsRelativePosition?: boolean\n /** optional boolean to add border to results */\n resultsBorder?: boolean\n /** optional boolean to enable fuzzy search via fuse.js */\n enableFuzzySearch?: boolean\n /** optional config of fuzzy search\n * passing a value to this prop, automatically enables fuzzy search\n */\n fuzzySearchOptions?: IFuseOptions<SearchInputItem>\n}\n\nconst defaultFuzzySearchOptions = {\n keys: [\n {\n name: 'label',\n weight: 0.6,\n },\n {\n name: 'tags',\n weight: 0.4,\n },\n ],\n findAllMatches: true,\n minMatchCharLength: 1,\n location: 0,\n threshold: 0.45,\n distance: 55,\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(\n function SearchInput(\n {\n id: idProp,\n name = 'search_input',\n className = '',\n placeholder,\n searchList,\n showIcon = false,\n renderAsTitle = false,\n value,\n onBlur,\n onFound,\n onNotFound,\n notFoundComponent,\n fallbackStyle,\n resultsRelativePosition = false,\n resultsBorder = true,\n enableFuzzySearch = false,\n fuzzySearchOptions,\n clearSearch,\n ...otherProps\n },\n ref,\n ) {\n const wrapperRef = useRef(null)\n const theme = useTheme()\n const id = useUniqueId(idProp)\n const [showOptions, setShowOptions] = useState(false)\n const [selectedValue, setSelectedValue] = useControllableState<\n string | null\n >({\n initialState: null,\n stateProp: value,\n })\n const [searchQuery, setSearchQuery] = useState<string | null>(null)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n\n const selectedValueLabel = searchList.find(\n (option) =>\n option.label === selectedValue || option.value === selectedValue,\n )?.label\n\n const handleBlur = () => {\n if (selectedValue) {\n setSearchQuery(selectedValueLabel ?? null)\n } else if (!selectedValue) {\n setSearchQuery(null)\n }\n }\n\n useOnClickOutside({\n ref: wrapperRef,\n callback: () => {\n handleBlur()\n setShowOptions(false)\n },\n })\n\n // oxlint-disable-next-line react/react-compiler\n const fuse = useMemo(() => {\n return new Fuse(searchList, {\n ...defaultFuzzySearchOptions,\n ...fuzzySearchOptions,\n })\n }, [searchList])\n\n // oxlint-disable-next-line react/react-compiler\n const filteredList = useMemo(() => {\n if (searchQuery === null || searchQuery === '') {\n return searchList\n }\n\n if (enableFuzzySearch || !!fuzzySearchOptions) {\n return fuse.search(searchQuery).map(({ item }) => item)\n }\n\n return searchList.filter(({ label }) =>\n label.toLowerCase().includes(searchQuery.toLocaleLowerCase()),\n )\n // oxlint-disable-next-line react/react-compiler\n }, [searchQuery, enableFuzzySearch, !!fuzzySearchOptions])\n\n const getDisplayedInputText = () => {\n if (searchQuery !== null) {\n return searchQuery\n }\n if (selectedValue !== null) {\n return selectedValueLabel || ''\n }\n return ''\n }\n\n const isSelected = selectedValue !== null\n const displayedInputText = getDisplayedInputText()\n\n const updateSearchQuery = (query: string | null) => {\n setSearchQuery(query)\n setHighlightedIndex(-1)\n\n if (query === null) {\n setSelectedValue(null)\n setShowOptions(false)\n } else {\n setShowOptions(true)\n }\n }\n\n const handleClick = () => {\n setShowOptions(true)\n if (searchQuery !== null) {\n updateSearchQuery(searchQuery)\n setShowOptions(true)\n } else if (selectedValue !== null && searchQuery === null) {\n setSearchQuery(selectedValueLabel || null)\n setShowOptions(true)\n }\n }\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {\n const nextValue = event.currentTarget.value\n updateSearchQuery(nextValue)\n }\n\n const handleSelect = (nextValue: SearchInputItem): void => {\n updateSearchQuery(null)\n setSelectedValue(nextValue.label)\n onFound(nextValue.value)\n }\n\n const handleClearSearch = () => {\n updateSearchQuery(null)\n setSelectedValue(null)\n onFound('')\n }\n\n const handleCaretClick = () => {\n setShowOptions(!showOptions)\n }\n\n const handleKeyDown = (event: {\n key: string\n preventDefault: () => void\n }) => {\n if (event.key === 'Enter' && highlightedIndex !== -1) {\n event.preventDefault()\n const focusedItem = filteredList[highlightedIndex]\n handleSelect(focusedItem)\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = (highlightedIndex + 1) % filteredList.length\n setHighlightedIndex(nextIndex)\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex =\n (highlightedIndex - 1 + filteredList.length) % filteredList.length\n setHighlightedIndex(prevIndex)\n }\n }\n\n const showClearSearchButton =\n !!clearSearch && (!!value || !!selectedValue || !!searchQuery)\n\n return (\n <Wrapper ref={wrapperRef}>\n <Field\n className={className}\n renderAsTitle={renderAsTitle}\n htmlFor={id}\n {...otherProps}\n >\n <Box flex alignItems=\"center\" justifyContent=\"flex-start\">\n {showIcon && (\n <InputLeadingIconContainer $size={20}>\n <FontAwesomeIcon\n icon={faSearch}\n color={theme.color.text.subtle}\n />\n </InputLeadingIconContainer>\n )}\n <Input\n id={id}\n name={name}\n ref={ref}\n placeholder={placeholder}\n $error={otherProps.error}\n $frontIcon={showIcon}\n $fallbackStyle={fallbackStyle}\n autoComplete=\"off\"\n value={displayedInputText}\n onFocus={handleClick}\n onChange={handleInputChange}\n selected={isSelected}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onBlur={(e) => {\n onBlur?.(e)\n }}\n />\n <Icons\n flex\n alignItems=\"center\"\n gap=\"space.100\"\n $clearSearch={showClearSearchButton}\n >\n {showClearSearchButton && (\n <IconContainer\n title=\"Clear search\"\n onClick={handleClearSearch}\n type=\"button\"\n as=\"button\"\n $size={20}\n style={{ cursor: 'pointer' }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n )}\n <Line />\n <IconContainer\n type=\"button\"\n title=\"icon-button\"\n as=\"button\"\n onClick={handleCaretClick}\n $size={20}\n >\n <FontAwesomeIcon\n style={{\n rotate: showOptions ? '180deg' : '0deg',\n }}\n icon={faChevronDown}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n </Icons>\n </Box>\n\n {showOptions && (\n <SearchOptions\n displayedList={filteredList}\n selectedValue={selectedValue}\n highlightedIndex={highlightedIndex}\n setHighlightedIndex={setHighlightedIndex}\n onKeyDown={handleKeyDown}\n searchTerm={searchQuery || ''}\n onSelect={handleSelect}\n positionRelative={resultsRelativePosition}\n resultsBorder={resultsBorder}\n onNotFound={onNotFound}\n notFoundComponent={notFoundComponent?.(searchQuery ?? '')}\n />\n )}\n </Field>\n </Wrapper>\n )\n },\n)\n\nconst Wrapper = styled(Box)`\n position: relative;\n`\n\nconst Line = styled(Box)`\n background: ${({ theme }) => theme.color.border.subtle};\n height: 24px;\n width: 1px;\n`\n\nconst Icons = styled(Box)<{ $clearSearch: boolean }>`\n position: relative;\n right: ${({ $clearSearch }) => ($clearSearch ? '80px' : '48px')};\n margin-right: ${({ $clearSearch }) => ($clearSearch ? '-80px' : '-48px')};\n`\n"]}
1
+ {"version":3,"file":"SearchInput.js","sourceRoot":"","sources":["../../src/SearchInput/SearchInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import Fuse, { IFuseOptions } from 'fuse.js'\nimport {\n ChangeEvent,\n FocusEvent,\n ReactNode,\n forwardRef,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport styled, { useTheme } from 'styled-components'\nimport { Box } from '../Box'\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n Input,\n InputLeadingIconContainer,\n} from '../fields/components/CommonInput'\nimport { useOnClickOutside } from '../hooks'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { SearchKeyEvent, SearchOptions } from './components/SearchOptions'\nimport { visuallyHidden } from '../utils/visuallyHidden'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faXmark,\n faChevronDown,\n faSearch,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nexport type SearchInputItem = {\n label: string\n value: string\n tags?: any[]\n}\n\nexport interface SearchInputProps extends CommonFieldProps {\n /** Optional input className */\n name?: string\n /** Optional placeholder text */\n placeholder?: string\n /** List of input items to search on*/\n searchList: SearchInputItem[]\n /** callback to handle found item click */\n onFound: (element: string) => void\n /** optional callback to run when no results found */\n onNotFound?: (searchTerm: string) => void\n /** optional Component to render when no results found */\n notFoundComponent?: (searchTerm: string) => ReactNode\n /** optional boolean to show search icon */\n showIcon?: boolean\n /** optional boolean to show a clear search button */\n clearSearch?: boolean\n /** Optional callback to run on blur */\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n /** Optional default value for input */\n value?: string\n /** Optional boolean to move results to a realtive position */\n resultsRelativePosition?: boolean\n /** optional boolean to add border to results */\n resultsBorder?: boolean\n /** optional boolean to enable fuzzy search via fuse.js */\n enableFuzzySearch?: boolean\n /** optional config of fuzzy search\n * passing a value to this prop, automatically enables fuzzy search\n */\n fuzzySearchOptions?: IFuseOptions<SearchInputItem>\n}\n\nconst defaultFuzzySearchOptions = {\n keys: [\n {\n name: 'label',\n weight: 0.6,\n },\n {\n name: 'tags',\n weight: 0.4,\n },\n ],\n findAllMatches: true,\n minMatchCharLength: 1,\n location: 0,\n threshold: 0.45,\n distance: 55,\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(\n function SearchInput(\n {\n id: idProp,\n name = 'search_input',\n className = '',\n placeholder,\n searchList,\n showIcon = false,\n renderAsTitle = false,\n value,\n onBlur,\n onFound,\n onNotFound,\n notFoundComponent,\n fallbackStyle,\n resultsRelativePosition = false,\n resultsBorder = true,\n enableFuzzySearch = false,\n fuzzySearchOptions,\n clearSearch,\n ...otherProps\n },\n ref,\n ) {\n const wrapperRef = useRef(null)\n const theme = useTheme()\n const id = useUniqueId(idProp)\n const [showOptions, setShowOptions] = useState(false)\n const [selectedValue, setSelectedValue] = useControllableState<\n string | null\n >({\n initialState: null,\n stateProp: value,\n })\n const [searchQuery, setSearchQuery] = useState<string | null>(null)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [emptyMessage, setEmptyMessage] = useState('')\n\n const selectedValueLabel = searchList.find(\n (option) =>\n option.label === selectedValue || option.value === selectedValue,\n )?.label\n\n const handleBlur = () => {\n setSearchQuery(null)\n }\n\n // Every way of closing the list goes through here, so a reopened list\n // never starts with a stale highlighted option.\n const closeOptions = () => {\n setShowOptions(false)\n setHighlightedIndex(-1)\n }\n\n useOnClickOutside({\n ref: wrapperRef,\n callback: () => {\n handleBlur()\n closeOptions()\n },\n })\n\n // oxlint-disable-next-line react/react-compiler\n const fuse = useMemo(() => {\n return new Fuse(searchList, {\n ...defaultFuzzySearchOptions,\n ...fuzzySearchOptions,\n })\n }, [searchList])\n\n // oxlint-disable-next-line react/react-compiler\n const filteredList = useMemo(() => {\n if (searchQuery === null || searchQuery === '') {\n return searchList\n }\n\n if (enableFuzzySearch || !!fuzzySearchOptions) {\n return fuse.search(searchQuery).map(({ item }) => item)\n }\n\n return searchList.filter(({ label }) =>\n label.toLowerCase().includes(searchQuery.toLocaleLowerCase()),\n )\n // oxlint-disable-next-line react/react-compiler\n }, [searchQuery, enableFuzzySearch, !!fuzzySearchOptions])\n\n const getDisplayedInputText = () => {\n if (searchQuery !== null) {\n return searchQuery\n }\n if (selectedValue !== null) {\n return selectedValueLabel || ''\n }\n return ''\n }\n\n const isSelected = selectedValue !== null\n const displayedInputText = getDisplayedInputText()\n\n const updateSearchQuery = (query: string | null) => {\n setSearchQuery(query)\n setHighlightedIndex(-1)\n\n if (query === null) {\n setSelectedValue(null)\n closeOptions()\n } else {\n setShowOptions(true)\n }\n }\n\n const handleClick = () => {\n setShowOptions(true)\n if (searchQuery !== null) {\n updateSearchQuery(searchQuery)\n setShowOptions(true)\n }\n }\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {\n const nextValue = event.currentTarget.value\n updateSearchQuery(nextValue)\n }\n\n const handleSelect = (nextValue: SearchInputItem): void => {\n updateSearchQuery(null)\n setSelectedValue(nextValue.label)\n onFound(nextValue.value)\n }\n\n const handleClearSearch = () => {\n updateSearchQuery(null)\n setSelectedValue(null)\n onFound('')\n }\n\n const handleCaretClick = () => {\n if (showOptions) {\n closeOptions()\n } else {\n setShowOptions(true)\n }\n }\n\n const listboxId = `${id}-listbox`\n const getOptionId = (index: number) => `${id}-option-${index}`\n const hasResults = showOptions && filteredList.length > 0\n const hasHighlight =\n hasResults &&\n highlightedIndex >= 0 &&\n highlightedIndex < filteredList.length\n const noResults = showOptions && filteredList.length === 0\n\n const handleKeyDown = (event: SearchKeyEvent) => {\n if (event.key === 'Escape') {\n if (!showOptions) return\n event.preventDefault()\n event.stopPropagation()\n closeOptions()\n } else if (event.key === 'Enter') {\n if (hasHighlight) {\n event.preventDefault()\n handleSelect(filteredList[highlightedIndex])\n }\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showOptions) {\n setShowOptions(true)\n } else if (filteredList.length > 0) {\n setHighlightedIndex((highlightedIndex + 1) % filteredList.length)\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n if (showOptions && filteredList.length > 0) {\n setHighlightedIndex(\n (highlightedIndex - 1 + filteredList.length) % filteredList.length,\n )\n }\n }\n }\n\n const showClearSearchButton =\n !!clearSearch && (!!value || !!selectedValue || !!searchQuery)\n\n return (\n <Wrapper ref={wrapperRef}>\n <Field\n className={className}\n renderAsTitle={renderAsTitle}\n htmlFor={id}\n {...otherProps}\n >\n <Box flex alignItems=\"center\" justifyContent=\"flex-start\">\n {showIcon && (\n <InputLeadingIconContainer $size={20}>\n <FontAwesomeIcon\n icon={faSearch}\n color={theme.color.text.subtle}\n />\n </InputLeadingIconContainer>\n )}\n <Input\n id={id}\n name={name}\n ref={ref}\n placeholder={placeholder}\n $error={otherProps.error}\n $frontIcon={showIcon}\n $fallbackStyle={fallbackStyle}\n autoComplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={hasResults}\n aria-controls={hasResults ? listboxId : undefined}\n aria-activedescendant={\n hasHighlight ? getOptionId(highlightedIndex) : undefined\n }\n value={displayedInputText}\n onFocus={handleClick}\n onChange={handleInputChange}\n selected={isSelected}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onBlur={(e) => {\n onBlur?.(e)\n }}\n />\n <Icons\n flex\n alignItems=\"center\"\n gap=\"space.100\"\n $clearSearch={showClearSearchButton}\n >\n {showClearSearchButton && (\n <IconContainer\n title=\"Clear search\"\n onClick={handleClearSearch}\n type=\"button\"\n as=\"button\"\n $size={20}\n style={{ cursor: 'pointer' }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n )}\n <Line />\n <IconContainer\n type=\"button\"\n title=\"icon-button\"\n as=\"button\"\n onClick={handleCaretClick}\n $size={20}\n >\n <FontAwesomeIcon\n style={{\n rotate: showOptions ? '180deg' : '0deg',\n }}\n icon={faChevronDown}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n </Icons>\n </Box>\n\n {showOptions && (\n <SearchOptions\n displayedList={filteredList}\n selectedValue={selectedValue}\n highlightedIndex={highlightedIndex}\n setHighlightedIndex={setHighlightedIndex}\n onKeyDown={handleKeyDown}\n listboxId={listboxId}\n listboxLabel={otherProps.label ?? placeholder ?? 'Search results'}\n getOptionId={getOptionId}\n searchTerm={searchQuery || ''}\n onSelect={handleSelect}\n positionRelative={resultsRelativePosition}\n resultsBorder={resultsBorder}\n onNotFound={onNotFound}\n onEmptyMessageChange={setEmptyMessage}\n notFoundComponent={notFoundComponent?.(searchQuery ?? '')}\n />\n )}\n </Field>\n <LiveRegion role=\"status\">{noResults ? emptyMessage : ''}</LiveRegion>\n </Wrapper>\n )\n },\n)\n\nconst Wrapper = styled(Box)`\n position: relative;\n`\n\nconst LiveRegion = styled.div`\n ${visuallyHidden}\n`\n\nconst Line = styled(Box)`\n background: ${({ theme }) => theme.color.border.subtle};\n height: 24px;\n width: 1px;\n`\n\nconst Icons = styled(Box)<{ $clearSearch: boolean }>`\n position: relative;\n right: ${({ $clearSearch }) => ($clearSearch ? '80px' : '48px')};\n margin-right: ${({ $clearSearch }) => ($clearSearch ? '-80px' : '-48px')};\n`\n"]}
@@ -1,6 +1,7 @@
1
1
  import { FC, ReactNode } from 'react';
2
2
  interface Props {
3
3
  onNotFound?: (searchTerm: string) => void;
4
+ onMessageChange: (message: string) => void;
4
5
  searchTerm: string;
5
6
  notFoundComponent?: ReactNode;
6
7
  }
@@ -1,12 +1,45 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect } from 'react';
3
- export const EmptyResults = ({ onNotFound, searchTerm, notFoundComponent, }) => {
2
+ import { useEffect, useLayoutEffect, useRef } from 'react';
3
+ const DEFAULT_MESSAGE = 'No results';
4
+ const BLOCK_SELECTOR = 'address, article, aside, blockquote, dd, div, dl, dt, figcaption, footer, h1, h2, h3, h4, h5, h6, header, li, ol, p, section, ul';
5
+ // Reads the element's text as a screen reader would: skipping hidden parts,
6
+ // and adding a space where it moves to another block or line, so a heading
7
+ // and a hint don't run together.
8
+ const getText = (element) => {
9
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
10
+ let text = '';
11
+ let previousBlock = null;
12
+ while (walker.nextNode()) {
13
+ const node = walker.currentNode;
14
+ if (node.nodeName === 'BR') {
15
+ text += ' ';
16
+ continue;
17
+ }
18
+ if (node.nodeType !== Node.TEXT_NODE)
19
+ continue;
20
+ const parent = node.parentElement;
21
+ if (parent?.closest('[aria-hidden="true"], [hidden]'))
22
+ continue;
23
+ const block = parent?.closest(BLOCK_SELECTOR) ?? null;
24
+ if (previousBlock && block !== previousBlock) {
25
+ text += ' ';
26
+ }
27
+ text += node.textContent ?? '';
28
+ previousBlock = block;
29
+ }
30
+ return text.replace(/\s+/g, ' ').trim();
31
+ };
32
+ export const EmptyResults = ({ onNotFound, onMessageChange, searchTerm, notFoundComponent, }) => {
33
+ const ref = useRef(null);
4
34
  useEffect(() => {
5
35
  onNotFound?.(searchTerm);
6
36
  }, []);
7
- if (notFoundComponent) {
8
- return _jsx("li", { children: notFoundComponent });
9
- }
10
- return _jsx("li", { children: "No results" });
37
+ // Report the text people see, so the live region announces a custom
38
+ // notFoundComponent too. A layout effect updates it before paint.
39
+ useLayoutEffect(() => {
40
+ const text = ref.current ? getText(ref.current) : '';
41
+ onMessageChange(text || DEFAULT_MESSAGE);
42
+ }, [searchTerm, notFoundComponent, onMessageChange]);
43
+ return _jsx("li", { ref: ref, children: notFoundComponent || DEFAULT_MESSAGE });
11
44
  };
12
45
  //# sourceMappingURL=EmptyResults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmptyResults.js","sourceRoot":"","sources":["../../../src/SearchInput/components/EmptyResults.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAQhD,MAAM,CAAC,MAAM,YAAY,GAAc,CAAC,EACtC,UAAU,EACV,UAAU,EACV,iBAAiB,GAClB,EAAE,EAAE;IACH,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,EAAE,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,IAAI,iBAAiB,EAAE,CAAC;QACtB,OAAO,uBAAK,iBAAiB,GAAM,CAAA;IACrC,CAAC;IAED,OAAO,sCAAmB,CAAA;AAC5B,CAAC,CAAA","sourcesContent":["import { FC, ReactNode, useEffect } from 'react'\n\ninterface Props {\n onNotFound?: (searchTerm: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nexport const EmptyResults: FC<Props> = ({\n onNotFound,\n searchTerm,\n notFoundComponent,\n}) => {\n useEffect(() => {\n onNotFound?.(searchTerm)\n }, [])\n\n if (notFoundComponent) {\n return <li>{notFoundComponent}</li>\n }\n\n return <li>No results</li>\n}\n"]}
1
+ {"version":3,"file":"EmptyResults.js","sourceRoot":"","sources":["../../../src/SearchInput/components/EmptyResults.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAiB,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzE,MAAM,eAAe,GAAG,YAAY,CAAA;AASpC,MAAM,cAAc,GAClB,kIAAkI,CAAA;AAEpI,4EAA4E;AAC5E,2EAA2E;AAC3E,iCAAiC;AACjC,MAAM,OAAO,GAAG,CAAC,OAAoB,EAAE,EAAE;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CACtC,OAAO,EACP,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,YAAY,CAC/C,CAAA;IACD,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,aAAa,GAAmB,IAAI,CAAA;IACxC,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAA;QAC/B,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,IAAI,GAAG,CAAA;YACX,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS;YAAE,SAAQ;QAE9C,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAA;QACjC,IAAI,MAAM,EAAE,OAAO,CAAC,gCAAgC,CAAC;YAAE,SAAQ;QAE/D,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,IAAI,CAAA;QACrD,IAAI,aAAa,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;YAC7C,IAAI,IAAI,GAAG,CAAA;QACb,CAAC;QACD,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;QAC9B,aAAa,GAAG,KAAK,CAAA;IACvB,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AACzC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAc,CAAC,EACtC,UAAU,EACV,eAAe,EACf,UAAU,EACV,iBAAiB,GAClB,EAAE,EAAE;IACH,MAAM,GAAG,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAA;IAEvC,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,EAAE,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,oEAAoE;IACpE,kEAAkE;IAClE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACpD,eAAe,CAAC,IAAI,IAAI,eAAe,CAAC,CAAA;IAC1C,CAAC,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAA;IAEpD,OAAO,aAAI,GAAG,EAAE,GAAG,YAAG,iBAAiB,IAAI,eAAe,GAAM,CAAA;AAClE,CAAC,CAAA","sourcesContent":["import { FC, ReactNode, useEffect, useLayoutEffect, useRef } from 'react'\n\nconst DEFAULT_MESSAGE = 'No results'\n\ninterface Props {\n onNotFound?: (searchTerm: string) => void\n onMessageChange: (message: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nconst BLOCK_SELECTOR =\n 'address, article, aside, blockquote, dd, div, dl, dt, figcaption, footer, h1, h2, h3, h4, h5, h6, header, li, ol, p, section, ul'\n\n// Reads the element's text as a screen reader would: skipping hidden parts,\n// and adding a space where it moves to another block or line, so a heading\n// and a hint don't run together.\nconst getText = (element: HTMLElement) => {\n const walker = document.createTreeWalker(\n element,\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\n )\n let text = ''\n let previousBlock: Element | null = null\n while (walker.nextNode()) {\n const node = walker.currentNode\n if (node.nodeName === 'BR') {\n text += ' '\n continue\n }\n if (node.nodeType !== Node.TEXT_NODE) continue\n\n const parent = node.parentElement\n if (parent?.closest('[aria-hidden=\"true\"], [hidden]')) continue\n\n const block = parent?.closest(BLOCK_SELECTOR) ?? null\n if (previousBlock && block !== previousBlock) {\n text += ' '\n }\n text += node.textContent ?? ''\n previousBlock = block\n }\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nexport const EmptyResults: FC<Props> = ({\n onNotFound,\n onMessageChange,\n searchTerm,\n notFoundComponent,\n}) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n onNotFound?.(searchTerm)\n }, [])\n\n // Report the text people see, so the live region announces a custom\n // notFoundComponent too. A layout effect updates it before paint.\n useLayoutEffect(() => {\n const text = ref.current ? getText(ref.current) : ''\n onMessageChange(text || DEFAULT_MESSAGE)\n }, [searchTerm, notFoundComponent, onMessageChange])\n\n return <li ref={ref}>{notFoundComponent || DEFAULT_MESSAGE}</li>\n}\n"]}
@@ -3,19 +3,25 @@ type Option = {
3
3
  label: string;
4
4
  value: string;
5
5
  };
6
+ export type SearchKeyEvent = {
7
+ key: string;
8
+ preventDefault: () => void;
9
+ stopPropagation: () => void;
10
+ };
6
11
  type SearchOptionsProps = {
7
12
  displayedList: Array<Option>;
8
13
  selectedValue: string | null;
9
14
  highlightedIndex: number;
10
15
  setHighlightedIndex: (arg: number) => void;
11
16
  onSelect: (option: Option) => void;
12
- onKeyDown: (e: {
13
- key: string;
14
- preventDefault: () => void;
15
- }) => void;
17
+ onKeyDown: (e: SearchKeyEvent) => void;
18
+ listboxId: string;
19
+ listboxLabel: string;
20
+ getOptionId: (index: number) => string;
16
21
  positionRelative: boolean;
17
22
  resultsBorder: boolean;
18
23
  onNotFound?: (searchTerm: string) => void;
24
+ onEmptyMessageChange: (message: string) => void;
19
25
  searchTerm: string;
20
26
  notFoundComponent?: ReactNode;
21
27
  };
@@ -4,7 +4,7 @@ import styled, { css } from 'styled-components';
4
4
  import { Box } from '../../Box';
5
5
  import { Icon } from '../../Icon';
6
6
  import { EmptyResults } from './EmptyResults';
7
- export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex, setHighlightedIndex, onSelect, onKeyDown, positionRelative, resultsBorder, onNotFound, searchTerm, notFoundComponent, }) => {
7
+ export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex, setHighlightedIndex, onSelect, onKeyDown, listboxId, listboxLabel, getOptionId, positionRelative, resultsBorder, onNotFound, onEmptyMessageChange, searchTerm, notFoundComponent, }) => {
8
8
  const itemRefs = useRef([]);
9
9
  const resultsListRef = useRef(null);
10
10
  useEffect(() => {
@@ -25,16 +25,16 @@ export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex,
25
25
  }
26
26
  setHighlightedIndex(-1);
27
27
  }, [searchTerm, setHighlightedIndex]);
28
- return (_jsx(BoxWithPositionRelative, { children: _jsx(StyledResultsContainer, { $positionRelative: positionRelative, children: _jsx(ResultsList, { ref: resultsListRef, $resultsBorder: resultsBorder, onKeyDown: onKeyDown, children: displayedList.length ? (
28
+ return (_jsx(BoxWithPositionRelative, { children: _jsx(StyledResultsContainer, { $positionRelative: positionRelative, children: _jsx(ResultsList, { ref: resultsListRef, id: listboxId, role: displayedList.length ? 'listbox' : undefined, "aria-label": displayedList.length ? listboxLabel : undefined, $resultsBorder: resultsBorder, onKeyDown: onKeyDown, children: displayedList.length ? (
29
29
  // oxlint-disable-next-line react/react-compiler
30
30
  displayedList.map((el, i) => {
31
31
  const isSelected = selectedValue === el.label || selectedValue === el.value;
32
- return (_jsxs(ListButton, { "aria-label": el.label + '_list_item', ref: itemRefs.current[i], onClick: () => onSelect(el), $isSelected: isSelected, $showBg: highlightedIndex === i, onMouseEnter: () => {
32
+ return (_jsxs(ListButton, { id: getOptionId(i), role: "option", "aria-selected": highlightedIndex === i, ref: itemRefs.current[i], onClick: () => onSelect(el), $isSelected: isSelected, $showBg: highlightedIndex === i, onMouseEnter: () => {
33
33
  setHighlightedIndex(i);
34
34
  }, onFocus: () => {
35
35
  setHighlightedIndex(i);
36
36
  }, children: [el.label, isSelected && (_jsx(Icon, { render: "tick", size: 16, color: "color.surface.brand.300" }))] }, el.label + '_list_item'));
37
- })) : (_jsx(EmptyResults, { onNotFound: onNotFound, searchTerm: searchTerm, notFoundComponent: notFoundComponent })) }) }) }));
37
+ })) : (_jsx(EmptyResults, { onNotFound: onNotFound, onMessageChange: onEmptyMessageChange, searchTerm: searchTerm, notFoundComponent: notFoundComponent })) }) }) }));
38
38
  };
39
39
  const StyledResultsContainer = styled.div `
40
40
  box-sizing: border-box;
@@ -1 +1 @@
1
- {"version":3,"file":"SearchOptions.js","sourceRoot":"","sources":["../../../src/SearchInput/components/SearchOptions.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, RefObject, createRef, useEffect, useRef } from 'react'\nimport styled, { css } from 'styled-components'\nimport { TransientProps } from '../../utils/utilTypes'\nimport { Box } from '../../Box'\nimport { Icon } from '../../Icon'\nimport { EmptyResults } from './EmptyResults'\n\ntype Option = {\n label: string\n value: string\n}\n\ntype SearchOptionsProps = {\n displayedList: Array<Option>\n selectedValue: string | null\n highlightedIndex: number\n setHighlightedIndex: (arg: number) => void\n onSelect: (option: Option) => void\n onKeyDown: (e: { key: string; preventDefault: () => void }) => void\n positionRelative: boolean\n resultsBorder: boolean\n onNotFound?: (searchTerm: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nexport const SearchOptions: FC<SearchOptionsProps> = ({\n displayedList,\n selectedValue,\n highlightedIndex,\n setHighlightedIndex,\n onSelect,\n onKeyDown,\n positionRelative,\n resultsBorder,\n onNotFound,\n searchTerm,\n notFoundComponent,\n}) => {\n const itemRefs = useRef<RefObject<HTMLLIElement | null>[]>([])\n const resultsListRef = useRef<HTMLUListElement>(null)\n\n useEffect(() => {\n itemRefs.current = displayedList.map(\n (_, i) => itemRefs.current[i] ?? createRef<HTMLLIElement>(),\n )\n }, [displayedList.length])\n\n useEffect(() => {\n const focusedListItem = itemRefs?.current[highlightedIndex]?.current\n if (highlightedIndex >= 0 && focusedListItem) {\n focusedListItem.scrollIntoView({\n block: 'nearest',\n inline: 'start',\n })\n }\n }, [highlightedIndex])\n\n useEffect(() => {\n if (resultsListRef.current) {\n resultsListRef.current.scrollTop = 0\n }\n setHighlightedIndex(-1)\n }, [searchTerm, setHighlightedIndex])\n\n return (\n <BoxWithPositionRelative>\n <StyledResultsContainer $positionRelative={positionRelative}>\n <ResultsList\n ref={resultsListRef}\n $resultsBorder={resultsBorder}\n onKeyDown={onKeyDown}\n >\n {displayedList.length ? (\n // oxlint-disable-next-line react/react-compiler\n displayedList.map((el, i) => {\n const isSelected =\n selectedValue === el.label || selectedValue === el.value\n\n return (\n <ListButton\n key={el.label + '_list_item'}\n aria-label={el.label + '_list_item'}\n ref={itemRefs.current[i]}\n onClick={() => onSelect(el)}\n $isSelected={isSelected}\n $showBg={highlightedIndex === i}\n onMouseEnter={() => {\n setHighlightedIndex(i)\n }}\n onFocus={() => {\n setHighlightedIndex(i)\n }}\n >\n {el.label}\n {isSelected && (\n <Icon\n render=\"tick\"\n size={16}\n color=\"color.surface.brand.300\"\n />\n )}\n </ListButton>\n )\n })\n ) : (\n <EmptyResults\n onNotFound={onNotFound}\n searchTerm={searchTerm}\n notFoundComponent={notFoundComponent}\n />\n )}\n </ResultsList>\n </StyledResultsContainer>\n </BoxWithPositionRelative>\n )\n}\n\nconst StyledResultsContainer = styled.div<\n TransientProps<Pick<SearchOptionsProps, 'positionRelative'>>\n>`\n box-sizing: border-box;\n overflow: hidden;\n margin: -16px;\n padding: ${({ theme }) => theme.space[200]};\n ${({ $positionRelative }) => !$positionRelative && 'position: absolute;'}\n width: calc(100% + 32px);\n left: 0px;\n top: -8px;\n\n ul {\n max-height: 192px;\n }\n`\n\nconst ResultsList = styled.ul<\n TransientProps<Pick<SearchOptionsProps, 'resultsBorder'>>\n>`\n position: relative;\n list-style: none;\n overflow-y: auto;\n padding: 0;\n margin: 0;\n background-color: ${({ theme }) => theme.color.background['000']};\n border-radius: 12px;\n margin-top: 14px;\n z-index: 1000;\n border: 1px solid ${({ theme }) => theme.color.border.subtle};\n box-shadow:\n 0px 10px 18px rgba(18, 18, 23, 0.03),\n 0px 5px 8px rgba(18, 18, 23, 0.04),\n 0px 2px 4px rgba(18, 18, 23, 0.04);\n\n li {\n padding: 16px 14px;\n box-sizing: border-box;\n font-size: 16px;\n color: ${({ theme }) => theme.color.text.base};\n cursor: pointer;\n }\n`\n\nconst ListButton = styled.li<{ $isSelected: boolean; $showBg: boolean }>`\n display: flex;\n justify-content: space-between;\n\n &:focus {\n outline: none;\n }\n &:focus-visible {\n outline: none;\n }\n\n ${({ $showBg }) =>\n $showBg &&\n css`\n background-color: ${({ theme }) => theme.color.background[200]};\n `}\n\n ${({ $isSelected }) =>\n $isSelected &&\n css`\n background-color: ${({ theme }) => theme.color.surface.base[300]};\n `}\n`\n\nconst BoxWithPositionRelative = styled(Box)`\n position: relative;\n`\n"]}
1
+ {"version":3,"file":"SearchOptions.js","sourceRoot":"","sources":["../../../src/SearchInput/components/SearchOptions.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, RefObject, createRef, useEffect, useRef } from 'react'\nimport styled, { css } from 'styled-components'\nimport { TransientProps } from '../../utils/utilTypes'\nimport { Box } from '../../Box'\nimport { Icon } from '../../Icon'\nimport { EmptyResults } from './EmptyResults'\n\ntype Option = {\n label: string\n value: string\n}\n\nexport type SearchKeyEvent = {\n key: string\n preventDefault: () => void\n stopPropagation: () => void\n}\n\ntype SearchOptionsProps = {\n displayedList: Array<Option>\n selectedValue: string | null\n highlightedIndex: number\n setHighlightedIndex: (arg: number) => void\n onSelect: (option: Option) => void\n onKeyDown: (e: SearchKeyEvent) => void\n listboxId: string\n listboxLabel: string\n getOptionId: (index: number) => string\n positionRelative: boolean\n resultsBorder: boolean\n onNotFound?: (searchTerm: string) => void\n onEmptyMessageChange: (message: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nexport const SearchOptions: FC<SearchOptionsProps> = ({\n displayedList,\n selectedValue,\n highlightedIndex,\n setHighlightedIndex,\n onSelect,\n onKeyDown,\n listboxId,\n listboxLabel,\n getOptionId,\n positionRelative,\n resultsBorder,\n onNotFound,\n onEmptyMessageChange,\n searchTerm,\n notFoundComponent,\n}) => {\n const itemRefs = useRef<RefObject<HTMLLIElement | null>[]>([])\n const resultsListRef = useRef<HTMLUListElement>(null)\n\n useEffect(() => {\n itemRefs.current = displayedList.map(\n (_, i) => itemRefs.current[i] ?? createRef<HTMLLIElement>(),\n )\n }, [displayedList.length])\n\n useEffect(() => {\n const focusedListItem = itemRefs?.current[highlightedIndex]?.current\n if (highlightedIndex >= 0 && focusedListItem) {\n focusedListItem.scrollIntoView({\n block: 'nearest',\n inline: 'start',\n })\n }\n }, [highlightedIndex])\n\n useEffect(() => {\n if (resultsListRef.current) {\n resultsListRef.current.scrollTop = 0\n }\n setHighlightedIndex(-1)\n }, [searchTerm, setHighlightedIndex])\n\n return (\n <BoxWithPositionRelative>\n <StyledResultsContainer $positionRelative={positionRelative}>\n <ResultsList\n ref={resultsListRef}\n id={listboxId}\n role={displayedList.length ? 'listbox' : undefined}\n aria-label={displayedList.length ? listboxLabel : undefined}\n $resultsBorder={resultsBorder}\n onKeyDown={onKeyDown}\n >\n {displayedList.length ? (\n // oxlint-disable-next-line react/react-compiler\n displayedList.map((el, i) => {\n const isSelected =\n selectedValue === el.label || selectedValue === el.value\n\n return (\n <ListButton\n key={el.label + '_list_item'}\n id={getOptionId(i)}\n role=\"option\"\n aria-selected={highlightedIndex === i}\n ref={itemRefs.current[i]}\n onClick={() => onSelect(el)}\n $isSelected={isSelected}\n $showBg={highlightedIndex === i}\n onMouseEnter={() => {\n setHighlightedIndex(i)\n }}\n onFocus={() => {\n setHighlightedIndex(i)\n }}\n >\n {el.label}\n {isSelected && (\n <Icon\n render=\"tick\"\n size={16}\n color=\"color.surface.brand.300\"\n />\n )}\n </ListButton>\n )\n })\n ) : (\n <EmptyResults\n onNotFound={onNotFound}\n onMessageChange={onEmptyMessageChange}\n searchTerm={searchTerm}\n notFoundComponent={notFoundComponent}\n />\n )}\n </ResultsList>\n </StyledResultsContainer>\n </BoxWithPositionRelative>\n )\n}\n\nconst StyledResultsContainer = styled.div<\n TransientProps<Pick<SearchOptionsProps, 'positionRelative'>>\n>`\n box-sizing: border-box;\n overflow: hidden;\n margin: -16px;\n padding: ${({ theme }) => theme.space[200]};\n ${({ $positionRelative }) => !$positionRelative && 'position: absolute;'}\n width: calc(100% + 32px);\n left: 0px;\n top: -8px;\n\n ul {\n max-height: 192px;\n }\n`\n\nconst ResultsList = styled.ul<\n TransientProps<Pick<SearchOptionsProps, 'resultsBorder'>>\n>`\n position: relative;\n list-style: none;\n overflow-y: auto;\n padding: 0;\n margin: 0;\n background-color: ${({ theme }) => theme.color.background['000']};\n border-radius: 12px;\n margin-top: 14px;\n z-index: 1000;\n border: 1px solid ${({ theme }) => theme.color.border.subtle};\n box-shadow:\n 0px 10px 18px rgba(18, 18, 23, 0.03),\n 0px 5px 8px rgba(18, 18, 23, 0.04),\n 0px 2px 4px rgba(18, 18, 23, 0.04);\n\n li {\n padding: 16px 14px;\n box-sizing: border-box;\n font-size: 16px;\n color: ${({ theme }) => theme.color.text.base};\n cursor: pointer;\n }\n`\n\nconst ListButton = styled.li<{ $isSelected: boolean; $showBg: boolean }>`\n display: flex;\n justify-content: space-between;\n\n &:focus {\n outline: none;\n }\n &:focus-visible {\n outline: none;\n }\n\n ${({ $showBg }) =>\n $showBg &&\n css`\n background-color: ${({ theme }) => theme.color.background[200]};\n `}\n\n ${({ $isSelected }) =>\n $isSelected &&\n css`\n background-color: ${({ theme }) => theme.color.surface.base[300]};\n `}\n`\n\nconst BoxWithPositionRelative = styled(Box)`\n position: relative;\n`\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrshmllw/smores-react",
3
- "version": "16.9.0",
3
+ "version": "16.10.1",
4
4
  "description": "Collection of React components used by Marshmallow Technology",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -68,7 +68,7 @@
68
68
  "devDependencies": {
69
69
  "@commitlint/cli": "^21.2.3",
70
70
  "@mrshmllw/campfire": "^7.1.0",
71
- "@snyk/protect": "^1.1307.4",
71
+ "@snyk/protect": "^1.1308.0",
72
72
  "@storybook/addon-a11y": "^10.6.1",
73
73
  "@storybook/addon-coverage": "^3.0.2",
74
74
  "@storybook/addon-docs": "^10.6.1",
@@ -82,7 +82,7 @@
82
82
  "@types/react": "^19.3.0",
83
83
  "@types/react-dom": "^19.3.0",
84
84
  "axe-playwright": "^2.2.2",
85
- "concurrently": "^10.0.5",
85
+ "concurrently": "^10.0.6",
86
86
  "eslint-plugin-prettier": "^5.5.6",
87
87
  "husky": "^9.1.7",
88
88
  "jest-styled-components": "^7.4.0",