Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Komp-355-searchAsync #635

Merged
merged 5 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/empty-readers-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kvib/react": patch
---

SearchAsync - Legger til to nye props: isDisabled og focusBorderColor.
189 changes: 163 additions & 26 deletions apps/storybook/stories/components/search/search-async/SearchAsync.mdx
Original file line number Diff line number Diff line change
@@ -1,33 +1,152 @@
import { Meta, Canvas, Story, Controls, Source } from "@storybook/blocks";
import * as SearchAsyncStories from "./SearchAsync.stories";
import { DocsHeading, DocsAnatomy, DocsContainer, DocsStory, Feedback } from "../../../templates";
import { Text, Code, Box, Link, UnorderedList, ListItem } from "@kvib/react/src/";

<Meta of={SearchAsyncStories} />

# SearchAsync

SearchAsync er et søkefelt som laster inn søkeresultater fra en liste med objekter. Resultatene blir vist i en dropdown der brukeren kan velge hvilket resultat som skal bli valgt.

## Ta i bruk

```jsx
import { SearchAsync } from "@kvib/react";
```

## Props

<Canvas of={SearchAsyncStories.SearchAsync} />
<Controls of={SearchAsyncStories.SearchAsync} />

## Async søkeresultater
<DocsContainer>

**Egnet til:**

- Raskt finne relevant innhold
- Laste inn innhold asynkront
- Select med søk

**Uegnet til:**

- Innhold som lett kan navigeres til uten søk

<Feedback component="SearchAsync" />

</DocsContainer>

<DocsHeading>Alternativer</DocsHeading>
<DocsContainer>

<DocsStory
title="Required props"
description={
<Box>
<Text as="b">loadOptions</Text>
<Text>SearchAsync komponentens prop <Code>loadOptions</Code> er en funksjon ment for asynkron lasting av alternativer basert på brukerinput i inputfeltet. Den tar to parametere:</Text>
<UnorderedList>
<ListItem>
<Text><Code>inputValue</Code>: Teksten brukeren skriver inn.</Text>
</ListItem>
<ListItem>
<Text>
<Code>callback</Code>: En funksjon som tar en liste av objekter (<Code>options</Code>) og brukes til å returnere søkeresultatene. Hvert objekt i <Code>options</Code> må ha en <Code>label</Code> (tekst vist i dropdown) og en <Code>value</Code> (verdien returnert ved valg).
</Text>
</ListItem>
</UnorderedList>

<Text as="b">onChange</Text>
<Text><Code>onChange</Code> er en funksjon som håndterer endringer i valgt verdi. Den tar ett parameter:</Text>
<UnorderedList>
<ListItem>
<Text>
<Code>newValue</Code>: Den nye verdien som er valgt av brukeren. Denne verdien er av typen <Code>T</Code> eller <Code>null</Code>. <Code>T</Code> representerer typen av objektene i dine <Code>options</Code>, og <Code>null</Code> brukes når det ikke er noen valgt verdi.
</Text>
</ListItem>
</UnorderedList>
</Box>
}
story={<Canvas of={SearchAsyncStories.SearchAsyncResults} />}
/>

<Canvas of={SearchAsyncStories.SearchAsyncResults} />
<DocsStory
title="Utseende"
description={
<Box>
<Text>
SearchAsync har fire varianter. <Code>outline</Code> er standardvarianten og skal brukes i de fleste tilfeller.
<Code>filled</Code> kan brukes som et alternativ til outline. For mindre viktige søkefelt kan
<Code>flushed</Code> og
<Code>unstyled</Code> brukes.
</Text>
<Text>
Det er mulig å velge mellom tre forskjellige størrelser: <Code>sm</Code>, <Code>md</Code> og <Code>lg</Code>.
<Code>md</Code> er standard.
</Text>
<Text>
For å endre borderColor når søkefeltet er i fokus kan du sette farge med color-token (f.eks. green.500) i{" "}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hva er default-farge for focusBorderColor? Siden ikke vi har kodet noe for det selv, så gjetter jeg at det er blå eller grønn avhengig av valgt farge for prosjektet. Det kan kanskje legges til en setning om det hvis det er tilfelle. I så fall er det jo veldig sjelden at det vil være behov for å endre fargen.

<Code>focusBorderColor</Code>.
</Text>
</Box>
}
story={<Canvas of={SearchAsyncStories.SearchAsyncVariants} />}
/>

## Egendefinert tilbakemelding ved ingen resultater
<DocsStory
title="Standardalternativer"
description={
<Box>
<Text>
SearchAsync har mulighet for å legge inn standardalternativer for resultater ved hjelp av proppen{" "}
<Code>defaultOptions</Code>. Det betyr at brukeren vil få muligheten til å velge resultat basert på en
dropdown-liste i komponenten, i tillegg til å kunne søke. Du kan velge mellom å inkludere alle søkealternativer
som standardalternativer (<Code>defaultOptions="true"</Code>), eller bare legge til noen (
<Code>defaultOptions=["eple", "banan"]</Code>). Dersom det forekommer et høyt antall av søkealternativer
anbefales det å ikke legge til alle.
</Text>
<Text>
Legg til et <Code>Icon</Code> i <Code>dropDownIndicator</Code> for å poengtere at det er en dropdown, dersom det
er ønskelig.
</Text>
</Box>
}
story={<Canvas of={SearchAsyncStories.SearchAsyncDropdown} />}
/>

Det er mulig å endre på tilbakemeldingen som dukker opp i dropdown ved å bruke propen `noOptionsMessage`. Denne tar inn en funksjon som returnerer en React-komponent. Funksjonen tar inn et objekt med `inputValue` som inneholder det brukeren har skrevet i søkefeltet.
Kodeblokken under eksempelet viser hvordan man kan bruke `noOptionsMessage` og `loadOptions` til å sørge for at brukeren skriver inn minst tre tegn før vi starter et søk.
<DocsStory
title="Multi"
description={
<Box>
<Text>
Det er mulig å la brukeren velge flere resultater med <Code>isMulti</Code>. Her er det også mulig å legge inn
standardalternativer, slik at brukeren kan velge fra dropdown - som i eksempelet.
</Text>
<Text>
Typen til
<Code>onChange</Code> endrer seg ved <Code>isMulti=true</Code> for å kunne håndtere flere resultater: <Code>{`(newValue: readonly T[] | null,`}</Code> <Code>{`actionMeta: ActionMeta<T>) => void`}</Code>

.<Code>newValue</Code> er en liste med valgte resultater, og <Code>actionMeta</Code> er en liste med objekter
som inneholder informasjon om handlingen som ble utført. Disse to blir logget til konsollen i eksempelet,
inspiser siden for å se.
</Text>
</Box>

}
story={<Canvas of={SearchAsyncStories.SearchAsyncMultiDropdown} />}
/>

<Canvas of={SearchAsyncStories.SearchAsyncNoOptions} />
<DocsStory
title="Egendefinert feedback ved ingen resultater"
description={
<Box>
<Text>
Det er mulig å endre på tilbakemeldingen som dukker opp i dropdown ved å bruke propen{" "}
<Code>noOptionsMessage</Code>. Denne tar inn en funksjon som returnerer en React-komponent. Funksjonen tar inn
et objekt med <Code>inputValue</Code> som inneholder det brukeren har skrevet i søkefeltet.
</Text>
<Text>
Kodeblokken under viser hvordan man kan bruke
<Code>noOptionsMessage</Code> og <Code>loadOptions</Code> til å sørge for at brukeren skriver inn minst tre tegn
før vi starter et søk.
</Text>
</Box>
}
story={<Canvas of={SearchAsyncStories.SearchAsyncNoOptions} />}
/>

<Source
code={`const mockLoadOptions = (inputValue: string, callback: (options: typeof fruits) => void) => {
Expand All @@ -53,26 +172,44 @@ const noOptionsMessage = ({ inputValue }: { inputValue: string }) => {
dark
/>

## Søkeresultater med delay
</DocsContainer>

<Canvas of={SearchAsyncStories.SearchAsyncResultsDebounce} />
<DocsHeading>Retningslinjer</DocsHeading>

## Standardalternativer
<DocsContainer>

<Canvas inline of={SearchAsyncStories.SearchAsyncDropdown} />
### Bredde

## Multi
Søkefeltets bredde bør tilsvare lengden på søkeordene som brukes. Feltets størrelse er et viktig signal til brukeren om hva de kan skrive inn. Et søkefelt som brukes til å søke på personnumre bør ha en bredde som tilsvarer et personnummer, mens hovedsøket for et nettsted bør ha et bredere felt som lar brukerne se flere ord samtidig.

<Canvas of={SearchAsyncStories.SearchAsyncMulti} />
Ta gjerne en kikk på søkeloggene dine for å se hvordan brukerne dine ordlegger seg når de søker på sidene dine. Unngå at teksten i søkefeltet må scrolles for å romme typiske søk.

## Multi med dropdown
### Innsending av query

<Canvas of={SearchAsyncStories.SearchAsyncMultiDropdown} />
Det er anbefalt å bruke `<form>` rundt SearchAsync dersom du skal bruke det valgte resultatet. Dette gjør det mulig å bruke `<button type="submit">` for å sende inn søket.
`FormControl` kan hjelpe til med å gi SearchAsync kontekst.

## Størrelser

<Canvas of={SearchAsyncStories.SearchAsyncSizes} />
<Source
code={`
<form onSubmit={handleSubmit}>
<FormControl>
<FormLabel htmlFor="async-search">Søk etter frukt</FormLabel>
<SearchAsync
loadOptions={mockLoadOptions}
onChange={handleChange}
placeholder="Søk etter frukt..."
isMulti={false}
/>
<Button mt={4} colorScheme="blue" type="submit">
Send
</Button>
</FormControl>
</form>`}
dark
/>

## Varianter
</DocsContainer>

<Canvas of={SearchAsyncStories.SearchAsyncVariants} />
<DocsHeading>Props</DocsHeading>
<Canvas of={SearchAsyncStories.SearchAsync} />
<Controls of={SearchAsyncStories.SearchAsync} />
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const meta: Meta<typeof KvibSearchAsync> = {
parameters: {
docs: {
story: { inline: true },
canvas: { sourceState: "shown" },
canvas: { sourceState: "hidden" },
},
a11y: {
// Label warnings + contrast ratio because of chakra wrapper.
Expand Down Expand Up @@ -101,6 +101,20 @@ const meta: Meta<typeof KvibSearchAsync> = {
},
control: "text",
},
isDisabled: {
table: {
type: { summary: "boolean" },
defaultValue: { summary: "false" },
},
control: "boolean",
},
focusBorderColor: {
table: {
type: { summary: "string" },
defaultValue: { summary: "blue.500" },
},
control: "text",
},
},
args: { onChange: undefined, loadOptions: undefined },
};
Expand All @@ -110,6 +124,13 @@ type Story = StoryObj<typeof KvibSearchAsync>;

export const SearchAsync: Story = {
args: {},
parameters: {
docs: {
canvas: {
sourceState: "shown",
},
},
},
render: (args) => (
<Box h={20}>
<KvibSearchAsync {...args} />
Expand Down Expand Up @@ -154,7 +175,7 @@ export const SearchAsyncResults: Story = {
isMulti: false,
},
render: (args) => (
<Box h={40}>
<Box h="20rem">
<KvibSearchAsync {...args} />
</Box>
),
Expand Down Expand Up @@ -217,7 +238,7 @@ export const SearchAsyncDropdown: Story = {
onChange: handleChange,
dropdownIndicator: <Icon icon="expand_more" weight={400} />,
defaultOptions: true,
placeholder: "Søk etter frukt...",
placeholder: "Søk eller velg frukt...",
},
render: (args) => (
<Box h="20rem">
Expand Down Expand Up @@ -247,7 +268,7 @@ export const SearchAsyncMultiDropdown: Story = {
isMulti: true,
dropdownIndicator: <Icon icon="expand_more" weight={400} />,
defaultOptions: true,
placeholder: "Søk etter frukt...",
placeholder: "Søk eller velg frukt...",
},
render: (args) => (
<Box h="20rem">
Expand Down Expand Up @@ -276,9 +297,10 @@ export const SearchAsyncVariants: Story = {
loadOptions: mockLoadOptions,
onChange: handleChange,
placeholder: "Søk etter frukt...",
focusBorderColor: "green.500",
},
render: (args) => (
<KvibStack h={60}>
<KvibStack h={"12rem"}>
<KvibSearchAsync {...args} variant="outline" />
<KvibSearchAsync {...args} variant="filled" />
<KvibSearchAsync {...args} variant="flushed" />
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/search-async/SearchAsync.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export type BaseProps<T> = {
/** Id set to the SelectContainer component */
id?: string;

/** Determines if the input is disabled */
isDisabled?: boolean;

/** Determines the color of the border when focused. Use color keys in `theme.colors`. */
focusBorderColor?: string;

/** Function to map inputValue to a text output when no options are loaded */
noOptionsMessage?: ((obj: { inputValue: string }) => ReactNode) | undefined;
};
Expand Down Expand Up @@ -74,6 +80,8 @@ export const SearchAsync = <T extends unknown>({
id,
isMulti = false,
noOptionsMessage,
isDisabled,
focusBorderColor,
}: SearchAsyncProps<T>) => {
const noOptionsMessageDefault = ({ inputValue }: { inputValue: string }): ReactNode => {
if (inputValue.replaceAll(/\s/g, "").length < 1) {
Expand Down Expand Up @@ -118,6 +126,8 @@ export const SearchAsync = <T extends unknown>({
variant={variant}
id={id}
isMulti={isMulti}
isDisabled={isDisabled}
focusBorderColor={focusBorderColor}
/>
);
};
Expand Down