Skip to content

Commit

Permalink
Prototype new combobox variant
Browse files Browse the repository at this point in the history
  • Loading branch information
kyledurand committed Jan 17, 2024
1 parent 966d190 commit 116da55
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 15 deletions.
48 changes: 34 additions & 14 deletions polaris-react/playground/DetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ import {
TopBar,
FooterHelp,
Link,
Card,
BlockStack,
InlineGrid,
Icon,
Tooltip,
} from '../src';
import type {DropZoneProps, PageProps} from '../src';

import {Select as NewSelect} from './components';
import styles from './DetailsPage.module.scss';

export function DetailsPage() {
Expand Down Expand Up @@ -549,7 +555,6 @@ export function DetailsPage() {
// ---- Page markup ----
const actualPageMarkup = (
<Page
fullWidth
backAction={{content: 'Products', url: '/products/31'}}
title={title}
titleMetadata={<Badge tone="success">Success badge</Badge>}
Expand Down Expand Up @@ -626,25 +631,40 @@ export function DetailsPage() {
</LegacyCard>
</Layout.Section>
<Layout.Section variant="oneThird">
<LegacyCard title="Organization">
<LegacyCard.Section>
<Select
label="Product type"
options={options}
onChange={setSelected}
value={selected}
<Card>
<BlockStack gap="200">
<InlineGrid columns="1fr auto" alignItems="center">
<Text as="h2" variant="headingSm">
Organization
</Text>
<Tooltip content="Learn more">
<Icon source={InfoMinor} tone="subdued" />
</Tooltip>
</InlineGrid>
<NewSelect
title="Category"
defaultSelected={['2345678']}
options={[
{value: '0123456', label: 'Ambiance'},
{value: '1234567', label: 'Atomic'},
{value: '2345678', label: 'Bedroom'},
{value: '3456789', label: 'Danish'},
{value: '4567890', label: 'Essence'},
{value: '5678901', label: 'Featured'},
{value: '6789012', label: 'Mid-century'},
]}
/>
<br />
<NewSelect title="Type" />
<NewSelect title="Vendor" />
{/* Used for reference */}
<Select
label="Vendor"
label="Product type"
options={options}
onChange={setSelected}
value={selected}
/>
</LegacyCard.Section>
<LegacyCard.Section title="Collections" />
<LegacyCard.Section title="Tags" />
</LegacyCard>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
Expand Down
111 changes: 111 additions & 0 deletions polaris-react/playground/components/Options/Options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React, {useState, useCallback, useMemo} from 'react';

import {Tag, Listbox, Combobox, InlineStack} from '../../../src';

export function Options() {
const deselectedOptions = useMemo(
() => [
{value: 'rustic', label: 'Rustic'},
{value: 'antique', label: 'Antique'},
{value: 'vinyl', label: 'Vinyl'},
{value: 'vintage', label: 'Vintage'},
{value: 'refurbished', label: 'Refurbished'},
],
[],
);

const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
const [inputValue, setInputValue] = useState('');
const [options, setOptions] = useState(deselectedOptions);

const updateText = useCallback(
(value: string) => {
setInputValue(value);

if (value === '') {
setOptions(deselectedOptions);
return;
}

const filterRegex = new RegExp(value, 'i');
const resultOptions = deselectedOptions.filter((option) =>
option.label.match(filterRegex),
);
setOptions(resultOptions);
},
[deselectedOptions],
);

const updateSelection = useCallback(
(selected: string) => {
if (selectedOptions.includes(selected)) {
setSelectedOptions(
selectedOptions.filter((option) => option !== selected),
);
} else {
setSelectedOptions([...selectedOptions, selected]);
}

updateText('');
},
[selectedOptions, updateText],
);

const removeTag = useCallback(
(tag: string) => () => {
const options = [...selectedOptions];
options.splice(options.indexOf(tag), 1);
setSelectedOptions(options);
},
[selectedOptions],
);

const tagsMarkup = selectedOptions.map((option) => (
<Tag key={`option-${option}`} onRemove={removeTag(option)}>
{option}
</Tag>
));

const optionsMarkup =
options.length > 0
? options.map((option) => {
const {label, value} = option;

return (
<Listbox.Option
key={`${value}`}
value={value}
selected={selectedOptions.includes(value)}
accessibilityLabel={label}
>
{label}
</Listbox.Option>
);
})
: null;

return (
<div style={{height: '225px'}}>
<Combobox
allowMultiple
persistent
activator={
<Combobox.TextField
onChange={updateText}
label="Search tags"
labelHidden
value={inputValue}
placeholder="Search tags"
autoComplete="off"
/>
}
>
{optionsMarkup ? (
<Listbox onSelect={updateSelection}>{optionsMarkup}</Listbox>
) : null}
</Combobox>

<InlineStack gap="100">{tagsMarkup}</InlineStack>
</div>
);
}
31 changes: 31 additions & 0 deletions polaris-react/playground/components/Select/Select.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.Select {
padding: calc(var(--p-space-200) + var(--p-space-050)) var(--p-space-200)
var(--p-space-200) var(--p-space-300);
border-radius: var(--p-border-radius-200);
border: var(--p-border-width-0165) solid var(--p-color-input-border);
background-color: var(--Color-input-bg-surface);
width: 100%;

&:focus:not(:active) {
outline: var(--p-border-width-050) solid var(--p-color-border-focus);
outline-offset: var(--p-border-width-025);
}

&:hover {
border-color: var(--p-color-input-border-hover);
background-color: var(--Color-input-bg-surface-hover);

svg {
fill: var(--p-color-icon-hover);
}
}

&:active {
border-color: var(--p-color-input-border-active);
background-color: var(--Color-input-bg-surface-active);

svg {
fill: var(--p-color-icon-active);
}
}
}
67 changes: 67 additions & 0 deletions polaris-react/playground/components/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import {SelectMinor} from '@shopify/polaris-icons';

import type {OptionListProps} from '../../../src';
import {
BlockStack,
Icon,
Text,
InlineGrid,
UnstyledButton,
Popover,
} from '../../../src';
import {Options} from '../Options/Options';

import styles from './Select.module.scss';

interface Props {
title: string;
defaultSelected?: string[];
}

export function Select({
title,
defaultSelected,
options,
}: Props & Pick<OptionListProps, 'options'>) {
const [active, setActive] = React.useState(false);
// const [selected, setSelected] = React.useState(defaultSelected ?? []);

const firstSelected = options?.find(
(option) => option.value === defaultSelected?.[0],
)?.label;

const activator = (
<UnstyledButton
onClick={() => setActive((active) => !active)}
className={styles.Select}
>
<InlineGrid columns="1fr auto" alignItems="center">
<BlockStack gap="100" inlineAlign="start">
<Text as="h3" variant="bodySm" tone="subdued">
{title}
</Text>
{defaultSelected?.length && (
<Text as="span" variant="bodyMd">
{firstSelected}
</Text>
)}
</BlockStack>
<Icon source={SelectMinor} tone="subdued" />
</InlineGrid>
</UnstyledButton>
);

return (
<Popover
fullWidth
active={active}
onClose={() => setActive(false)}
// TODO: Allow coverage of activator
// preferredAlignment="cover"
activator={activator}
>
<Options />
</Popover>
);
}
2 changes: 2 additions & 0 deletions polaris-react/playground/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Select/Select';
export * from './Options/Options';
22 changes: 21 additions & 1 deletion polaris-react/src/components/Combobox/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type {
ComboboxListboxType,
ComboboxListboxOptionType,
} from '../../utilities/combobox';
import {BlockStack} from '../BlockStack';
import {Box} from '../Box';

import styles from './Combobox.module.scss';
import {TextField} from './components';
Expand All @@ -31,6 +33,7 @@ export interface ComboboxProps {
willLoadMoreOptions?: boolean;
/** Height to set on the Popover Pane. */
height?: string;
persistent?: boolean;
/** Callback fired when the bottom of the lisbox is reached. Use to lazy load when listbox option data is paginated. */
onScrolledToBottom?(): void;
/** Callback fired when the popover closes */
Expand All @@ -44,6 +47,7 @@ export function Combobox({
preferredPosition = 'below',
willLoadMoreOptions,
height,
persistent,
onScrolledToBottom,
onClose,
}: ComboboxProps) {
Expand Down Expand Up @@ -148,7 +152,23 @@ export function Combobox({
],
);

return (
return persistent ? (
<>
<Box paddingBlockStart="200" paddingInline="200">
<ComboboxTextFieldContext.Provider value={textFieldContextValue}>
{activator}
</ComboboxTextFieldContext.Provider>
</Box>

<ComboboxListboxContext.Provider value={listboxContextValue}>
<ComboboxListboxOptionContext.Provider
value={listboxOptionContextValue}
>
<div className={styles.Listbox}>{children}</div>
</ComboboxListboxOptionContext.Provider>
</ComboboxListboxContext.Provider>
</>
) : (
<Popover
ref={ref}
active={popoverActive}
Expand Down

0 comments on commit 116da55

Please sign in to comment.