diff --git a/.changeset/few-squids-speak.md b/.changeset/few-squids-speak.md new file mode 100644 index 0000000000..ef1ef07ab5 --- /dev/null +++ b/.changeset/few-squids-speak.md @@ -0,0 +1,6 @@ +--- +"@digdir/designsystemet-css": major +"@digdir/designsystemet-react": major +--- + +Search: New compound API diff --git a/packages/css/button.css b/packages/css/button.css index 450c1c59ae..ce70cc8584 100644 --- a/packages/css/button.css +++ b/packages/css/button.css @@ -47,6 +47,8 @@ } &[data-icon] { + width: var(--dsc-button-size); /* Ensure it keeps square shape */ + height: var(--dsc-button-size); /* Ensure it keeps square shape */ padding: 0; } diff --git a/packages/css/search.css b/packages/css/search.css index 132a0c758f..88d9f2e86e 100644 --- a/packages/css/search.css +++ b/packages/css/search.css @@ -1,170 +1,101 @@ .ds-search { - --dsc-search-clear-button-size: var(--ds-sizing-6); - --dsc-search-input-background: var(--ds-color-neutral-background-default); - --dsc-search-input-border-color: var(--ds-color-neutral-border-default); - --dsc-search-input-border: 1px solid var(--dsc-search-input-border-color); - --dsc-search-input-color: var(--ds-color-neutral-text-default); - - display: inline-grid; - width: 100%; - gap: var(--ds-spacing-2); -} - -.ds-search--sm { - --dsc-search-clear-button-size: var(--ds-sizing-5); -} - -.ds-search--md { - --dsc-search-clear-button-size: var(--ds-sizing-6); -} - -.ds-search--lg { - --dsc-search-clear-button-size: var(--ds-sizing-8); -} - -.ds-search__label { - min-width: min-content; - display: inline-flex; - flex-direction: row; - gap: var(--ds-spacing-1); + --dsc-search-padding-inline: var(--ds-spacing-2); + --dsc-search-clear-padding: var(--ds-sizing-1); + --dsc-search-clear-size: var(--ds-sizing-9); + --dsc-search-clear-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='currentColor' d='M6.53 5.47a.75.75 0 0 0-1.06 1.06L10.94 12l-5.47 5.47a.75.75 0 1 0 1.06 1.06L12 13.06l5.47 5.47a.75.75 0 1 0 1.06-1.06L13.06 12l5.47-5.47a.75.75 0 0 0-1.06-1.06L12 10.94z'/%3E%3C/svg%3E"); + --dsc-search-icon-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath d='M10.5 3.25a7.25 7.25 0 1 0 4.57 12.88l5.41 5.41a.75.75 0 1 0 1.06-1.06l-5.41-5.41A7.25 7.25 0 0 0 10.5 3.25M4.75 10.5a5.75 5.75 0 1 1 11.5 0 5.75 5.75 0 0 1-11.5 0'/%3E%3C/svg%3E"); + --dsc-search-icon-size: var(--ds-sizing-7); + + display: grid; align-items: center; -} - -.ds-search__field { - display: flex; + grid-template-columns: 1fr auto; width: 100%; - align-items: stretch; border-radius: var(--ds-border-radius-default); position: relative; -} - -.ds-search__icon { - position: absolute; - height: 100%; - z-index: 2; - left: var(--ds-spacing-4); - transform: scale(1.5); - pointer-events: none; -} -[type='search']::-webkit-search-decoration, -[type='search']::-webkit-search-cancel-button { - appearance: none; -} - -.ds-search__input { - background: var(--dsc-search-input-background); - color: var(--dsc-search-input-color); - border: var(--dsc-search-input-border); - border-radius: var(--ds-border-radius-default); - font-family: inherit; - position: relative; - box-sizing: border-box; - flex: 0 1 auto; - height: 100%; - width: 100%; - appearance: none; - padding: 0 var(--ds-spacing-3); -} - -.ds-search__input.ds-search__input--with-search-button { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.ds-search__input:disabled { - cursor: not-allowed; -} - -.ds-search__input[type='search']:focus-visible { - z-index: 1; -} - -.ds-search:has(.ds-search__input:disabled) { - opacity: var(--ds-disabled-opacity); -} - -.ds-search__search-button { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.ds-search__search-button:not(:focus-visible) { - border-left: 0; -} - -.ds-search__search-button:focus-visible { - z-index: 1; -} - -.ds-search__clear-button { - color: var(--ds-color-neutral-text-default); - display: inline-flex; - align-items: center; - justify-content: center; - position: absolute; - background: none; - border: none; - right: 0.6em; - top: 50%; - transform: translateY(-50%); - cursor: pointer; - height: var(--dsc-search-clear-button-size); - width: var(--dsc-search-clear-button-size); - border-radius: var(--ds-border-radius-default); - font-size: 1.25rem; - padding: 0; - z-index: 2; -} - -.ds-search--sm .ds-search__input { - --dsc-search-clear-button-size: var(--ds-sizing-4); - - height: var(--ds-sizing-10); - padding: 0 var(--ds-spacing-3); - padding-right: 2.5em; -} - -.ds-search--sm .ds-search__icon { - left: var(--ds-spacing-3); -} - -.ds-search--md .ds-search__input { - --dsc-search-clear-button-size: var(--ds-sizing-6); + /* Add magnifier icon when no submit button is present */ + &:not(:has(button:not([type='reset']))) { + & input { + padding-inline-start: calc(var(--dsc-search-icon-size) + calc(var(--dsc-search-padding-inline) * 2)); + } + &::before { + grid-area: 1 / 1; + margin-inline: var(--dsc-search-padding-inline); + pointer-events: none; + position: relative; + z-index: 2; + } + } - height: var(--ds-sizing-12); - padding: 0 var(--ds-spacing-4); - padding-right: 2.2em; -} + /* Render magnifier icon when no submit button, or submit button is empty */ + &:not(:has(button:not([type='reset'])))::before, + & button[type='submit']:empty::before, + & button[type='button']:empty::before { + background: currentcolor; + content: ''; + height: var(--dsc-search-icon-size); + width: var(--dsc-search-icon-size); + mask: var(--dsc-search-icon-url) center / contain no-repeat; + } -.ds-search--md .ds-search__icon { - left: var(--ds-spacing-4); -} + & input { + grid-area: 1 / 1; + padding-inline: var(--dsc-search-padding-inline); -.ds-search--lg .ds-search__input { - --dsc-search-clear-button-size: var(--ds-sizing-12); + &::-webkit-search-decoration, + &::-webkit-search-cancel-button { + appearance: none; + } + } - height: var(--ds-sizing-14); - padding: 0 var(--ds-spacing-5); - padding-right: 2em; -} + /* We hide the clear button when input is empty */ + &:has(input:placeholder-shown) button[type='reset'], + &:has(input:is(:read-only, :disabled, [aria-disabled='true'])) button[type='reset'] { + display: none; + } -.ds-search--lg .ds-search__icon { - left: var(--ds-spacing-5); -} + &:has(button[type='reset']) input { + padding-inline-end: calc(var(--dsc-search-clear-size) + var(--dsc-search-padding-inline)); + } -.ds-search__input.ds-search__input--simple { - padding-left: 2.4em; -} + & button[type='reset'] { + --dsc-button-size: var(--dsc-search-clear-size); + + grid-area: 1 / 1; + justify-self: end; + margin-inline: var(--dsc-search-padding-inline); + padding: var(--dsc-search-clear-padding); + position: relative; + scale: 0.75; + z-index: 2; + + &::before { + content: ''; + height: var(--dsc-search-clear-size); + width: var(--dsc-search-clear-size); + mask: var(--dsc-search-clear-icon-url) center / contain no-repeat; + background: currentcolor; + } + } -@media (hover: hover) and (pointer: fine) { - .ds-search__input:not(:focus-visible, :disabled, [aria-disabled]):hover { - --dsc-search-input-border-color: var(--ds-color-accent-border-strong); + & button[type='submit'], + & button[type='button'] { + border-top-left-radius: 0; + border-bottom-left-radius: 0; - box-shadow: inset 0 0 0 1px var(--ds-color-accent-border-strong); + &:not(:focus-visible) { + border-left: 0; + } } - .ds-search__clear-button:not(:focus-visible, :disabled, [aria-disabled]):hover { - background: var(--ds-color-accent-surface-hover); + &:has(button[type='submit'], button[type='button']) { + &::before { + display: none; + } + + input { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } } } diff --git a/packages/react/src/components/form/Search/Search.mdx b/packages/react/src/components/form/Search/Search.mdx index ffe281f0a5..067f22dc1b 100644 --- a/packages/react/src/components/form/Search/Search.mdx +++ b/packages/react/src/components/form/Search/Search.mdx @@ -10,20 +10,39 @@ import * as SearchStories from './Search.stories'; +## Slik bruker du Search + +Du velger selv om du vil bruke `Search.Clear` og `Search.Button`. Vi anbefaler at du bruker `Search.Clear` for å gi brukeren mulighet til å fjerne søket. +Dersom du ikke har `Search.Button` vil input feltet få et søkeikon. + +Du burde legge på `aria-label` eller `aria-labelledby` dersom du ikke kobler `Search.Input` med en `Label`. + +```tsx +import { Search } from '@digdir/designsystemet-react'; + + + + + + +``` + ## Kontrollert -## Full bredde +## Varianter + +Du kan endre `variant` på `Button`, eller ta den bort for å få forskjellige varianter. - + -## Ikon søkeknapp +## Med Label -Bruk `searchButtonLabel` og `clearButtonLabel` til å sett innholdet i knappene på `Search`. -Ønsker du å bruke kun et ikon på søkeknappen, sørg for at den har `title` definert. +Dersom du vil ha label på søkefeltet, må du legge til en `Label` komponent som du kobler sammen med `Search.Input`. +I eksempelet har vi brukt `Field` for å få oppkobling mellom `Label` og `Search.Input`. - + ## Skjema diff --git a/packages/react/src/components/form/Search/Search.stories.tsx b/packages/react/src/components/form/Search/Search.stories.tsx index c8d72f7cc6..7c44ed5ccb 100644 --- a/packages/react/src/components/form/Search/Search.stories.tsx +++ b/packages/react/src/components/form/Search/Search.stories.tsx @@ -1,47 +1,36 @@ -import { MagnifyingGlassIcon } from '@navikt/aksel-icons'; -import type { Meta, StoryFn, StoryObj } from '@storybook/react'; +import type { Meta, StoryFn } from '@storybook/react'; import { useState } from 'react'; -import { Button, Divider, Heading, Paragraph } from '../..'; +import { Button, Divider, Field, Label, Paragraph } from '../..'; import { Search } from '.'; -type Story = StoryObj; - export default { title: 'Komponenter/Search', component: Search, } as Meta; -export const Preview: Story = { - args: { - label: 'Label', - disabled: false, - 'data-size': 'md', - placeholder: '', - variant: 'simple', - }, -}; - -export const FullWidth: Story = { - args: { - label: 'Label', - }, - parameters: { - layout: 'padded', - }, -}; +export const Preview: StoryFn = (args) => ( + + + + + +); export const Controlled: StoryFn = () => { const [value, setValue] = useState(); return ( <> - setValue(e.target.value)} - onClear={() => setValue('')} - /> + + setValue(e.target.value)} + /> + + + @@ -53,18 +42,41 @@ export const Controlled: StoryFn = () => { ); }; -export const OnlyIcon: StoryFn = () => { - return ( - - } - variant='primary' - /> - ); -}; +export const Variants: StoryFn = () => ( + + + + + + + + + + + + + + + + + + + + + + +); + +export const WithLabel: StoryFn = () => ( + + Søk etter katter + + + + + + +); export const Form: StoryFn = () => { const [value, setValue] = useState(); @@ -72,13 +84,6 @@ export const Form: StoryFn = () => { return ( <> - - Submitted value: {submittedValue} - { // Prevent navigation from Storybook @@ -86,16 +91,20 @@ export const Form: StoryFn = () => { setSubmittedValue(value); }} > - setValue(e.target.value)} - searchButtonLabel={ - - } - variant='primary' - /> + + setValue(e.target.value)} + /> + + + + + + Submitted value: {submittedValue} + > ); }; diff --git a/packages/react/src/components/form/Search/Search.test.tsx b/packages/react/src/components/form/Search/Search.test.tsx index bdcf8015c7..933e00324b 100644 --- a/packages/react/src/components/form/Search/Search.test.tsx +++ b/packages/react/src/components/form/Search/Search.test.tsx @@ -1,141 +1,32 @@ -import { render as renderRtl, screen } from '@testing-library/react'; +import { act, render as renderRtl, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { act } from 'react'; - -import type { SearchProps } from './Search'; -import { Search } from './Search'; +import { Search } from './'; describe('Search', () => { - test('has correct value and label', () => { - render({ value: 'test', label: 'label', clearButtonLabel: 'clear' }); - expect(screen.getByLabelText('label')).toBeDefined(); - expect(screen.getByText('clear')).toBeDefined(); - expect(screen.getByDisplayValue('test')).toBeDefined(); - }); - - test('has correct aria-label when hiding label', () => { - render({ label: 'label', hideLabel: true }); - - expect(screen.getByLabelText('label')).toBeDefined(); - }); - - it('Triggers onBlur event when field loses focus', async () => { - const onBlur = vi.fn(); - const { user } = render({ onBlur }); - const element = screen.getByRole('searchbox'); - await act(async () => await user.click(element)); - expect(element).toHaveFocus(); - await act(async () => await user.tab()); - expect(onBlur).toHaveBeenCalledTimes(1); - }); - - it('Triggers onChange event for each keystroke', async () => { - const onChange = vi.fn(); - const data = 'test'; - const { user } = render({ onChange }); - const element = screen.getByRole('searchbox'); - await act(async () => await user.click(element)); - expect(element).toHaveFocus(); - await act(async () => await user.keyboard(data)); - expect(onChange).toHaveBeenCalledTimes(data.length); - }); - - it('Sets given id on search field', () => { - const id = 'some-unique-id'; - render({ id }); - expect(screen.getByRole('searchbox')).toHaveAttribute('id', id); - }); - - it('Focuses on search field when label is clicked and id is not given', async () => { - const label = 'Lorem ipsum'; - const { user } = render({ label, hideLabel: false }); - await act(async () => await user.click(screen.getByText(label))); - expect(screen.getByRole('searchbox')).toHaveFocus(); - }); - - it('Focuses on search field when label is clicked and id is given', async () => { - const label = 'Lorem ipsum'; - const { user } = render({ id: 'some-unique-id', label, hideLabel: false }); - await act(async () => await user.click(screen.getByText(label))); - expect(screen.getByRole('searchbox')).toHaveFocus(); - }); - - it('clear value with clear button and focus is set to searchbox afterwards', async () => { - const onClear = vi.fn(); - const clearButtonLabel = 'clear'; - const typedText = 'typed text by user'; - - const { user } = render({ onClear, clearButtonLabel }); - - const searchbox = screen.getByRole('searchbox'); - await act(async () => await user.type(searchbox, typedText)); - expect(searchbox.value).toBe(typedText); - - const clearButton = screen.getByText(clearButtonLabel); - - await act(async () => await user.click(clearButton)); - - expect(onClear).toBeCalledWith(typedText); - expect(searchbox.value).toBe(''); - expect(searchbox).toHaveFocus(); - }); - - it('onSearchClick is triggered with correct search value when search button is interacted', async () => { - const onSearchClick = vi.fn(); - const searchButtonLabel = 'search'; - const typedText = 'typed text by user'; - - const { user } = render({ - onSearchClick, - searchButtonLabel, - variant: 'primary', - }); - - const searchbox = screen.getByRole('searchbox'); - await act(async () => await user.type(searchbox, typedText)); - expect(searchbox.value).toBe(typedText); - - const searchButton = screen.getByText(searchButtonLabel); + it('should clear input when clear button is clickd', async () => { + renderRtl( + + + + , + ); - await act(async () => await user.click(searchButton)); + const input = screen.getByRole('searchbox'); + const clearButton = screen.getByRole('button'); - expect(onSearchClick).toBeCalledWith(typedText); - }); + expect(input).toHaveValue(''); + expect(clearButton).toBeInTheDocument(); - it('trigger onSubmit in form by default', async () => { - const user = userEvent.setup(); + input.focus(); - const onSubmit = vi.fn(); - const typedText = 'typed text by user'; + expect(input).toHaveFocus(); - renderRtl( - - - , - ); + await act(async () => await userEvent.type(input, 'Hello, World!')); - const searchbox = screen.getByRole('searchbox'); - await act(async () => await user.type(searchbox, typedText)); - expect(searchbox.value).toBe(typedText); + expect(input).toHaveValue('Hello, World!'); - await act(async () => await user.keyboard('[Enter]')); + await act(async () => await userEvent.click(clearButton)); - expect(onSubmit).toHaveBeenCalled(); + expect(input).toHaveValue(''); }); }); - -const render = (props: Partial = {}) => { - const user = userEvent.setup(); - - return { - user, - ...renderRtl( - , - ), - }; -}; diff --git a/packages/react/src/components/form/Search/Search.tsx b/packages/react/src/components/form/Search/Search.tsx index 4cd3bcf1c0..8d9687f349 100644 --- a/packages/react/src/components/form/Search/Search.tsx +++ b/packages/react/src/components/form/Search/Search.tsx @@ -1,184 +1,13 @@ -import { useMergeRefs } from '@floating-ui/react'; -import { MagnifyingGlassIcon, XMarkIcon } from '@navikt/aksel-icons'; import cl from 'clsx/lite'; -import type { ChangeEvent, InputHTMLAttributes, ReactNode } from 'react'; -import { forwardRef, useCallback, useRef, useState } from 'react'; +import { forwardRef } from 'react'; import type { DefaultProps } from '../../../types'; -import { omit } from '../../../utilities'; -import { Button } from '../../Button/Button'; -import { Label } from '../../Label'; -import { Paragraph } from '../../Paragraph'; -import type { FormFieldProps } from '../useFormField'; -import { useSearch } from './useSearch'; +export type SearchProps = DefaultProps & React.HTMLAttributes; -export type SearchProps = { - /** Label */ - label?: ReactNode; - /** Visually hides `label` and `description` (still available for screen readers) - * @default true - */ - hideLabel?: boolean; - /** Variant - * @default 'simple' - */ - variant?: 'primary' | 'secondary' | 'simple'; - /** Callback for when clear button is activated */ - onClear?: (value: InputHTMLAttributes['value']) => void; - /**Callback for Search-button submit */ - onSearchClick?: (value: string) => void; - /** Search button label. Use this for providing a descriptive button text and/or icon - * @default 'Søk' - */ - searchButtonLabel?: ReactNode; - /** Clear button label. Hidden visually. Used for screen readers - * @default 'Tøm' - */ - clearButtonLabel?: string; - /** Exposes the HTML `size` attribute. - * @default 27 - */ - htmlSize?: number; -} & Omit< - FormFieldProps, - 'size' | 'description' | 'readOnly' | 'error' | 'errorId' -> & - Omit, 'size' | 'readOnly'> & - DefaultProps; - -/** Search field - * - * @example - * ```tsx - * - * ``` - */ -export const Search = forwardRef( - (props, ref) => { - const { - label, - style, - hideLabel = true, - variant = 'simple', - searchButtonLabel = 'Søk', - clearButtonLabel = 'Tøm', - defaultValue, - value, - onChange, - onClear, - disabled, - onSearchClick, - htmlSize = 27, - className, - ...rest - } = props; - - const { inputProps, size = 'md' } = useSearch(props); - - const inputRef = useRef(); - const mergedRef = useMergeRefs([ref, inputRef]); - - const [internalValue, setInternalValue] = useState(defaultValue ?? ''); - - const handleChange = useCallback( - (e: ChangeEvent) => { - const newValue = e.target.value; - value === undefined && setInternalValue(newValue); - onChange?.(e); - }, - [onChange, value], - ); - - const handleClear = () => { - onClear?.(internalValue); - setInternalValue(''); - inputRef?.current?.focus(); - }; - - const handleSearchClick = () => { - onSearchClick?.((value ?? internalValue).toString()); - }; - - const isSimple = variant === 'simple'; - const showClearButton = Boolean(value ?? internalValue) && !disabled; - - return ( - - - {!hideLabel && ( - - {label} - - )} - - - - {isSimple && ( - - )} - - {showClearButton && ( - - {clearButtonLabel} - - - )} - - {!isSimple && ( - - {searchButtonLabel} - - )} - - - - ); - }, -); - -Search.displayName = 'Search'; +export const Search = forwardRef(function Search( + { className, ...rest }, + ref, +) { + return ; +}); diff --git a/packages/react/src/components/form/Search/SearchButton.tsx b/packages/react/src/components/form/Search/SearchButton.tsx new file mode 100644 index 0000000000..cae600d044 --- /dev/null +++ b/packages/react/src/components/form/Search/SearchButton.tsx @@ -0,0 +1,22 @@ +import { forwardRef } from 'react'; + +import { Button, type ButtonProps } from '../../Button'; + +export type SearchButtonProps = Omit & { + variant?: 'primary' | 'secondary'; + /** + * Children of the button + * @default 'Søk' + */ + children?: React.ReactNode; +}; + +export const SearchButton = forwardRef( + function SearchButton({ children = 'Søk', ...rest }, ref) { + return ( + + {children} + + ); + }, +); diff --git a/packages/react/src/components/form/Search/SearchClear.tsx b/packages/react/src/components/form/Search/SearchClear.tsx new file mode 100644 index 0000000000..3e8facf63d --- /dev/null +++ b/packages/react/src/components/form/Search/SearchClear.tsx @@ -0,0 +1,48 @@ +import { forwardRef } from 'react'; +import { Button, type ButtonProps } from '../../Button'; +import { setReactInputValue } from '../Combobox/utilities'; + +/* We omit children since we render the icon with css */ +export type SearchClearProps = Omit & { + /** + * Aria label for the clear button + * @default 'Tøm' + */ + 'aria-label'?: string; +}; + +export const SearchClear = forwardRef( + function SearchClear({ 'aria-label': label = 'Tøm', onClick, ...rest }, ref) { + const handleClear = ( + e: React.MouseEvent, + ) => { + const target = e.target; + let input: HTMLElement | null | undefined = null; + + if (target instanceof HTMLElement) + input = target.closest('.ds-search')?.querySelector('input'); + + if (!input) throw new Error('Input is missing'); + /* narrow type to make TS happy */ + if (!(input instanceof HTMLInputElement)) + throw new Error('Input is not an input element'); + + e.preventDefault(); + setReactInputValue(input, ''); + input.focus(); + onClick?.(e); + }; + + return ( + + ); + }, +); diff --git a/packages/react/src/components/form/Search/SearchInput.tsx b/packages/react/src/components/form/Search/SearchInput.tsx new file mode 100644 index 0000000000..209414bf24 --- /dev/null +++ b/packages/react/src/components/form/Search/SearchInput.tsx @@ -0,0 +1,21 @@ +import { forwardRef } from 'react'; +import { Input } from '../Input'; + +export type SearchInputProps = Omit< + React.InputHTMLAttributes, + 'readOnly' | 'type' +>; + +export const SearchInput = forwardRef( + function SearchInput({ ...rest }, ref) { + return ( + + ); + }, +); diff --git a/packages/react/src/components/form/Search/index.ts b/packages/react/src/components/form/Search/index.ts index addd53308b..6c5d2d933a 100644 --- a/packages/react/src/components/form/Search/index.ts +++ b/packages/react/src/components/form/Search/index.ts @@ -1 +1,32 @@ -export * from './Search'; +import { Search as SearchRoot } from './Search'; +import { SearchButton } from './SearchButton'; +import { SearchClear } from './SearchClear'; +import { SearchInput } from './SearchInput'; + +/** + * Search field + * + * @example + * ```tsx + * + * + * + * + * + * ``` + */ +const Search = Object.assign(SearchRoot, { + Clear: SearchClear, + Button: SearchButton, + Input: SearchInput, +}); + +Search.Clear.displayName = 'Search.Clear'; +Search.Button.displayName = 'Search.Button'; +Search.Input.displayName = 'Search.Input'; + +export type { SearchProps } from './Search'; +export type { SearchButtonProps } from './SearchButton'; +export type { SearchClearProps } from './SearchClear'; +export type { SearchInputProps } from './SearchInput'; +export { SearchClear, SearchButton, SearchInput, Search }; diff --git a/packages/react/src/components/form/Search/useSearch.ts b/packages/react/src/components/form/Search/useSearch.ts deleted file mode 100644 index 4427c47d21..0000000000 --- a/packages/react/src/components/form/Search/useSearch.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { InputHTMLAttributes } from 'react'; -import { useContext } from 'react'; - -import { FieldsetContext } from '../Fieldset/FieldsetContext'; -import type { FormField } from '../useFormField'; -import { useFormField } from '../useFormField'; - -import type { SearchProps } from './Search'; - -type UseSearch = (props: SearchProps) => Omit & { - inputProps: Pick< - InputHTMLAttributes, - 'readOnly' | 'name' | 'required' | 'onClick' | 'onChange' - > & - FormField['inputProps']; -}; - -/** Handles props for `Search` in context with `Fieldset` */ -export const useSearch: UseSearch = (props) => { - const fieldset = useContext(FieldsetContext); - const { - inputProps, - readOnly, - size = fieldset?.size ?? 'md', - ...rest - } = useFormField(props, 'search'); - - return { - ...rest, - readOnly, - size, - inputProps: { - ...inputProps, - type: 'search', - name: props.name ?? 'q', - readOnly, - onClick: (e) => { - if (readOnly) { - e.preventDefault(); - return; - } - props?.onClick?.(e); - }, - onChange: (e) => { - if (readOnly) { - e.preventDefault(); - return; - } - props?.onChange?.(e); - }, - }, - }; -}; diff --git a/packages/react/stories/showcase.stories.tsx b/packages/react/stories/showcase.stories.tsx index c2f91981b2..b9fdfba633 100644 --- a/packages/react/stories/showcase.stories.tsx +++ b/packages/react/stories/showcase.stories.tsx @@ -157,12 +157,10 @@ export const Showcase: StoryFn = () => { Utfør - + + + +