Compare commits

..
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] 09828a4223 Version Packages (#3043)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-07 11:52:24 +02:00
Halvor Haugan e776c7c560 Endre changeset 2024-08-07 11:45:17 +02:00
Halvor Haugan 2dd266f0c4 ⬆️ Upgrade dependencies (minor) (#3070) 2024-08-07 09:13:14 +02:00
Ken daee394a71 feat: Hide sandbox-pages from robots (#3069) 2024-08-06 07:31:39 +02:00
KenandHalvor Haugan 8fd183cc90 feat: Updated font support and definition (#3044)
* feat: Updated font definitions

* memo: Changeset

* stories adjustments

---------

Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2024-08-05 09:34:07 +02:00
Halvor Haugan 0b1ac4f82c [aksel.nav.no] Support relative markdown links (#3059) 2024-08-05 09:32:46 +02:00
KenandHalvor Haugan ec79e25060 feat: Added stale-issue handling (#3029)
* feat: Added stale-issue handling

* Update .github/workflows/stale.yml

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-08-05 08:57:44 +02:00
Vegard HaugstvedtandHalvor Haugan 18d20aaa8e Combobox: Pressing Escape in while focusing a Combobox inside a Modal will close the Modal (#3053)
* If pressing Escape while FilteredOptions is open, Combobox should swallow the keydown event, so it does not close a Modal

* Added changeset

* Update @navikt/core/react/src/form/combobox/Input/Input.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Prevent default Escape from bubbling out of Combobox also when we have a value (which will be cleared).
Also co-located all the Escape-handling in keyDown. This also better mimics Select's native behaviour.

* Add clearInput to dependency array

* Legg til et Storybook-eksempel på Combobox i Modal, for manuell testing. La inn en tekst for å forklare caset.

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-08-02 13:30:22 +02:00
Halvor Haugan 72c8f3112a Combobox: Fixed aria-describedby and some types (#3065) 2024-08-02 12:51:25 +02:00
Vegard HaugstvedtandHalvor Haugan 6e63069d6b Combobox: Fix NullPointer error in single-select with allowNewValues (#3054)
* Prevent NullPointer when selecting a new/custom value in Combobox single-select with Enter (without selecting it in FilteredOptions)

* Update .changeset/cyan-carrots-return.md

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Expect AddNewButton to be visible

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-08-02 12:47:37 +02:00
Halvor Haugan 825d87af43 Combobox: Entering an already selected option and pressing enter no longer removes it (#3061) 2024-08-02 12:43:34 +02:00
9181d2ce75 Combobox: Implement free text search (#2974)
* Implement fuzzy search in combobox

* bug: ArrowUp now works as intended, stopped onChange from running each arrow-press

* memo: Added comment

* refactor: Removed extra state-update for searchterm

* Update @navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* bug: Missed function export after refactor

* Add test for filtering in search

* Fix comments to better reflect the new code

* Change type for onChange to take in only value, not the entire event. This simplifies calling onChange programatically.

* Added changesets to reflect the changes in this branch

* KeyboardEvent.currentTarget does not have selectionStart property

* Update example

* Update storybook examples

* bug: Fixed undefined value sent to toggleOption callback

* revert: Removed test for what was corret interaction

* Add generic to enhance the event type for handleKeyDown

* Updated JSDoc

* Changed method signatures for onChange earlier, but forgot these two tests

* Add test for triggering onChange on every typed character in Combobox with autocomplete.

Currently fails on the last character, as it does not actually change the word in the input field.

* Move onChangeHandler from triggering onChange to onInput, as the former does not trigger when typing the last character of an autocompleted word.
onInput seems to work more consistently, but will test multiple use cases.

* Update aksel.nav.no/website/pages/eksempler/combobox/multi-select-controlled.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/combobox/Input/Input.tsx

* Update .changeset/wild-owls-tan.md

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/combobox/Input/Input.tsx

* updateInputValue is no longer neccessary

* Update @navikt/core/react/src/form/combobox/Input/Input.tsx

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-08-02 11:04:50 +02:00
Ken 22016ec90f prettier-plugin-tailwindcss update (#3046)
* feat: Updated tailwind prettier plugin

* refactor: Used prettier-plugin to remove extra whitespace in className
2024-07-10 10:44:58 +02:00
Ken f7d4c43f98 Added support for markdown links in intro lists (#3045)
* feat: Added support for markdown-links in intro-list

* memo: Added comments
2024-07-10 10:40:22 +02:00
Ken c67fd8bcda feat: Increased amount of articles shown in masonary 2024-07-09 14:27:42 +02:00
Julian Nymark 5efa09d00e 🐛 bump playroom version + set white bg (#3042) 2024-07-08 15:35:46 +02:00
Ken 3afc568a04 refactor: Better naming for modifiers check in Tabs and Togglegroup 2024-07-08 08:44:36 +02:00
Ken 2fe2586ac6 Respect meta-keys when navigating in Tabs, ToggleGroup (#3041)
* bug: RovingFocus now respects alt-keys

* memo: changeset
2024-07-08 08:41:11 +02:00
Ken 420c5c30d8 Primitives CSS update (#3028)
* refactor: Updated stack to only add CSS when used as prop

* refactor: Updated hgrid to only add CSS when used as prop
2024-07-04 14:55:25 +02:00
Ken 6cf141a8c6 Font sync (#3032)
* refactor: Created namespaced location for SourceSans3 fonts

* testing: Added story/chromatic for fonts

* refactor: Rename to use - instead of underline

* refactor: Remove SourceSans3 prefix
2024-07-01 11:04:54 +02:00
dcba0fffad Chromatic stories consolidation (#2996)
* Guidepanel

* Bleed

* Move 'options' object out of 'control' object

* Hide & Show

* Page

* h2 instead of Heading in Chromatic FN

* 🎨 update stories

* 🐛 disable the forcedColors parameter, seems buggy?

- it seems to be a chromatic error, or at least I'm not sure what's
  happening when this parameter is enabled. Some stories seem to
  sporadically get the forcedColors applied.

* Stack

* Combobox: put 'Test' before every test-story for clarity

* Combobox & move combobox tests to tests-file

* made combobox stories testable with Chromatic snapshots

* Update @navikt/core/react/src/button/button.stories.tsx

* Alert rewrite

* Bleed add chromatic disable false

* FileUpload

* FormSummary

* HelpText

* Tooltip

* yarn update chromatic to 11.5.4

* Show: move parameters into Chromatic object

* memo: Removed duplicate heading

* bug: Removed extra argTypes

---------

Co-authored-by: Julian Nymark <julian.nymark@nav.no>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2024-07-01 10:58:33 +02:00
Halvor Haugan da0a66fb62 Fix type for default export in stories (#3038) 2024-07-01 09:47:02 +02:00
Halvor Haugan 3a52359169 Improved example of top aligned Modal (#3039) 2024-07-01 09:40:01 +02:00
1b8d553c05 <VirtualFocus /> component (#2894)
* 🚧 WIP on some dropdown prop API

* ♻️ rename dropdown -> autocomplete

* 🚧 WIP towards Autocomplete (inspiration from Menu)

* 🚧 WIP Autocomplete

* 🚧 wip Autocomplete

* 🚧 wip Autocomplete

* 🚧 working idea for context (get & set autocomplete)

const thinger = () => {

* 🚧 pick property on Item

* 🚧 enrich story/example of Autocomplete

show how easy it is to add icons / do something more complicated inside
the autocomplete suggestions list alongside a <Search>

* 🚧 wip

- fairly nicely working list of virtually focusable items
- API interface kept simple (moved pick to Anchor though, single "exit
  point")
- item has focus function now, which triggers when it gets virtual
  focus, the API should not distinguish between virtual and real focus
  was the thinking here, it's more an implementation detail.

* 🚧 WIP new story for Autocomplete

- showcase dropdown search autocomplete

* Update @navikt/core/react/src/form/autocomplete/Autocomplete.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/form/autocomplete/Autocomplete.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* 🔥 refactor class -> attr, remove css, use user provided <style> in story

* 🔥 remove originial ideas for a prop on <Search>

* ♻️ refactor + use mouseMove over mouseOver

* 🐛 reset virtualfocus on input change (within Anchor)

* ✨ add prop 'loop' for Autocomplete

* 🚚 Autocomplete -> VirtualFocus

* 🚧 WIP on some dropdown prop API

* ♻️ rename dropdown -> autocomplete

* 🚧 WIP towards Autocomplete (inspiration from Menu)

* 🚧 WIP Autocomplete

* 🚧 wip Autocomplete

* 🚧 wip Autocomplete

* 🚧 working idea for context (get & set autocomplete)

const thinger = () => {

* 🚧 pick property on Item

* 🚧 enrich story/example of Autocomplete

show how easy it is to add icons / do something more complicated inside
the autocomplete suggestions list alongside a <Search>

* 🚧 wip

- fairly nicely working list of virtually focusable items
- API interface kept simple (moved pick to Anchor though, single "exit
  point")
- item has focus function now, which triggers when it gets virtual
  focus, the API should not distinguish between virtual and real focus
  was the thinking here, it's more an implementation detail.

* 🚧 WIP new story for Autocomplete

- showcase dropdown search autocomplete

* 🔥 refactor class -> attr, remove css, use user provided <style> in story

* Update @navikt/core/react/src/form/autocomplete/Autocomplete.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update @navikt/core/react/src/form/autocomplete/Autocomplete.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* 🔥 remove originial ideas for a prop on <Search>

* ♻️ refactor + use mouseMove over mouseOver

* 🐛 reset virtualfocus on input change (within Anchor)

* ✨ add prop 'loop' for Autocomplete

* 🚚 Autocomplete -> VirtualFocus

* 🚚 Autocomplete -> VirtualFocus

* 🔥 remove unused CSS

* 🚧 add onActive, delete unused code

* 🚧 rename CSS classes

* ♻️ simplify <VirtualFocus> impl

- use the descendants API > grabbing the DOM nodes...

* 🔥 remove the aria-activedescendant responsibility from VirtualFocus

* Update aksel.nav.no/website/sanity/config.ts

* 🔥 remove comments that no longer apply

* 🔥 remove biome.json changes from PR

* ♻️ various PR changes

- homogenize the API (so both anchor and item have the sameish props,
  onActive, pick...)
- use useId() so multiple <VirtualFocus> on the same page get unique ids

* 🚧 + ♻️ testing changes in VO

- it's not great, weird behaviour on readouts of Items... (FF completely
  broken, sometimes reads "rgla" from "hourglass", other times nothing,
  Chrome is a little better)
- is this a decent API at all? is it overcomplicated?

* 🏷️ type itemRole

* 🔥 remove empty file

* ♻️ pr comments

- renaming to better match the rest of DS
- remove itemRole (just use role)
- eslint ignore

* ⚡ do not go via descendant API if self

* 🎨 add forwardRef & ...rest to Content

* 🐛 fix lying prop doc comment, remove unused CSS class

* ♻️ use <Slot /> (hoistmerge child)

* 💄 fix broken CSS (class was removed)

* 📝 update jsdoc

* 🚚 move role to Anchor (from VirtualFocus wrapper)

* 🎨 remove filtering from broken example

* 🐛 Anchor should trigger virtualfocused.onSelect()

* 🐛 use react v17 shimmed useId()

* Update @navikt/core/react/src/form/virtualfocus/VirtualFocus.tsx

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* 🚧 import

* 🎨 improve story

* 🐛 do not let consumer overwrite certain VirtualBox implementation

* 🎨 update story

* 🚚 move virtualfocus to utils

* 🎨 update to latest RovingFocus.tsx, tweaks to stories

* Update @navikt/core/react/src/util/virtualfocus/VirtualFocus.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* 🎨 improve docs (jsdoc), add Simple story

* 🚚 split up monster component into /parts

* 🐛 pull out dependency to remove cyclic dependency

* 🔥 RovingFocus.tsx

* 🐛 do not ignore external prop

* 🎨 simplify function calls + reorder for consistency

* 🔥 remove unused code

* 🎨 disable chromatic for internal tool stories

* 🏷️ Omit<> props that will get ignored/overridden

* 🏷️ Omit<> props that will get ignored/overridden

* 🏷️ use satisfies

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-28 15:56:54 +02:00
github-actions[bot]andgithub-actions[bot] 1aea10573c Version Packages (#3037)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-28 12:42:02 +02:00
Halvor Haugan 7c644cd9b1 Modal: New prop for aligning modal to top (#3033) 2024-06-27 13:58:45 +02:00
Halvor Haugan fe93f6450b Adjust examples and JSDOC for Alert (#3035) 2024-06-27 13:54:46 +02:00
github-actions[bot]andgithub-actions[bot] 86e52df680 Version Packages (#3013)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-25 15:39:57 +02:00
Halvor Haugan ff8072e352 Icons: FigureCombination icon adjusted (#3031) 2024-06-25 12:32:37 +02:00
Julian Nymark 5b033ab218 ✨ start formatting with biome too (#2991) 2024-06-25 12:27:35 +02:00
Halvor Haugan 6276b676d6 [aksel.nav.no] Validate unique title in exampletext_block (#3024) 2024-06-25 09:52:07 +02:00
Halvor Haugan c2ffd466af Update link in form templates (#3022) 2024-06-24 15:03:36 +02:00
Andreas Nordahl d6d6fb06b1 start align link items (#3026) 2024-06-24 13:05:47 +02:00
Halvor Haugan e829753847 Example of Alert with buttons (#3019) 2024-06-24 13:01:27 +02:00
Ken f1d8e9c4d3 feat: New icon ChildHairEyes (#3020) 2024-06-24 11:56:18 +02:00
Halvor Haugan 4d0f8067b7 Adjust examples of Chips 2024-06-24 10:00:34 +02:00
Halvor Haugan 4afe90122b Add Chromatic modes on some templates (#3023) 2024-06-24 09:23:49 +02:00
Halvor HauganandKen 350dfd3477 Adjust buttons in form summary page template (#2999)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-06-20 14:00:18 +02:00
Ken 9a74fecbfb fire: Removed old testing-templates (#3011) 2024-06-20 13:57:33 +02:00
Ken 7ec73f2008 [ToggleGroup] Selecting already selected value avoids sending extra onChange (#3012)
* feat: Selecting already selected value avoids sending extra onChange-event

* Update @navikt/core/react/src/toggle-group/ToggleGroup.stories.tsx
2024-06-20 13:55:34 +02:00
Halvor Haugan 952655ed63 Refactor templates (#3017) 2024-06-20 13:51:15 +02:00
Halvor Haugan 6af8cc526f Adjust examples of Chips (#3016) 2024-06-20 13:49:18 +02:00
Halvor Haugan 23ea3232fa Alert: Misc. adjustments (#3007) 2024-06-20 13:43:09 +02:00
b38789b98d Implement BasePrimitive for Box, Stack and HGrid (#3003)
* feat: Implemented baseprimitive in box

* refactor: segmented box-features into classes

* feat: Implemented baseprimitive margin css

* refactor: Correctly allow box with no children when no asChild is used

* refactor: Implement BasePrimitive on Stack/HGrid, addef as-prop for hgrid

* typo: Rename constant to a more 'content' nameing convention

* bug: Fix slot-import

* 🚧 --__ac-r-m.*

* bug: Fixed nesting of className

* feat: BasePrimitive now supports auto

* memo: Changeset

* bug: Added margin auto support for marginBlock

* refactor: Redo naming of base props

* bug: Typo

* bug: Wrong css-token reference for pi and pb

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* bug: Removed static padding representation

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* refactor: Renamed AsChildProps used in Primitives

* bug: Re-added fallback to padding/margin when using a subset of breakpoints for inline/block

* Update @navikt/core/react/src/layout/base/BasePrimitive.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* memo: JSDoc

* refactor: Removed undefined as-props from docgen

---------

Co-authored-by: Julian Nymark <julian.nymark@nav.no>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-19 11:17:08 +02:00
Halvor Haugan cb304d0bb6 Add example of horizontal radios (#3009) 2024-06-19 09:33:14 +02:00
github-actions[bot]andgithub-actions[bot] 1dc3e43c6f Version Packages (#2981)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-19 09:16:54 +02:00
Halvor Haugan 50afa6f535 Adjust exampels of Tag (#3006) 2024-06-14 14:08:55 +02:00
Ken 5753acd5c1 refactor: Fixed no-double-equals biome (#3002) 2024-06-14 11:02:52 +02:00
KenandHalvor Haugan ff1e8b1140 refactor: Fixed no misleading character class biome (#3001)
* refactor: Fixed no misleading character class biome

* bug: Add lint ignore rule to support nextjs

* Update aksel.nav.no/website/sanity/schema/util.ts

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-14 10:49:05 +02:00
Ken 60ad18804a feat: Added version-tag to tokens (#2998)
* feat: Added version-tag to built tokens-lib

* feat: Avoid duplicate version-tags

* refactor: Changed --aksel -> -a when adding version-tag
2024-06-13 11:27:57 +02:00
Ken dda89c8a0d bug: BasePrimitive imported 'Slot' from wrong dir 2024-06-13 10:04:43 +02:00
Ken 623fbba362 🚚 Moved Slot into separate directory (#2959)
* refactor: Moved slot to single folder

* refactor: Updated use of Slot in system

* refactor: Rename SlotElement -> SlottedElement

* refactor: Removed slottedElement

Because of typing issues when using anything other than 'div', this added more complexity than needed at this stage
2024-06-13 09:54:33 +02:00
KenandHalvor Haugan 221f7c5d61 [Primitives] BasePrimitive (#2842)
* 🎉 Boilerplate for BasePrimitive

* feat: Lagt til height og width attrb

* test: Chromatic snapshot av height & width api

* feat: Position API

* feat: Overflow

* feat: Flex

* memo: Prop-comments

* refactor: children now React.ReactElement in BasePrimtive

* bug: removed comment strangler

* Update @navikt/core/react/src/layout/base/BasePrimitive.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* memo: Updated stories

* refactor: remove use of backtick

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/layout/base/BasePrimitive.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-12 13:57:03 +02:00
Ken 82f9c30936 [Aksel.nav.no] Fixed clipping of focus-border in toc (#3000)
* bug: Fixed clipping of focus-border

* refactor: removed addition of padding-block in sidebar
2024-06-12 13:51:11 +02:00
Halvor HauganandKen e35ddd5282 [aksel.nav.no] Add possibility to import code in examples (#2970)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-06-11 12:47:00 +02:00
KenandHalvor Haugan 5b70a84674 feature: ToggleGroup icon and label-props replacing children. (#2929)
* refactor: ToggleGroup now supports label and icon-prop

* memo: changeset

* bug: Fixed render of default story

* Update @navikt/core/react/src/toggle-group/parts/ToggleItem.tsx

* Update @navikt/core/react/src/toggle-group/parts/ToggleItem.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update .changeset/tiny-lions-stare.md

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* doc: Added separate stories for group and item-labels in togglegroup

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-11 11:42:13 +02:00
KenandHalvor Haugan ce9da16147 update: Migrated to v9 of next-sanity (#2923)
* feat: API-routes for entering draftmode in nextjs

* feat: Grunlleggende now uses the new livequery-API

* feat: New preview for frontpage

* feat: New preview for standalone-pages

* feat: New preview for blogg

* feat: New preview for prinsipper

* feat: New preview for monster-maler

* feat: New preview for komponenter

* feat: New preview for god-praksis

* fire: Removed code from different PR

* refactor: default export syntax cleanup

* bug: Fixed colorScheme handling for studio

* bug: Added back global styling changes

* types: Better typed query-params for PagePreview

* refactor: typo with KomponentFrontpage in god-praksis

* refactor: Moved client definition out from livequeryprovider -> sanity client config

* Update aksel.nav.no/website/pages/komponenter/[...slug].tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* refactor: Removed select + valid from groq in prinsipp

* refactor: __props -> previewProps for more declarative naming

* refactor: Remove use of memo

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-11 11:41:12 +02:00
Halvor Haugan bed7db15c4 Fix error message when starting Storybook (#2994) 2024-06-11 09:36:29 +02:00
Halvor Haugan feff5ff5e4 [aksel.nav.no] Convert client.server.js to ts (#2993) 2024-06-11 09:15:48 +02:00
Halvor Haugan 8a338851b4 [aksel.nav.no] Fix React warning in Snippet (#2992) 2024-06-10 09:32:33 +02:00
Halvor Haugan a7bd3894ca Split BackgroundToken into BackgroundColorToken and SurfaceColorToken (#2990) 2024-06-07 15:11:14 +02:00
Ken 1b927d6396 feat: Synced button icon size with CopyButton (#2984)
* feat: Synced button icon size with copybutton

* bug: Added min-width to button small
2024-06-07 12:23:13 +02:00
Ken 96ed611e15 List spacing (#2979)
* WIP: List spacing

* test: Added spacing-story test for list

* feat: Updated List with new spacing and Typo-elemenet

* refactor: Changed to spacing-2

* refactor: Updated nest list spacing

* bug: Reverted change in gap between icon and text

* bug: Fixed marker height

* Update .changeset/strong-bulldogs-jam.md
2024-06-07 12:00:19 +02:00
KenandHalvor Haugan a23621d0b0 refactor: Fixed noShadowRestrictedNames lint-errors (#2982)
* refactor: Fixed noShadowRestrictedNames lint-errors

* bug: Fieldset error-story broke

* Update @navikt/core/react/src/form/confirmation-panel/confirmation-panel.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/fieldset/fieldset.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/fieldset/fieldset.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/textfield/text-field.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/react/src/form/select/select.stories.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* refactor: synced new story-names

* refactor: Fixed noAssignInExpressions lint-errors (#2983)

* refactor: Fixed noAssignInExpressions lint-errors

* Update @navikt/core/react/src/util/i18n/get.ts

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* refactor: added dialog-polyfill to biome ignorelist

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-07 11:58:52 +02:00
Halvor HauganandKen 395eea108f Improved JSDOC for background and borderColor in Box and Page (#2988)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-06-07 11:32:18 +02:00
Ken 2e4351cced refactor: Removed surface-subtle from background-prop (#2987) 2024-06-07 11:27:53 +02:00
Ken f1707fe057 feat: Added prop for hidden heading to allow screen-readers to navigate to pagination (#2976)
* bug: Omit page-prop from PaginationItem and add data-attrb

* feat: Added prop for hidden heading to allow screen-readers to navigate to pagination

* refactor: Revert change of page-attrb removal since it could be breaking

* refactor: Fixed GH PR-comments

* refactor: Changed heading -> srHeading

* refactor: Modernized pagination-story

* refactor: Change to aria-labelledby, update stories
2024-06-07 11:26:21 +02:00
Ken ba76356178 Workflow update to node 20 (#2980)
* refactor: use github checkout v4

* refactor: use setup-node v4, default node 20

* workflow: Updated workflows to use node 20

* bug: removal of node-version in setup-node broke tests

* refactor: Revert removal of node-version in setup node for all workflows

* memo: Fixed typo in workflow-name
2024-06-06 09:37:21 +02:00
Ken 4c7de30732 memo: Edit broken docs-link in changelog 2024-06-05 14:39:18 +02:00
Ken 99ea2fa022 memo: Edit broken docs-link 2024-06-05 14:38:10 +02:00
Ken d5818a5e60 design: Copybutton-icon is now 20px for small og xsmall sizes (#2977)
* design: Copybutton-icon is now 20px for small og xsmall sizes

* bug: Replace aspect-ratio for min height/width with regular min-width and min-height attrb
2024-06-05 13:50:41 +02:00
KenandHalvor Haugan d0debc10ab feat: Added description-prop for fileupload item (#2975)
* feat: Added description-prop for fileupload item

* Update @navikt/core/react/src/form/file-upload/parts/item/Item.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-05 12:09:26 +02:00
KenandHalvor Haugan 5b011192f7 feat: Added option to use quotes in sanity editor (#2962)
* feat: Added option to use quotes i sanity editor

* Update aksel.nav.no/website/sanity/schema/objects/shared/riktekst.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-06-05 12:07:21 +02:00
github-actions[bot]andgithub-actions[bot] 754d151a7f Version Packages (#2972)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-05 11:32:10 +02:00
Ken 677170a767 update: Updated svgr cli to v8 (#2930) 2024-06-05 11:19:22 +02:00
Ken 2cd6b1a391 bug: Icon-package missed src-dir referenced in sourcemap (#2978) 2024-06-05 10:09:53 +02:00
Ken 5bcb870b16 bug: Etterlevelse-link in footer had wrong ingress 2024-06-04 11:29:49 +02:00
Halvor Haugan 704e4f6c77 Adjust examples of Pagination (#2973) 2024-06-04 09:57:27 +02:00
Halvor Haugan 334627061a 💄 Remove bottom margin on last item in Stepper and adjust FormProgress accordingly (#2971) 2024-06-03 14:32:05 +02:00
355 changed files with 10553 additions and 10086 deletions

No files matched your search

+21 -1
View File
@@ -91,6 +91,27 @@ module.exports = {
"aksel-local/comment-check": ["error"],
},
},
{
files: ["**/examples/__parts*/*.tsx"],
plugins: ["aksel-local"],
rules: {
"aksel-local/import-check": ["error"], // Only allow imports from @navikt and react
},
},
{
files: ["**/examples/__parts-inline/*.tsx"],
rules: {
"arrow-body-style": ["error", "never"],
"func-style": ["error", "expression"],
"import/no-named-export": "error",
},
},
{
files: ["examples/**"],
rules: {
"react/react-in-jsx-scope": "off",
},
},
],
globals: {
Locale: "readonly",
@@ -100,7 +121,6 @@ module.exports = {
"node_modules",
"lib",
"public",
"examples",
"esm",
"cjs",
"dist",
+3 -3
View File
@@ -14,10 +14,10 @@ jobs:
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
+6 -6
View File
@@ -14,11 +14,11 @@ jobs:
name: Update component data
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
@@ -47,11 +47,11 @@ jobs:
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
+5 -4
View File
@@ -1,4 +1,4 @@
name: Redeploy image to for aksel.nav.no
name: Redeploy image for aksel.nav.no
on:
workflow_dispatch:
inputs:
@@ -19,10 +19,11 @@ jobs:
name: Redeploy image
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
- uses: actions/checkout@v3
node-version: lts/*
cache: yarn
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # Use commit-sha1 instead of tag for security concerns
with:
+5 -5
View File
@@ -8,13 +8,13 @@ jobs:
execute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required to retrieve git history
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
@@ -26,10 +26,10 @@ jobs:
- name: Export dataset
run: cd aksel.nav.no/website && SANITY_PREVIEW_TOKEN=${{secrets.SANITY_DATASET_EDITOR_TOKEN}} yarn backup
- name: Upload GCP bucket
uses: "google-github-actions/auth@v0"
uses: "google-github-actions/auth@v2"
with:
credentials_json: "${{ secrets.gcp_credentials_prod }}"
- uses: "google-github-actions/upload-cloud-storage@v0"
- uses: "google-github-actions/upload-cloud-storage@v2"
with:
path: ./aksel.nav.no/website
destination: "aksel-website-prod/backups"
+5 -14
View File
@@ -17,32 +17,23 @@ concurrency:
cancel-in-progress: true
jobs:
visual_regression_test:
chromatic:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-chromatic') }}
name: "visual regression test (chromatic)"
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-visual-regression-test-${{ github.sha }}
restore-keys: |
${{ runner.os }}-visual-regression-test-
- name: Install dependencies
run: yarn
@@ -50,7 +41,7 @@ jobs:
run: yarn boot
- name: Chromatic
uses: chromaui/action@v1
uses: chromaui/action@latest
id: chromatic_tests
continue-on-error: true
with:
+6 -3
View File
@@ -16,13 +16,13 @@ jobs:
permissions:
pull-requests: "write"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
@@ -37,5 +37,8 @@ jobs:
- name: Lint
run: yarn lint
- name: Validate example code
run: yarn validate:eksempler
- name: Test
run: yarn test
+4 -4
View File
@@ -15,10 +15,10 @@ jobs:
container:
image: mcr.microsoft.com/playwright:v1.35.1-jammy
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
@@ -42,7 +42,7 @@ jobs:
- name: Run Playwright tests
run: yarn workspace website e2e:ci-full
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
id-token: "write"
steps:
- uses: "actions/checkout@v3"
- uses: "actions/checkout@v4"
- id: upload
uses: nais/deploy/actions/cdn-upload/v2@master
+3 -3
View File
@@ -22,13 +22,13 @@ jobs:
packages: "write"
pull-requests: "write"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version: lts/*
cache: yarn
- name: set-env for yarn install to @navikt scope
shell: bash
+29
View File
@@ -0,0 +1,29 @@
# https://github.com/actions/stale
name: Mark stale issues
on:
schedule:
- cron: "31 3 * * *"
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 120
days-before-pr-stale: -1
days-before-close: 30
any-of-labels: "Besvart"
stale-issue-message: |-
Hei! Det ser ut som det ikke har vært aktivitet her på en stund. Etter 30 dager vil saken bli lukket automatisk.
Om saken fortsatt er relevant eller du har oppdateringer, vil en kommentar hindre at den lukkes. Du kan alltids åpne den igjen hvis det skulle være nødvendig!
stale-issue-label: "no-issue-activity"
ascending: true
operations-per-run: 200
+2 -2
View File
@@ -9,14 +9,14 @@ jobs:
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: Restore cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.os }}-build-${{ github.sha }}
+3 -2
View File
@@ -88,17 +88,18 @@ export default {
const { mergeConfig } = await import("vite");
// The TsconfigPathsPlugin is only used to silence errors when importing nextjs components, but the imports does not acutally work.
const tsConfigPathsPluginOpts = { root: "aksel.nav.no/website/" };
return mergeConfig(config, {
plugins:
configType === "PRODUCTION"
? [
TsconfigPathsPlugin(),
TsconfigPathsPlugin(tsConfigPathsPluginOpts),
turbosnap({
rootDir: config.root ?? process.cwd(),
}),
]
: [TsconfigPathsPlugin()],
: [TsconfigPathsPlugin(tsConfigPathsPluginOpts)],
});
},
} satisfies StorybookConfig;
+22
View File
@@ -1,5 +1,27 @@
# @navikt/aksel-icons
## 6.14.0
## 6.13.0
## 6.12.0
### Minor Changes
- Icons: New icon ChildHairEyes ([#3020](https://github.com/navikt/aksel/pull/3020))
### Patch Changes
- Icons: FigureCombination icon adjusted ([#3031](https://github.com/navikt/aksel/pull/3031))
## 6.11.0
## 6.10.1
### Patch Changes
- Icons: Added back support for sourcemapping. ([#2978](https://github.com/navikt/aksel/pull/2978))
## 6.10.0
## 6.9.0
@@ -1,7 +1,10 @@
export function paginate(arr, size) {
return arr.reduce((acc, val, i) => {
let idx = Math.floor(i / size);
let page = acc[idx] || (acc[idx] = []);
if (!acc[idx]) {
acc[idx] = [];
}
let page = acc[idx];
page.push(val);
return acc;
+4 -1
View File
@@ -2,7 +2,10 @@ const path = require("path");
const defaultIndexTemplate = (paths) => {
const exportEntries = paths.map((filePath) => {
const basename = path.basename(filePath, path.extname(filePath));
const basename = path.basename(
filePath.originalPath,
path.extname(filePath.originalPath),
);
return `export { default as ${basename}Icon } from './${basename}'`;
});
@@ -120,8 +120,8 @@ const App = () => {
onChange={(v) => setToggle(v as any)}
variant="neutral"
>
<ToggleGroup.Item value="stroke">Stroke</ToggleGroup.Item>
<ToggleGroup.Item value="fill">Fill</ToggleGroup.Item>
<ToggleGroup.Item value="stroke" label="Stroke" />
<ToggleGroup.Item value="fill" label="Fill" />
</ToggleGroup>
</div>
<div className="form-search">
+1 -1
View File
@@ -8,5 +8,5 @@ keywords:
- back
- tilbake
variant: Stroke
updated_at: 21.05.2024
updated_at: 31.05.2024
created_at: 07.03.2023
+1 -1
View File
@@ -9,5 +9,5 @@ keywords:
- frem
- fram
variant: Stroke
updated_at: 21.05.2024
updated_at: 10.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -12,5 +12,5 @@ keywords:
- ombestemt
- back
variant: Stroke
updated_at: 10.01.2024
updated_at: 11.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -10,5 +10,5 @@ keywords:
- close
- lukke
variant: Stroke
updated_at: 02.05.2024
updated_at: 06.06.2024
created_at: 07.03.2023
@@ -14,5 +14,5 @@ keywords:
- opptak
- gjenopptak
variant: Stroke
updated_at: 17.04.2024
updated_at: 31.05.2024
created_at: 07.03.2023
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- fullføring
- validering
variant: Stroke
updated_at: 17.04.2024
updated_at: 10.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -10,5 +10,5 @@ keywords:
- åpner
- mer
variant: Stroke
updated_at: 21.05.2024
updated_at: 19.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -6,5 +6,5 @@ keywords:
- venstre
- side
variant: Stroke
updated_at: 24.04.2024
updated_at: 29.05.2024
created_at: 07.03.2023
+1 -1
View File
@@ -6,5 +6,5 @@ keywords:
- høyre
- side
variant: Stroke
updated_at: 17.04.2024
updated_at: 10.06.2024
created_at: 07.03.2023
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.651 3.94427C8.68476 4.52559 7.25 6.34524 7.25 8.5C7.25 11.1234 9.37665 13.25 12 13.25C14.6234 13.25 16.75 11.1234 16.75 8.5C16.75 6.98351 16.0393 5.63301 14.9329 4.76335C14.9538 5.10864 14.9022 5.44954 14.7891 5.75643C14.1895 7.38197 12.1053 7.74555 10.677 6.5063C10.3641 6.23484 10.3305 5.76115 10.602 5.44829C10.8734 5.13543 11.3471 5.10186 11.66 5.37331C12.4681 6.07443 13.2206 5.67424 13.3817 5.23739C13.5006 4.91494 13.4405 4.49643 13.124 4.20959C12.8111 3.92606 12.1541 3.61035 10.651 3.94427ZM12 2.25C8.54822 2.25 5.75 5.04822 5.75 8.5C5.75 11.9518 8.54822 14.75 12 14.75C15.4518 14.75 18.25 11.9518 18.25 8.5C18.25 5.04822 15.4518 2.25 12 2.25ZM10.5 8.75C10.0858 8.75 9.75 9.08579 9.75 9.5C9.75 9.91421 10.0858 10.25 10.5 10.25H10.51C10.9242 10.25 11.26 9.91421 11.26 9.5C11.26 9.08579 10.9242 8.75 10.51 8.75H10.5ZM12.75 9.5C12.75 9.08579 13.0858 8.75 13.5 8.75H13.51C13.9242 8.75 14.26 9.08579 14.26 9.5C14.26 9.91421 13.9242 10.25 13.51 10.25H13.5C13.0858 10.25 12.75 9.91421 12.75 9.5ZM9.34835 18.3484C10.0516 17.6451 11.0054 17.25 12 17.25C12.9946 17.25 13.9484 17.6451 14.6516 18.3483C15.3549 19.0516 15.75 20.0054 15.75 21C15.75 21.4142 16.0858 21.75 16.5 21.75C16.9142 21.75 17.25 21.4142 17.25 21C17.25 19.6076 16.6969 18.2723 15.7123 17.2877C14.7277 16.3031 13.3924 15.75 12 15.75C10.6076 15.75 9.27226 16.3031 8.28769 17.2877C7.30312 18.2723 6.75 19.6076 6.75 21C6.75 21.4142 7.08579 21.75 7.5 21.75C7.91421 21.75 8.25 21.4142 8.25 21C8.25 20.0054 8.64509 19.0516 9.34835 18.3484Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,12 @@
name: ChildHairEyes
category: People
sub_category: Children
keywords:
- human
- menneske
- øyne
- barn
- baby
variant: Stroke
updated_at: 20.06.2024
created_at: 20.06.2024
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7365 4.51074C14.9187 4.01666 14.9409 3.43431 14.7577 2.88768C16.8265 3.90652 18.25 6.036 18.25 8.49796C18.25 11.9497 15.4518 14.748 12 14.748C8.54822 14.748 5.75 11.9497 5.75 8.49796C5.75 5.62729 7.68536 3.20864 10.3225 2.47564L10.3228 2.4771C11.98 2.10394 12.8426 2.42231 13.2398 2.78233C13.6388 3.14395 13.7136 3.67177 13.5637 4.07821C13.3388 4.68809 12.3735 5.11262 11.4336 4.29706C11.1728 4.07085 10.7781 4.09882 10.5519 4.35954C10.3257 4.62027 10.3537 5.015 10.6144 5.24122C12.0712 6.50522 14.1463 6.11119 14.7365 4.51074ZM10.5 8.74796C10.0858 8.74796 9.75 9.08374 9.75 9.49796C9.75 9.91217 10.0858 10.248 10.5 10.248H10.51C10.9242 10.248 11.26 9.91217 11.26 9.49796C11.26 9.08374 10.9242 8.74796 10.51 8.74796H10.5ZM12.75 9.49796C12.75 9.08374 13.0858 8.74796 13.5 8.74796H13.51C13.9242 8.74796 14.26 9.08374 14.26 9.49796C14.26 9.91217 13.9242 10.248 13.51 10.248H13.5C13.0858 10.248 12.75 9.91217 12.75 9.49796ZM12 15.748C10.6076 15.748 9.27226 16.3011 8.28769 17.2856C7.30312 18.2702 6.75 19.6056 6.75 20.998C6.75 21.1969 6.82902 21.3876 6.96967 21.5283C7.11032 21.6689 7.30109 21.748 7.5 21.748L16.5 21.748C16.6989 21.748 16.8897 21.6689 17.0303 21.5283C17.171 21.3876 17.25 21.1969 17.25 20.998C17.25 19.6056 16.6969 18.2702 15.7123 17.2856C14.7277 16.3011 13.3924 15.748 12 15.748Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,12 @@
name: ChildHairEyesFill
category: People
sub_category: Children
keywords:
- human
- menneske
- øyne
- barn
- baby
variant: Fill
updated_at: 20.06.2024
created_at: 20.06.2024
+1 -1
View File
@@ -8,5 +8,5 @@ keywords:
- gutt
- barn
variant: Stroke
updated_at: 16.01.2024
updated_at: 20.06.2024
created_at: 16.01.2024
@@ -8,5 +8,5 @@ keywords:
- gutt
- barn
variant: Fill
updated_at: 16.01.2024
updated_at: 20.06.2024
created_at: 16.01.2024
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3.75C11.3096 3.75 10.75 4.30964 10.75 5C10.75 5.69036 11.3096 6.25 12 6.25C12.6904 6.25 13.25 5.69036 13.25 5C13.25 4.30964 12.6904 3.75 12 3.75ZM9.25 5C9.25 3.48122 10.4812 2.25 12 2.25C13.5188 2.25 14.75 3.48122 14.75 5C14.75 6.51878 13.5188 7.75 12 7.75C10.4812 7.75 9.25 6.51878 9.25 5ZM12.75 9C12.75 8.58579 13.0858 8.25 13.5 8.25H14.847C15.912 8.25 16.73 9.19323 16.5794 10.2475L15.7425 16.1061C15.6897 16.4756 15.3732 16.75 15 16.75H14.25V21C14.25 21.4142 13.9142 21.75 13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21V16C12.75 15.5858 13.0858 15.25 13.5 15.25H14.3495L15.0945 10.0354C15.116 9.88475 14.9991 9.75 14.847 9.75H13.5C13.0858 9.75 12.75 9.41421 12.75 9ZM9.96929 9.91592C10.0048 9.81642 10.0991 9.75 10.2047 9.75H10.5C10.9142 9.75 11.25 9.41421 11.25 9C11.25 8.58579 10.9142 8.25 10.5 8.25H10.2047C9.46515 8.25 8.80542 8.71492 8.55668 9.41141L6.2937 15.7477C6.21167 15.9774 6.24645 16.2327 6.38694 16.432C6.52744 16.6314 6.75611 16.75 7 16.75H9.75V21C9.75 21.4142 10.0858 21.75 10.5 21.75C10.9142 21.75 11.25 21.4142 11.25 21V16C11.25 15.5858 10.9142 15.25 10.5 15.25H8.06426L9.96929 9.91592Z" fill="#23262A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.55 3.75C11.8596 3.75 11.3 4.30964 11.3 5C11.3 5.69036 11.8596 6.25 12.55 6.25C13.2404 6.25 13.8 5.69036 13.8 5C13.8 4.30964 13.2404 3.75 12.55 3.75ZM9.8 5C9.8 3.48122 11.0312 2.25 12.55 2.25C14.0688 2.25 15.3 3.48122 15.3 5C15.3 6.51878 14.0688 7.75 12.55 7.75C11.0312 7.75 9.8 6.51878 9.8 5ZM13.3 9C13.3 8.58579 13.6358 8.25 14.05 8.25H16.05C16.4642 8.25 16.8 8.58579 16.8 9V16.1061C16.8 16.5203 16.4642 16.8561 16.05 16.8561H14.8V21C14.8 21.4142 14.4642 21.75 14.05 21.75C13.6358 21.75 13.3 21.4142 13.3 21V16.1061C13.3 15.6919 13.6358 15.3561 14.05 15.3561H15.3V9.75H14.05C13.6358 9.75 13.3 9.41421 13.3 9ZM10.5193 9.91592C10.5548 9.81642 10.6491 9.75 10.7547 9.75H11.05C11.4642 9.75 11.8 9.41421 11.8 9C11.8 8.58579 11.4642 8.25 11.05 8.25H10.7547C10.0152 8.25 9.35542 8.71492 9.10668 9.41141L6.8437 15.7477C6.76167 15.9774 6.79645 16.2327 6.93695 16.432C7.07744 16.6314 7.30611 16.75 7.55001 16.75H10.3V21C10.3 21.4142 10.6358 21.75 11.05 21.75C11.4642 21.75 11.8 21.4142 11.8 21V16C11.8 15.5858 11.4642 15.25 11.05 15.25H8.61426L10.5193 9.91592Z" fill="#23262A"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -7,6 +7,7 @@ keywords:
- trans
- intetkjønn
- hen
- person
variant: Stroke
updated_at: 04.12.2023
updated_at: 25.06.2024
created_at: 07.03.2023
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.25 5C9.25 3.48122 10.4812 2.25 12 2.25C13.5188 2.25 14.75 3.48122 14.75 5C14.75 6.51878 13.5188 7.75 12 7.75C10.4812 7.75 9.25 6.51878 9.25 5ZM13.5 8.25C13.3011 8.25 13.1103 8.32902 12.9697 8.46967C12.829 8.61032 12.75 8.80109 12.75 9V21C12.75 21.4142 13.0858 21.75 13.5 21.75C13.9142 21.75 14.25 21.4142 14.25 21V16.75H15C15.3732 16.75 15.6897 16.4756 15.7425 16.1061L16.5794 10.2475C16.73 9.19323 15.912 8.25 14.847 8.25H13.5ZM8.55668 9.41141C8.80542 8.71492 9.46515 8.25 10.2047 8.25H10.5C10.9142 8.25 11.25 8.58579 11.25 9L11.25 21C11.25 21.4142 10.9142 21.75 10.5 21.75C10.0858 21.75 9.75 21.4142 9.75 21V16.75H7C6.75611 16.75 6.52744 16.6314 6.38694 16.432C6.24645 16.2327 6.21167 15.9774 6.2937 15.7477L8.55668 9.41141Z" fill="#23262A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.8 5C9.8 3.48122 11.0312 2.25 12.55 2.25C14.0688 2.25 15.3 3.48122 15.3 5C15.3 6.51878 14.0688 7.75 12.55 7.75C11.0312 7.75 9.8 6.51878 9.8 5ZM13.3 9C13.3 8.58579 13.6358 8.25 14.05 8.25H16.05C16.4642 8.25 16.8 8.58579 16.8 9V16C16.8 16.4142 16.4642 16.75 16.05 16.75H14.8V21C14.8 21.4142 14.4642 21.75 14.05 21.75C13.6358 21.75 13.3 21.4142 13.3 21V9ZM9.10668 9.41141C9.35542 8.71492 10.0152 8.25 10.7547 8.25H11.05C11.4642 8.25 11.8 8.58579 11.8 9L11.8 21C11.8 21.4142 11.4642 21.75 11.05 21.75C10.6358 21.75 10.3 21.4142 10.3 21V16.75H7.55001C7.30611 16.75 7.07744 16.6314 6.93695 16.432C6.79645 16.2327 6.76167 15.9774 6.8437 15.7477L9.10668 9.41141Z" fill="#23262A"/>
</svg>

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 827 B

@@ -7,6 +7,7 @@ keywords:
- trans
- intetkjønn
- hen
- person
variant: Fill
updated_at: 04.12.2023
updated_at: 25.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -9,5 +9,5 @@ keywords:
- herre
- han
variant: Stroke
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
@@ -9,5 +9,5 @@ keywords:
- herre
- han
variant: Fill
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -8,5 +8,5 @@ keywords:
- female
- hun
variant: Stroke
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
@@ -8,5 +8,5 @@ keywords:
- female
- hun
variant: Fill
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -13,5 +13,5 @@ keywords:
- organisering
- sosiale medier
variant: Stroke
updated_at: 05.01.2024
updated_at: 05.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -13,5 +13,5 @@ keywords:
- papir
- kontorrekvisita
variant: Stroke
updated_at: 03.05.2024
updated_at: 12.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -4,5 +4,5 @@ sub_category: Figure
keywords:
- silhuett
variant: Stroke
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -4,5 +4,5 @@ sub_category: Figure
keywords:
- silhuett
variant: Fill
updated_at: 04.12.2023
updated_at: 20.06.2024
created_at: 07.03.2023
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- avvis
- avslå
variant: Stroke
updated_at: 15.04.2024
updated_at: 13.06.2024
created_at: 07.03.2023
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "6.10.0",
"version": "6.14.0",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
@@ -52,10 +52,11 @@
},
"files": [
"dist/*",
"src",
"!**/*.tsbuildinfo"
],
"scripts": {
"create-icons": "svgr --no-prettier --silent --index-template config/index-template.js --out-dir src icons && prettier --write ./src",
"create-icons": "svgr --no-prettier --silent --index-template config/index-template.js --out-dir src icons && yarn prettier --write ./src",
"parse-svg": "node -e 'require(\"./config/parse-svg.js\").main()'",
"copy": "copyfiles util/* src",
"update-metadata": "node config/metadata.js",
@@ -67,7 +68,7 @@
"test:watch": "vitest watch"
},
"devDependencies": {
"@svgr/cli": "6.5.1",
"@svgr/cli": "8.1.0",
"adm-zip": "^0.5.10",
"concurrently": "7.2.1",
"copyfiles": "^2.4.1",
@@ -78,6 +79,7 @@
"js-yaml": "^4.1.0",
"lodash.camelcase": "4.3.0",
"lodash.startcase": "4.4.0",
"prettier": "3.1.1",
"react": "^18.0.0",
"rehype-parse": "8.0.4",
"tsc-alias": "1.8.8",
+10
View File
@@ -1,5 +1,15 @@
# @navikt/aksel-stylelint
## 6.14.0
## 6.13.0
## 6.12.0
## 6.11.0
## 6.10.1
## 6.10.0
## 6.9.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "6.10.0",
"version": "6.14.0",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^6.10.0",
"@navikt/ds-tokens": "^6.10.0",
"@navikt/ds-css": "^6.14.0",
"@navikt/ds-tokens": "^6.14.0",
"concurrently": "7.2.1",
"postcss-selector-parser": "^6.0.13",
"postcss-value-parser": "^4.2.0",
+35
View File
@@ -1,5 +1,40 @@
# @navikt/aksel
## 6.14.0
### Patch Changes
- Updated dependencies [[`8fd183cc9`](https://github.com/navikt/aksel/commit/8fd183cc909a1f27a0a9f496415d280c1207a479)]:
- @navikt/ds-css@6.14.0
## 6.13.0
### Patch Changes
- Updated dependencies [[`7c644cd9b`](https://github.com/navikt/aksel/commit/7c644cd9b1237c13295e9e4b416307efba030e9a)]:
- @navikt/ds-css@6.13.0
## 6.12.0
### Patch Changes
- Updated dependencies [[`23ea3232f`](https://github.com/navikt/aksel/commit/23ea3232fab71fdca29d2f97f279fa4962c440c0), [`b38789b98`](https://github.com/navikt/aksel/commit/b38789b98d7e60eaf79a73433c6378f8644ee825)]:
- @navikt/ds-css@6.12.0
## 6.11.0
### Patch Changes
- Updated dependencies [[`d5818a5e6`](https://github.com/navikt/aksel/commit/d5818a5e6070337d4d2884d68916dd8584e33806), [`1b927d639`](https://github.com/navikt/aksel/commit/1b927d6396a36abe6282d88f357e4d5eb8a7f366), [`96ed611e1`](https://github.com/navikt/aksel/commit/96ed611e15bae7877b9516f9c4541a188c6f8222)]:
- @navikt/ds-css@6.11.0
## 6.10.1
### Patch Changes
- Updated dependencies [[`334627061`](https://github.com/navikt/aksel/commit/334627061a5aa1a9af0e3118b7a69e1df784c35f)]:
- @navikt/ds-css@6.10.1
## 6.10.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "6.10.0",
"version": "6.14.0",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "6.10.0",
"@navikt/ds-css": "6.14.0",
"axios": "1.6.0",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
@@ -36,7 +36,9 @@ export default function transformer(file: JSCodeshift, api) {
let foundName: string = "";
getImportSpecifier(j, root, name, "@navikt/ds-tokens/dist/tokens").forEach(
(x) => (foundName = x.node.imported.name),
(x) => {
foundName = x.node.imported.name;
},
);
if (name === foundName) {
+39 -1
View File
@@ -1,5 +1,43 @@
# @navikt/ds-css
## 6.14.0
### Minor Changes
- Fonts: Added built-in support for cyrillic, cyrillic-ext and latin-ext ([#3044](https://github.com/navikt/aksel/pull/3044))
## 6.13.0
### Minor Changes
- Modal: New prop `placement` for anchoring the modal to the top of the viewport. ([#3033](https://github.com/navikt/aksel/pull/3033))
## 6.12.0
### Minor Changes
- Primitives: Added support for padding, paddingInline, paddingBlock, margin, marginInline, marginBlock, width, minWidth, maxWidth, height, minHeight, maxHeight, position, inset, top, right, left, bottom, overflow, overflowX, overflowY, flexBasis, flexGrow, flexShrink to Box, HGrid and Stack. ([#3003](https://github.com/navikt/aksel/pull/3003))
### Patch Changes
- Alert: Fix alignment, add prop for toggling content max-width, and adjust title on close icon. ([#3007](https://github.com/navikt/aksel/pull/3007))
## 6.11.0
### Patch Changes
- CopyButton: Icon is now 20px for size small and xsmall. ([#2977](https://github.com/navikt/aksel/pull/2977))
- Button: Updated icon size for size="small" to 1.25rem. ([#2984](https://github.com/navikt/aksel/pull/2984))
- List: Changed List.Item to BodyLong, changed Item spacing from 1rem to 0.5rem. ([#2979](https://github.com/navikt/aksel/pull/2979))
## 6.10.1
### Patch Changes
- :lipstick: Remove bottom margin on last item in Stepper and adjust FormProgress accordingly ([#2971](https://github.com/navikt/aksel/pull/2971))
## 6.10.0
### Minor Changes
@@ -489,7 +527,7 @@
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/primitives/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
+5 -1
View File
@@ -7,7 +7,11 @@
align-items: center;
}
.navds-alert__wrapper {
.navds-alert--small.navds-alert--close-button {
align-items: flex-start;
}
.navds-alert__wrapper--maxwidth {
max-width: 43.5rem;
}
+1 -1
View File
@@ -17,7 +17,7 @@
body,
:host {
font-family: var(--a-font-family, "Source Sans Pro", Arial, sans-serif);
font-family: var(--a-font-family, "Source Sans 3", "Source Sans Pro", Arial, sans-serif);
line-height: 1.333;
font-size: 1.125rem;
}
+109 -8
View File
@@ -1,28 +1,129 @@
/* cyrillic-ext */
@font-face {
font-family: "Source Sans 3";
font-style: italic;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-cyrillic-ext.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Source Sans 3";
font-style: italic;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-cyrillic.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
font-family: "Source Sans 3";
font-style: italic;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Source Sans 3";
font-style: italic;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: "Source Sans 3";
font-style: normal;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-cyrillic-ext.woff2) format("woff2");
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: "Source Sans 3";
font-style: normal;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-cyrillic.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* latin-ext */
@font-face {
font-family: "Source Sans 3";
font-style: normal;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Source Sans 3";
font-style: normal;
font-weight: 400 600;
font-display: swap;
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* Legacy support */
/* latin-ext */
@font-face {
font-family: "Source Sans Pro";
font-style: italic;
font-weight: 400;
font-weight: 400 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic.woff2") format("woff2");
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Source Sans Pro";
font-style: italic;
font-weight: 600;
font-weight: 400 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic-latin-semibold.woff2") format("woff2");
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/italic-latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
font-weight: 400;
font-weight: 400 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-normal.woff2") format("woff2");
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-latin-ext.woff2) format("woff2");
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
font-weight: 600;
font-weight: 400 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-normal.woff2") format("woff2");
src: url(https://cdn.nav.no/aksel/fonts/SourceSans3/normal-latin.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F,
U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+2 -5
View File
@@ -53,6 +53,7 @@
padding: var(--ac-button-padding-small, var(--__ac-button-padding));
min-height: 2rem;
min-width: 2rem;
}
.navds-button--xsmall {
@@ -112,11 +113,7 @@
margin: 0;
}
.navds-button--small .navds-button__icon {
--ac-button-icon-margin: -2px;
}
.navds-button--xsmall .navds-button__icon {
:where(.navds-button--xsmall, .navds-button--small) .navds-button__icon {
--ac-button-icon-margin: -2px;
font-size: 1.25rem;
+6 -4
View File
@@ -51,6 +51,7 @@
--__ac-copybutton-padding: var(--a-spacing-1) var(--a-spacing-3) var(--a-spacing-1) var(--a-spacing-2);
min-height: 2rem;
min-width: 2rem;
}
.navds-copybutton--small.navds-copybutton--icon-right {
@@ -61,6 +62,7 @@
--__ac-copybutton-padding: var(--a-spacing-05) var(--a-spacing-2) var(--a-spacing-05) var(--a-spacing-1);
min-height: 1.5rem;
min-width: 1.5rem;
}
.navds-copybutton--xsmall.navds-copybutton--icon-right {
@@ -79,15 +81,15 @@
--__ac-copybutton-padding: var(--a-spacing-05);
}
.navds-copybutton--xsmall .navds-copybutton__icon {
font-size: 1.25rem;
}
.navds-copybutton__icon {
font-size: 1.5rem;
display: flex;
}
:where(.navds-copybutton--xsmall, .navds-copybutton--small) .navds-copybutton__icon {
font-size: 1.25rem;
}
.navds-copybutton__icon:first-of-type {
margin-left: -0.25rem;
}
+1 -1
View File
@@ -47,6 +47,6 @@
.navds-form-progress__stepper {
border: 2px solid var(--a-border-subtle);
border-radius: var(--a-border-radius-large);
padding: var(--a-spacing-2) var(--a-spacing-4) 0;
padding: var(--a-spacing-2) var(--a-spacing-4);
margin-top: var(--a-spacing-1);
}
+7 -15
View File
@@ -15,15 +15,7 @@
}
.navds-list li {
margin-block-end: var(--a-spacing-4);
}
.navds-list--small li {
margin-block-end: var(--a-spacing-3);
}
.navds-list--nested li {
margin-block-end: var(--a-spacing-3);
margin-block-end: var(--a-spacing-2);
}
.navds-list:last-child > ul > li:last-child,
@@ -50,25 +42,25 @@
.navds-list .navds-list__item-marker--bullet {
display: flex;
align-items: center;
height: var(--a-font-line-height-large);
height: var(--a-font-line-height-xlarge);
grid-column: 1 / 2;
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
}
.navds-list--small .navds-list__item-marker--bullet {
height: var(--a-font-line-height-medium);
height: var(--a-font-line-height-large);
}
.navds-list__item-marker--icon {
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
display: flex;
align-items: center;
height: var(--a-font-line-height-large);
height: var(--a-font-line-height-xlarge);
padding-block-end: 1px;
}
.navds-list--small .navds-list__item-marker--icon {
height: var(--a-font-line-height-medium);
height: var(--a-font-line-height-large);
}
.navds-list__item-marker--icon svg {
@@ -93,10 +85,10 @@
font-weight: var(--a-font-weight-bold);
font-size: var(--a-font-size-large);
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
line-height: var(--a-font-line-height-large);
line-height: var(--a-font-line-height-xlarge);
}
.navds-list--small ol li::marker {
font-size: var(--a-font-size-small);
line-height: var(--a-font-line-height-medium);
line-height: var(--a-font-line-height-large);
}
+8 -3
View File
@@ -59,17 +59,22 @@
@media (min-width: 480px) {
.navds-modal {
max-width: calc(100% - 6px - 2em);
max-width: calc(100% - 2em);
}
.navds-modal--autowidth {
max-width: min(700px, calc(100% - 6px - 2em));
max-width: min(700px, calc(100% - 2em));
}
}
@media (min-height: 480px) {
.navds-modal {
max-height: calc(100% - 6px - 2em);
max-height: calc(100% - 2em);
}
.navds-modal--top {
margin-top: 2em;
max-height: calc(100% - 4em);
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "6.10.0",
"version": "6.14.0",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^6.10.0",
"@navikt/ds-tokens": "^6.14.0",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+4
View File
@@ -1,3 +1,7 @@
.navds-pagination {
position: relative;
}
.navds-pagination__list {
margin: 0;
padding: 0;
+777
View File
@@ -0,0 +1,777 @@
.navds-r-p {
--__ac-r-p-xs: initial;
--__ac-r-p-sm: var(--__ac-r-p-xs);
--__ac-r-p-md: var(--__ac-r-p-sm);
--__ac-r-p-lg: var(--__ac-r-p-md);
--__ac-r-p-xl: var(--__ac-r-p-lg);
--__ac-r-p-2xl: var(--__ac-r-p-xl);
--__ac-r-padding: var(--__ac-r-p-xs);
padding: var(--__ac-r-padding);
}
.navds-r-pi {
--__ac-r-pi-xs: initial;
--__ac-r-pi-sm: var(--__ac-r-pi-xs);
--__ac-r-pi-md: var(--__ac-r-pi-sm);
--__ac-r-pi-lg: var(--__ac-r-pi-md);
--__ac-r-pi-xl: var(--__ac-r-pi-lg);
--__ac-r-pi-2xl: var(--__ac-r-pi-xl);
--__ac-r-pi: var(--__ac-r-pi-xs);
padding-inline: var(--__ac-r-pi, var(--__ac-r-padding));
}
.navds-r-pb {
--__ac-r-pb-xs: initial;
--__ac-r-pb-sm: var(--__ac-r-pb-xs);
--__ac-r-pb-md: var(--__ac-r-pb-sm);
--__ac-r-pb-lg: var(--__ac-r-pb-md);
--__ac-r-pb-xl: var(--__ac-r-pb-lg);
--__ac-r-pb-2xl: var(--__ac-r-pb-xl);
--__ac-r-pb: var(--__ac-r-pb-xs);
padding-block: var(--__ac-r-pb, var(--__ac-r-padding));
}
.navds-r-m {
--__ac-r-m-xs: initial;
--__ac-r-m-sm: var(--__ac-r-m-xs);
--__ac-r-m-md: var(--__ac-r-m-sm);
--__ac-r-m-lg: var(--__ac-r-m-md);
--__ac-r-m-xl: var(--__ac-r-m-lg);
--__ac-r-m-2xl: var(--__ac-r-m-xl);
--__ac-r-margin: var(--__ac-r-m-xs);
margin: var(--__ac-r-margin);
}
.navds-r-mi {
--__ac-r-mi-xs: initial;
--__ac-r-mi-sm: var(--__ac-r-mi-xs);
--__ac-r-mi-md: var(--__ac-r-mi-sm);
--__ac-r-mi-lg: var(--__ac-r-mi-md);
--__ac-r-mi-xl: var(--__ac-r-mi-lg);
--__ac-r-mi-2xl: var(--__ac-r-mi-xl);
--__ac-r-mi: var(--__ac-r-mi-xs);
margin-inline: var(--__ac-r-mi, var(--__ac-r-margin));
}
.navds-r-mb {
--__ac-r-mb-xs: initial;
--__ac-r-mb-sm: var(--__ac-r-mb-xs);
--__ac-r-mb-md: var(--__ac-r-mb-sm);
--__ac-r-mb-lg: var(--__ac-r-mb-md);
--__ac-r-mb-xl: var(--__ac-r-mb-lg);
--__ac-r-mb-2xl: var(--__ac-r-mb-xl);
--__ac-r-mb: var(--__ac-r-mb-xs);
margin-block: var(--__ac-r-mb, var(--__ac-r-margin));
}
.navds-r-w {
--__ac-r-w-xs: initial;
--__ac-r-w-sm: var(--__ac-r-w-xs);
--__ac-r-w-md: var(--__ac-r-w-sm);
--__ac-r-w-lg: var(--__ac-r-w-md);
--__ac-r-w-xl: var(--__ac-r-w-lg);
--__ac-r-w-2xl: var(--__ac-r-w-xl);
--__ac-r-w: var(--__ac-r-w-xs);
width: var(--__ac-r-w);
}
.navds-r-minw {
--__ac-r-minw-xs: initial;
--__ac-r-minw-sm: var(--__ac-r-minw-xs);
--__ac-r-minw-md: var(--__ac-r-minw-sm);
--__ac-r-minw-lg: var(--__ac-r-minw-md);
--__ac-r-minw-xl: var(--__ac-r-minw-lg);
--__ac-r-minw-2xl: var(--__ac-r-minw-xl);
--__ac-r-minw: var(--__ac-r-minw-xs);
min-width: var(--__ac-r-minw);
}
.navds-r-maxw {
--__ac-r-maxw-xs: initial;
--__ac-r-maxw-sm: var(--__ac-r-maxw-xs);
--__ac-r-maxw-md: var(--__ac-r-maxw-sm);
--__ac-r-maxw-lg: var(--__ac-r-maxw-md);
--__ac-r-maxw-xl: var(--__ac-r-maxw-lg);
--__ac-r-maxw-2xl: var(--__ac-r-maxw-xl);
--__ac-r-maxw: var(--__ac-r-maxw-xs);
max-width: var(--__ac-r-maxw);
}
.navds-r-h {
--__ac-r-h-xs: initial;
--__ac-r-h-sm: var(--__ac-r-h-xs);
--__ac-r-h-md: var(--__ac-r-h-sm);
--__ac-r-h-lg: var(--__ac-r-h-md);
--__ac-r-h-xl: var(--__ac-r-h-lg);
--__ac-r-h-2xl: var(--__ac-r-h-xl);
--__ac-r-h: var(--__ac-r-h-xs);
height: var(--__ac-r-h);
}
.navds-r-minh {
--__ac-r-minh-xs: initial;
--__ac-r-minh-sm: var(--__ac-r-minh-xs);
--__ac-r-minh-md: var(--__ac-r-minh-sm);
--__ac-r-minh-lg: var(--__ac-r-minh-md);
--__ac-r-minh-xl: var(--__ac-r-minh-lg);
--__ac-r-minh-2xl: var(--__ac-r-minh-xl);
--__ac-r-minh: var(--__ac-r-minh-xs);
min-height: var(--__ac-r-minh);
}
.navds-r-maxh {
--__ac-r-maxh-xs: initial;
--__ac-r-maxh-sm: var(--__ac-r-maxh-xs);
--__ac-r-maxh-md: var(--__ac-r-maxh-sm);
--__ac-r-maxh-lg: var(--__ac-r-maxh-md);
--__ac-r-maxh-xl: var(--__ac-r-maxh-lg);
--__ac-r-maxh-2xl: var(--__ac-r-maxh-xl);
--__ac-r-maxh: var(--__ac-r-maxh-xs);
max-height: var(--__ac-r-maxh);
}
.navds-r-position {
--__ac-r-position-xs: initial;
--__ac-r-position-sm: var(--__ac-r-position-xs);
--__ac-r-position-md: var(--__ac-r-position-sm);
--__ac-r-position-lg: var(--__ac-r-position-md);
--__ac-r-position-xl: var(--__ac-r-position-lg);
--__ac-r-position-2xl: var(--__ac-r-position-xl);
--__ac-r-position: var(--__ac-r-position-xs);
position: var(--__ac-r-position);
}
.navds-r-inset {
--__ac-r-inset-xs: initial;
--__ac-r-inset-sm: var(--__ac-r-inset-xs);
--__ac-r-inset-md: var(--__ac-r-inset-sm);
--__ac-r-inset-lg: var(--__ac-r-inset-md);
--__ac-r-inset-xl: var(--__ac-r-inset-lg);
--__ac-r-inset-2xl: var(--__ac-r-inset-xl);
--__ac-r-inset: var(--__ac-r-inset-xs);
inset: var(--__ac-r-inset);
}
.navds-r-top {
--__ac-r-top-xs: initial;
--__ac-r-top-sm: var(--__ac-r-top-xs);
--__ac-r-top-md: var(--__ac-r-top-sm);
--__ac-r-top-lg: var(--__ac-r-top-md);
--__ac-r-top-xl: var(--__ac-r-top-lg);
--__ac-r-top-2xl: var(--__ac-r-top-xl);
--__ac-r-top: var(--__ac-r-top-xs);
top: var(--__ac-r-top);
}
.navds-r-right {
--__ac-r-right-xs: initial;
--__ac-r-right-sm: var(--__ac-r-right-xs);
--__ac-r-right-md: var(--__ac-r-right-sm);
--__ac-r-right-lg: var(--__ac-r-right-md);
--__ac-r-right-xl: var(--__ac-r-right-lg);
--__ac-r-right-2xl: var(--__ac-r-right-xl);
--__ac-r-right: var(--__ac-r-right-xs);
right: var(--__ac-r-right);
}
.navds-r-bottom {
--__ac-r-bottom-xs: initial;
--__ac-r-bottom-sm: var(--__ac-r-bottom-xs);
--__ac-r-bottom-md: var(--__ac-r-bottom-sm);
--__ac-r-bottom-lg: var(--__ac-r-bottom-md);
--__ac-r-bottom-xl: var(--__ac-r-bottom-lg);
--__ac-r-bottom-2xl: var(--__ac-r-bottom-xl);
--__ac-r-bottom: var(--__ac-r-bottom-xs);
bottom: var(--__ac-r-bottom);
}
.navds-r-left {
--__ac-r-left-xs: initial;
--__ac-r-left-sm: var(--__ac-r-left-xs);
--__ac-r-left-md: var(--__ac-r-left-sm);
--__ac-r-left-lg: var(--__ac-r-left-md);
--__ac-r-left-xl: var(--__ac-r-left-lg);
--__ac-r-left-2xl: var(--__ac-r-left-xl);
--__ac-r-left: var(--__ac-r-left-xs);
left: var(--__ac-r-left);
}
.navds-r-overflow {
--__ac-r-overflow-xs: initial;
--__ac-r-overflow-sm: var(--__ac-r-overflow-xs);
--__ac-r-overflow-md: var(--__ac-r-overflow-sm);
--__ac-r-overflow-lg: var(--__ac-r-overflow-md);
--__ac-r-overflow-xl: var(--__ac-r-overflow-lg);
--__ac-r-overflow-2xl: var(--__ac-r-overflow-xl);
--__ac-r-overflow: var(--__ac-r-overflow-xs);
overflow: var(--__ac-r-overflow);
}
.navds-r-overflowx {
--__ac-r-overflowx-xs: initial;
--__ac-r-overflowx-sm: var(--__ac-r-overflowx-xs);
--__ac-r-overflowx-md: var(--__ac-r-overflowx-sm);
--__ac-r-overflowx-lg: var(--__ac-r-overflowx-md);
--__ac-r-overflowx-xl: var(--__ac-r-overflowx-lg);
--__ac-r-overflowx-2xl: var(--__ac-r-overflowx-xl);
--__ac-r-overflowx: var(--__ac-r-overflowx-xs);
overflow-x: var(--__ac-r-overflowx);
}
.navds-r-overflowy {
--__ac-r-overflowy-xs: initial;
--__ac-r-overflowy-sm: var(--__ac-r-overflowy-xs);
--__ac-r-overflowy-md: var(--__ac-r-overflowy-sm);
--__ac-r-overflowy-lg: var(--__ac-r-overflowy-md);
--__ac-r-overflowy-xl: var(--__ac-r-overflowy-lg);
--__ac-r-overflowy-2xl: var(--__ac-r-overflowy-xl);
--__ac-r-overflowy: var(--__ac-r-overflowy-xs);
overflow-y: var(--__ac-r-overflowy);
}
.navds-r-flex-basis {
--__ac-r-flex-basis-xs: initial;
--__ac-r-flex-basis-sm: var(--__ac-r-flex-basis-xs);
--__ac-r-flex-basis-md: var(--__ac-r-flex-basis-sm);
--__ac-r-flex-basis-lg: var(--__ac-r-flex-basis-md);
--__ac-r-flex-basis-xl: var(--__ac-r-flex-basis-lg);
--__ac-r-flex-basis-2xl: var(--__ac-r-flex-basis-xl);
--__ac-r-flex-basis: var(--__ac-r-flex-basis-xs);
flex-basis: var(--__ac-r-flex-basis);
}
.navds-r-flex-grow {
--__ac-r-flex-grow-xs: initial;
--__ac-r-flex-grow-sm: var(--__ac-r-flex-grow-xs);
--__ac-r-flex-grow-md: var(--__ac-r-flex-grow-sm);
--__ac-r-flex-grow-lg: var(--__ac-r-flex-grow-md);
--__ac-r-flex-grow-xl: var(--__ac-r-flex-grow-lg);
--__ac-r-flex-grow-2xl: var(--__ac-r-flex-grow-xl);
--__ac-r-flex-grow: var(--__ac-r-flex-grow-xs);
flex-grow: var(--__ac-r-flex-grow);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink-xs: initial;
--__ac-r-flex-shrink-sm: var(--__ac-r-flex-shrink-xs);
--__ac-r-flex-shrink-md: var(--__ac-r-flex-shrink-sm);
--__ac-r-flex-shrink-lg: var(--__ac-r-flex-shrink-md);
--__ac-r-flex-shrink-xl: var(--__ac-r-flex-shrink-lg);
--__ac-r-flex-shrink-2xl: var(--__ac-r-flex-shrink-xl);
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-xs);
flex-shrink: var(--__ac-r-flex-shrink);
}
@media (min-width: 480px) {
.navds-r-p {
--__ac-r-padding: var(--__ac-r-p-sm);
}
.navds-r-pi {
--__ac-r-pi: var(--__ac-r-pi-sm);
}
.navds-r-pb {
--__ac-r-pb: var(--__ac-r-pb-sm);
}
.navds-r-m {
--__ac-r-margin: var(--__ac-r-m-sm);
}
.navds-r-mi {
--__ac-r-mi: var(--__ac-r-mi-sm);
}
.navds-r-mb {
--__ac-r-mb: var(--__ac-r-mb-sm);
}
.navds-r-w {
--__ac-r-w: var(--__ac-r-w-sm);
}
.navds-r-minw {
--__ac-r-minw: var(--__ac-r-minw-sm);
}
.navds-r-maxw {
--__ac-r-maxw: var(--__ac-r-maxw-sm);
}
.navds-r-h {
--__ac-r-h: var(--__ac-r-h-sm);
}
.navds-r-minh {
--__ac-r-minh: var(--__ac-r-minh-sm);
}
.navds-r-maxh {
--__ac-r-maxh: var(--__ac-r-maxh-sm);
}
.navds-r-position {
--__ac-r-position: var(--__ac-r-position-sm);
}
.navds-r-inset {
--__ac-r-inset: var(--__ac-r-inset-sm);
}
.navds-r-top {
--__ac-r-top: var(--__ac-r-top-sm);
}
.navds-r-right {
--__ac-r-right: var(--__ac-r-right-sm);
}
.navds-r-bottom {
--__ac-r-bottom: var(--__ac-r-bottom-sm);
}
.navds-r-left {
--__ac-r-left: var(--__ac-r-left-sm);
}
.navds-r-overflow {
--__ac-r-overflow: var(--__ac-r-overflow-sm);
}
.navds-r-overflowx {
--__ac-r-overflowx: var(--__ac-r-overflowx-sm);
}
.navds-r-overflowy {
--__ac-r-overflowy: var(--__ac-r-overflowy-sm);
}
.navds-r-flex-basis {
--__ac-r-flex-basis: var(--__ac-r-flex-basis-sm);
}
.navds-r-flex-grow {
--__ac-r-flex-grow: var(--__ac-r-flex-grow-sm);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-sm);
}
}
@media (min-width: 768px) {
.navds-r-p {
--__ac-r-padding: var(--__ac-r-p-md);
}
.navds-r-pi {
--__ac-r-pi: var(--__ac-r-pi-md);
}
.navds-r-pb {
--__ac-r-pb: var(--__ac-r-pb-md);
}
.navds-r-m {
--__ac-r-margin: var(--__ac-r-m-md);
}
.navds-r-mi {
--__ac-r-mi: var(--__ac-r-mi-md);
}
.navds-r-mb {
--__ac-r-mb: var(--__ac-r-mb-md);
}
.navds-r-w {
--__ac-r-w: var(--__ac-r-w-md);
}
.navds-r-minw {
--__ac-r-minw: var(--__ac-r-minw-md);
}
.navds-r-maxw {
--__ac-r-maxw: var(--__ac-r-maxw-md);
}
.navds-r-h {
--__ac-r-h: var(--__ac-r-h-md);
}
.navds-r-minh {
--__ac-r-minh: var(--__ac-r-minh-md);
}
.navds-r-maxh {
--__ac-r-maxh: var(--__ac-r-maxh-md);
}
.navds-r-position {
--__ac-r-position: var(--__ac-r-position-md);
}
.navds-r-inset {
--__ac-r-inset: var(--__ac-r-inset-md);
}
.navds-r-top {
--__ac-r-top: var(--__ac-r-top-md);
}
.navds-r-right {
--__ac-r-right: var(--__ac-r-right-md);
}
.navds-r-bottom {
--__ac-r-bottom: var(--__ac-r-bottom-md);
}
.navds-r-left {
--__ac-r-left: var(--__ac-r-left-md);
}
.navds-r-overflow {
--__ac-r-overflow: var(--__ac-r-overflow-md);
}
.navds-r-overflowx {
--__ac-r-overflowx: var(--__ac-r-overflowx-md);
}
.navds-r-overflowy {
--__ac-r-overflowy: var(--__ac-r-overflowy-md);
}
.navds-r-flex-basis {
--__ac-r-flex-basis: var(--__ac-r-flex-basis-md);
}
.navds-r-flex-grow {
--__ac-r-flex-grow: var(--__ac-r-flex-grow-md);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-md);
}
}
@media (min-width: 1024px) {
.navds-r-p {
--__ac-r-padding: var(--__ac-r-p-lg);
}
.navds-r-pi {
--__ac-r-pi: var(--__ac-r-pi-lg);
}
.navds-r-pb {
--__ac-r-pb: var(--__ac-r-pb-lg);
}
.navds-r-m {
--__ac-r-margin: var(--__ac-r-m-lg);
}
.navds-r-mi {
--__ac-r-mi: var(--__ac-r-mi-lg);
}
.navds-r-mb {
--__ac-r-mb: var(--__ac-r-mb-lg);
}
.navds-r-w {
--__ac-r-w: var(--__ac-r-w-lg);
}
.navds-r-minw {
--__ac-r-minw: var(--__ac-r-minw-lg);
}
.navds-r-maxw {
--__ac-r-maxw: var(--__ac-r-maxw-lg);
}
.navds-r-h {
--__ac-r-h: var(--__ac-r-h-lg);
}
.navds-r-minh {
--__ac-r-minh: var(--__ac-r-minh-lg);
}
.navds-r-maxh {
--__ac-r-maxh: var(--__ac-r-maxh-lg);
}
.navds-r-position {
--__ac-r-position: var(--__ac-r-position-lg);
}
.navds-r-inset {
--__ac-r-inset: var(--__ac-r-inset-lg);
}
.navds-r-top {
--__ac-r-top: var(--__ac-r-top-lg);
}
.navds-r-right {
--__ac-r-right: var(--__ac-r-right-lg);
}
.navds-r-bottom {
--__ac-r-bottom: var(--__ac-r-bottom-lg);
}
.navds-r-left {
--__ac-r-left: var(--__ac-r-left-lg);
}
.navds-r-overflow {
--__ac-r-overflow: var(--__ac-r-overflow-lg);
}
.navds-r-overflowx {
--__ac-r-overflowx: var(--__ac-r-overflowx-lg);
}
.navds-r-overflowy {
--__ac-r-overflowy: var(--__ac-r-overflowy-lg);
}
.navds-r-flex-basis {
--__ac-r-flex-basis: var(--__ac-r-flex-basis-lg);
}
.navds-r-flex-grow {
--__ac-r-flex-grow: var(--__ac-r-flex-grow-lg);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-lg);
}
}
@media (min-width: 1280px) {
.navds-r-p {
--__ac-r-padding: var(--__ac-r-p-xl);
}
.navds-r-pi {
--__ac-r-pi: var(--__ac-r-pi-xl);
}
.navds-r-pb {
--__ac-r-pb: var(--__ac-r-pb-xl);
}
.navds-r-m {
--__ac-r-margin: var(--__ac-r-m-xl);
}
.navds-r-mi {
--__ac-r-mi: var(--__ac-r-mi-xl);
}
.navds-r-mb {
--__ac-r-mb: var(--__ac-r-mb-xl);
}
.navds-r-w {
--__ac-r-w: var(--__ac-r-w-xl);
}
.navds-r-minw {
--__ac-r-minw: var(--__ac-r-minw-xl);
}
.navds-r-maxw {
--__ac-r-maxw: var(--__ac-r-maxw-xl);
}
.navds-r-h {
--__ac-r-h: var(--__ac-r-h-xl);
}
.navds-r-minh {
--__ac-r-minh: var(--__ac-r-minh-xl);
}
.navds-r-maxh {
--__ac-r-maxh: var(--__ac-r-maxh-xl);
}
.navds-r-position {
--__ac-r-position: var(--__ac-r-position-xl);
}
.navds-r-inset {
--__ac-r-inset: var(--__ac-r-inset-xl);
}
.navds-r-top {
--__ac-r-top: var(--__ac-r-top-xl);
}
.navds-r-right {
--__ac-r-right: var(--__ac-r-right-xl);
}
.navds-r-bottom {
--__ac-r-bottom: var(--__ac-r-bottom-xl);
}
.navds-r-left {
--__ac-r-left: var(--__ac-r-left-xl);
}
.navds-r-overflow {
--__ac-r-overflow: var(--__ac-r-overflow-xl);
}
.navds-r-overflowx {
--__ac-r-overflowx: var(--__ac-r-overflowx-xl);
}
.navds-r-overflowy {
--__ac-r-overflowy: var(--__ac-r-overflowy-xl);
}
.navds-r-flex-basis {
--__ac-r-flex-basis: var(--__ac-r-flex-basis-xl);
}
.navds-r-flex-grow {
--__ac-r-flex-grow: var(--__ac-r-flex-grow-xl);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-xl);
}
}
@media (min-width: 1440px) {
.navds-r-p {
--__ac-r-padding: var(--__ac-r-p-2xl);
}
.navds-r-pi {
--__ac-r-pi: var(--__ac-r-pi-2xl);
}
.navds-r-pb {
--__ac-r-pb: var(--__ac-r-pb-2xl);
}
.navds-r-m {
--__ac-r-margin: var(--__ac-r-m-2xl);
}
.navds-r-mi {
--__ac-r-mi: var(--__ac-r-mi-2xl);
}
.navds-r-mb {
--__ac-r-mb: var(--__ac-r-mb-2xl);
}
.navds-r-w {
--__ac-r-w: var(--__ac-r-w-2xl);
}
.navds-r-minw {
--__ac-r-minw: var(--__ac-r-minw-2xl);
}
.navds-r-maxw {
--__ac-r-maxw: var(--__ac-r-maxw-2xl);
}
.navds-r-h {
--__ac-r-h: var(--__ac-r-h-2xl);
}
.navds-r-minh {
--__ac-r-minh: var(--__ac-r-minh-2xl);
}
.navds-r-maxh {
--__ac-r-maxh: var(--__ac-r-maxh-2xl);
}
.navds-r-position {
--__ac-r-position: var(--__ac-r-position-2xl);
}
.navds-r-inset {
--__ac-r-inset: var(--__ac-r-inset-2xl);
}
.navds-r-top {
--__ac-r-top: var(--__ac-r-top-2xl);
}
.navds-r-right {
--__ac-r-right: var(--__ac-r-right-2xl);
}
.navds-r-bottom {
--__ac-r-bottom: var(--__ac-r-bottom-2xl);
}
.navds-r-left {
--__ac-r-left: var(--__ac-r-left-2xl);
}
.navds-r-overflow {
--__ac-r-overflow: var(--__ac-r-overflow-2xl);
}
.navds-r-overflowx {
--__ac-r-overflowx: var(--__ac-r-overflowx-2xl);
}
.navds-r-overflowy {
--__ac-r-overflowy: var(--__ac-r-overflowy-2xl);
}
.navds-r-flex-basis {
--__ac-r-flex-basis: var(--__ac-r-flex-basis-2xl);
}
.navds-r-flex-grow {
--__ac-r-flex-grow: var(--__ac-r-flex-grow-2xl);
}
.navds-r-flex-shrink {
--__ac-r-flex-shrink: var(--__ac-r-flex-shrink-2xl);
}
}
+27 -49
View File
@@ -1,29 +1,23 @@
.navds-box {
--__ac-box-padding-xs: initial;
--__ac-box-padding-sm: var(--__ac-box-padding-xs);
--__ac-box-padding-md: var(--__ac-box-padding-sm);
--__ac-box-padding-lg: var(--__ac-box-padding-md);
--__ac-box-padding-xl: var(--__ac-box-padding-lg);
--__ac-box-padding-2xl: var(--__ac-box-padding-xl);
--__ac-box-padding-inline-xs: initial;
--__ac-box-padding-inline-sm: var(--__ac-box-padding-inline-xs);
--__ac-box-padding-inline-md: var(--__ac-box-padding-inline-sm);
--__ac-box-padding-inline-lg: var(--__ac-box-padding-inline-md);
--__ac-box-padding-inline-xl: var(--__ac-box-padding-inline-lg);
--__ac-box-padding-inline-2xl: var(--__ac-box-padding-inline-xl);
--__ac-box-padding-block-xs: initial;
--__ac-box-padding-block-sm: var(--__ac-box-padding-block-xs);
--__ac-box-padding-block-md: var(--__ac-box-padding-block-sm);
--__ac-box-padding-block-lg: var(--__ac-box-padding-block-md);
--__ac-box-padding-block-xl: var(--__ac-box-padding-block-lg);
--__ac-box-padding-block-2xl: var(--__ac-box-padding-block-xl);
--__ac-box-padding: var(--__ac-box-padding-xs);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-xs);
--__ac-box-padding-block: var(--__ac-box-padding-block-xs);
.navds-box-bg {
--__ac-box-background: initial;
background-color: var(--__ac-box-background);
}
.navds-box-border-color {
--__ac-box-border-color: initial;
--__ac-box-shadow: initial;
border-color: var(--__ac-box-border-color);
}
.navds-box-border-width {
--__ac-box-border-width: initial;
border-style: solid;
border-width: var(--__ac-box-border-width, 0);
}
.navds-box-border-radius {
--__ac-box-border-radius-xs: initial;
--__ac-box-border-radius-sm: var(--__ac-box-border-radius-xs);
--__ac-box-border-radius-md: var(--__ac-box-border-radius-sm);
@@ -32,57 +26,41 @@
--__ac-box-border-radius-2xl: var(--__ac-box-border-radius-xl);
--__ac-box-border-radius: var(--__ac-box-border-radius-xs);
padding-inline: var(--__ac-box-padding-inline, var(--__ac-box-padding));
padding-block: var(--__ac-box-padding-block, var(--__ac-box-padding));
background-color: var(--__ac-box-background);
border-style: solid;
border-color: var(--__ac-box-border-color);
border-radius: var(--__ac-box-border-radius);
}
.navds-box-shadow {
--__ac-box-shadow: initial;
box-shadow: var(--__ac-box-shadow);
border-width: var(--__ac-box-border-width, 0);
}
@media (min-width: 480px) {
.navds-box {
--__ac-box-padding: var(--__ac-box-padding-sm);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-sm);
--__ac-box-padding-block: var(--__ac-box-padding-block-sm);
.navds-box-border-radius {
--__ac-box-border-radius: var(--__ac-box-border-radius-sm);
}
}
@media (min-width: 768px) {
.navds-box {
--__ac-box-padding: var(--__ac-box-padding-md);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-md);
--__ac-box-padding-block: var(--__ac-box-padding-block-md);
.navds-box-border-radius {
--__ac-box-border-radius: var(--__ac-box-border-radius-md);
}
}
@media (min-width: 1024px) {
.navds-box {
--__ac-box-padding: var(--__ac-box-padding-lg);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-lg);
--__ac-box-padding-block: var(--__ac-box-padding-block-lg);
.navds-box-border-radius {
--__ac-box-border-radius: var(--__ac-box-border-radius-lg);
}
}
@media (min-width: 1280px) {
.navds-box {
--__ac-box-padding: var(--__ac-box-padding-xl);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-xl);
--__ac-box-padding-block: var(--__ac-box-padding-block-xl);
.navds-box-border-radius {
--__ac-box-border-radius: var(--__ac-box-border-radius-xl);
}
}
@media (min-width: 1440px) {
.navds-box {
--__ac-box-padding: var(--__ac-box-padding-2xl);
--__ac-box-padding-inline: var(--__ac-box-padding-inline-2xl);
--__ac-box-padding-block: var(--__ac-box-padding-block-2xl);
.navds-box-border-radius {
--__ac-box-border-radius: var(--__ac-box-border-radius-2xl);
}
}
+26 -3
View File
@@ -6,6 +6,12 @@
--__ac-hgrid-columns-xl: var(--__ac-hgrid-columns-lg);
--__ac-hgrid-columns-2xl: var(--__ac-hgrid-columns-xl);
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xs);
display: grid;
grid-template-columns: var(--__ac-hgrid-columns);
}
.navds-hgrid-gap {
--__ac-hgrid-gap-xs: initial;
--__ac-hgrid-gap-sm: var(--__ac-hgrid-gap-xs);
--__ac-hgrid-gap-md: var(--__ac-hgrid-gap-sm);
@@ -13,17 +19,22 @@
--__ac-hgrid-gap-xl: var(--__ac-hgrid-gap-lg);
--__ac-hgrid-gap-2xl: var(--__ac-hgrid-gap-xl);
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xs);
gap: var(--__ac-hgrid-gap);
}
.navds-hgrid-align {
--__ac-hgrid-align: initial;
display: grid;
grid-template-columns: var(--__ac-hgrid-columns);
gap: var(--__ac-hgrid-gap);
align-items: var(--__ac-hgrid-align);
}
@media (min-width: 480px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-sm);
}
.navds-hgrid-gap {
--__ac-hgrid-gap: var(--__ac-hgrid-gap-sm);
}
}
@@ -31,6 +42,9 @@
@media (min-width: 768px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-md);
}
.navds-hgrid-gap {
--__ac-hgrid-gap: var(--__ac-hgrid-gap-md);
}
}
@@ -38,6 +52,9 @@
@media (min-width: 1024px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-lg);
}
.navds-hgrid-gap {
--__ac-hgrid-gap: var(--__ac-hgrid-gap-lg);
}
}
@@ -45,6 +62,9 @@
@media (min-width: 1280px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xl);
}
.navds-hgrid-gap {
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xl);
}
}
@@ -52,6 +72,9 @@
@media (min-width: 1440px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-2xl);
}
.navds-hgrid-gap {
--__ac-hgrid-gap: var(--__ac-hgrid-gap-2xl);
}
}
+1
View File
@@ -4,3 +4,4 @@
@import "hgrid.css";
@import "stack.css";
@import "responsive.css";
@import "base.css";
+81 -18
View File
@@ -1,5 +1,8 @@
.navds-stack {
--__ac-stack-wrap: initial;
display: flex;
}
.navds-stack-gap {
--__ac-stack-gap-xs: initial;
--__ac-stack-gap-sm: var(--__ac-stack-gap-xs);
--__ac-stack-gap-md: var(--__ac-stack-gap-sm);
@@ -7,13 +10,11 @@
--__ac-stack-gap-xl: var(--__ac-stack-gap-lg);
--__ac-stack-gap-2xl: var(--__ac-stack-gap-xl);
--__ac-stack-gap: var(--__ac-stack-gap-xs);
--__ac-stack-justify-xs: initial;
--__ac-stack-justify-sm: var(--__ac-stack-justify-xs);
--__ac-stack-justify-md: var(--__ac-stack-justify-sm);
--__ac-stack-justify-lg: var(--__ac-stack-justify-md);
--__ac-stack-justify-xl: var(--__ac-stack-justify-lg);
--__ac-stack-justify-2xl: var(--__ac-stack-justify-xl);
--__ac-stack-justify: var(--__ac-stack-justify-xs);
gap: var(--__ac-stack-gap);
}
.navds-stack-align {
--__ac-stack-align-xs: initial;
--__ac-stack-align-sm: var(--__ac-stack-align-xs);
--__ac-stack-align-md: var(--__ac-stack-align-sm);
@@ -21,6 +22,23 @@
--__ac-stack-align-xl: var(--__ac-stack-align-lg);
--__ac-stack-align-2xl: var(--__ac-stack-align-xl);
--__ac-stack-align: var(--__ac-stack-align-xs);
align-items: var(--__ac-stack-align);
}
.navds-stack-justify {
--__ac-stack-justify-xs: initial;
--__ac-stack-justify-sm: var(--__ac-stack-justify-xs);
--__ac-stack-justify-md: var(--__ac-stack-justify-sm);
--__ac-stack-justify-lg: var(--__ac-stack-justify-md);
--__ac-stack-justify-xl: var(--__ac-stack-justify-lg);
--__ac-stack-justify-2xl: var(--__ac-stack-justify-xl);
--__ac-stack-justify: var(--__ac-stack-justify-xs);
justify-content: var(--__ac-stack-justify);
}
.navds-stack-direction {
--__ac-stack-direction-xs: initial;
--__ac-stack-direction-sm: var(--__ac-stack-direction-xs);
--__ac-stack-direction-md: var(--__ac-stack-direction-sm);
@@ -29,11 +47,11 @@
--__ac-stack-direction-2xl: var(--__ac-stack-direction-xl);
--__ac-stack-direction: var(--__ac-stack-direction-xs);
gap: var(--__ac-stack-gap);
display: flex;
align-items: var(--__ac-stack-align);
justify-content: var(--__ac-stack-justify);
flex-flow: var(--__ac-stack-direction) var(--__ac-stack-wrap);
flex-direction: var(--__ac-stack-direction);
}
.navds-stack-wrap {
flex-wrap: wrap;
}
.navds-stack__spacer {
@@ -48,46 +66,91 @@
}
@media (min-width: 480px) {
.navds-stack {
.navds-stack-gap {
--__ac-stack-gap: var(--__ac-stack-gap-sm);
}
.navds-stack-align {
--__ac-stack-align: var(--__ac-stack-align-sm);
}
.navds-stack-justify {
--__ac-stack-justify: var(--__ac-stack-justify-sm);
}
.navds-stack-direction {
--__ac-stack-direction: var(--__ac-stack-direction-sm);
}
}
@media (min-width: 768px) {
.navds-stack {
.navds-stack-gap {
--__ac-stack-gap: var(--__ac-stack-gap-md);
}
.navds-stack-align {
--__ac-stack-align: var(--__ac-stack-align-md);
}
.navds-stack-justify {
--__ac-stack-justify: var(--__ac-stack-justify-md);
}
.navds-stack-direction {
--__ac-stack-direction: var(--__ac-stack-direction-md);
}
}
@media (min-width: 1024px) {
.navds-stack {
.navds-stack-gap {
--__ac-stack-gap: var(--__ac-stack-gap-lg);
}
.navds-stack-align {
--__ac-stack-align: var(--__ac-stack-align-lg);
}
.navds-stack-justify {
--__ac-stack-justify: var(--__ac-stack-justify-lg);
}
.navds-stack-direction {
--__ac-stack-direction: var(--__ac-stack-direction-lg);
}
}
@media (min-width: 1280px) {
.navds-stack {
.navds-stack-gap {
--__ac-stack-gap: var(--__ac-stack-gap-xl);
}
.navds-stack-align {
--__ac-stack-align: var(--__ac-stack-align-xl);
}
.navds-stack-justify {
--__ac-stack-justify: var(--__ac-stack-justify-xl);
}
.navds-stack-direction {
--__ac-stack-direction: var(--__ac-stack-direction-xl);
}
}
@media (min-width: 1440px) {
.navds-stack {
.navds-stack-gap {
--__ac-stack-gap: var(--__ac-stack-gap-2xl);
}
.navds-stack-align {
--__ac-stack-align: var(--__ac-stack-align-2xl);
}
.navds-stack-justify {
--__ac-stack-justify: var(--__ac-stack-justify-2xl);
}
.navds-stack-direction {
--__ac-stack-direction: var(--__ac-stack-direction-2xl);
}
}
+4
View File
@@ -70,6 +70,10 @@
margin: calc(var(--navds-stepper-border-width) * -1) calc(var(--navds-stepper-border-width) * -1) 1.75rem;
}
.navds-stepper__item:last-child .navds-stepper__step {
margin-bottom: 0;
}
@media (forced-colors: active) {
.navds-stepper__step {
background-color: ButtonFace;
+81 -1
View File
@@ -1,5 +1,85 @@
# @navikt/ds-react
## 6.14.0
### Minor Changes
- Breaking change: Combobox.onChange now receive only value as argument, instead of ChangeEvent ([#2974](https://github.com/navikt/aksel/pull/2974))
- Improved search in Combobox - find hits anywhere in the label ([#2974](https://github.com/navikt/aksel/pull/2974))
### Patch Changes
- Tabs, ToggleGroup: Rovingfocus now respects shift, alt, ctrl and meta-keys when navigating. ([#3041](https://github.com/navikt/aksel/pull/3041))
- Prevent NullPointer when adding a new/custom option in Combobox single-select ([#3054](https://github.com/navikt/aksel/pull/3054))
- Combobox: Entering an already selected option and pressing enter no longer removes it ([#3061](https://github.com/navikt/aksel/pull/3061))
- Combobox: Description is now connected to the input field via aria-describedby ([#3065](https://github.com/navikt/aksel/pull/3065))
- Prevent Escape in open Combobox from closing Modals ([#3053](https://github.com/navikt/aksel/pull/3053))
- Updated dependencies [[`8fd183cc9`](https://github.com/navikt/aksel/commit/8fd183cc909a1f27a0a9f496415d280c1207a479)]:
- @navikt/ds-tokens@6.14.0
- @navikt/aksel-icons@6.14.0
## 6.13.0
### Minor Changes
- Modal: New prop `placement` for anchoring the modal to the top of the viewport. ([#3033](https://github.com/navikt/aksel/pull/3033))
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@6.13.0
- @navikt/aksel-icons@6.13.0
## 6.12.0
### Minor Changes
- Primitives: Added support for padding, paddingInline, paddingBlock, margin, marginInline, marginBlock, width, minWidth, maxWidth, height, minHeight, maxHeight, position, inset, top, right, left, bottom, overflow, overflowX, overflowY, flexBasis, flexGrow, flexShrink to Box, HGrid and Stack. ([#3003](https://github.com/navikt/aksel/pull/3003))
### Patch Changes
- ToggleGroup: Selecting already selected value now avoids sending extra event ([#3012](https://github.com/navikt/aksel/pull/3012))
- Alert: Fix alignment, add prop for toggling content max-width, and adjust title on close icon. ([#3007](https://github.com/navikt/aksel/pull/3007))
- Updated dependencies [[`ff8072e35`](https://github.com/navikt/aksel/commit/ff8072e352f4f84f802a6427e1692ff223af722c), [`f1d8e9c4d`](https://github.com/navikt/aksel/commit/f1d8e9c4d357226893da4fdc3feb8063d7d830bb)]:
- @navikt/aksel-icons@6.12.0
- @navikt/ds-tokens@6.12.0
## 6.11.0
### Minor Changes
- FileUpload: Added description-prop for Item. ([#2975](https://github.com/navikt/aksel/pull/2975))
- Pagination: Added prop for hidden heading. ([#2976](https://github.com/navikt/aksel/pull/2976))
- ToggleGroup: Added props `icon` and `label` as a replacement for `children`. Children is marked as deprecated and will be removed in a future major-version. ([#2929](https://github.com/navikt/aksel/pull/2929))
### Patch Changes
- List: Changed List.Item to BodyLong, changed Item spacing from 1rem to 0.5rem. ([#2979](https://github.com/navikt/aksel/pull/2979))
- Page: Removed surface-subtle from background-prop ([#2987](https://github.com/navikt/aksel/pull/2987))
- Updated dependencies []:
- @navikt/ds-tokens@6.11.0
- @navikt/aksel-icons@6.11.0
## 6.10.1
### Patch Changes
- Updated dependencies [[`2cd6b1a39`](https://github.com/navikt/aksel/commit/2cd6b1a3915388535523989c2e998081bc68206c)]:
- @navikt/aksel-icons@6.10.1
- @navikt/ds-tokens@6.10.1
## 6.10.0
### Minor Changes
@@ -1075,7 +1155,7 @@
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/primitives/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "6.10.0",
"version": "6.14.0",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -594,8 +594,8 @@
"dependencies": {
"@floating-ui/react": "0.25.4",
"@floating-ui/react-dom": "^2.0.9",
"@navikt/aksel-icons": "^6.10.0",
"@navikt/ds-tokens": "^6.10.0",
"@navikt/aksel-icons": "^6.14.0",
"@navikt/ds-tokens": "^6.14.0",
"clsx": "^2.1.0",
"date-fns": "^3.0.0",
"react-day-picker": "8.10.0"
+27 -6
View File
@@ -16,7 +16,7 @@ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
*/
children: React.ReactNode;
/**
* Changes colors and icon usage when changed.
* Level of severity. Changes colors and icon.
*/
variant: "error" | "warning" | "info" | "success";
/**
@@ -30,15 +30,20 @@ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
*/
fullWidth?: boolean;
/**
* Removes background from Alert.
* Sets max-width on the content to 43.5rem.
* @default true
*/
contentMaxWidth?: boolean;
/**
* Removes background, border and padding.
* @default false
*/
inline?: boolean;
/**
* Removes close-button(X) when false.
* Adds a close-button (X).
*
* **Requires onClose to be set**.
* @default true
* @default false
*/
closeButton?: boolean;
/**
@@ -85,6 +90,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
variant,
size = "medium",
fullWidth = false,
contentMaxWidth = true,
inline = false,
closeButton = false,
onClose,
@@ -109,7 +115,14 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
)}
>
<Icon variant={variant} className="navds-alert__icon" />
<BodyLong as="div" size={size} className="navds-alert__wrapper">
<BodyLong
as="div"
size={size}
className={cl(
"navds-alert__wrapper",
contentMaxWidth && "navds-alert__wrapper--maxwidth",
)}
>
{children}
</BodyLong>
{closeButton && !inline && (
@@ -120,7 +133,15 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
variant="tertiary-neutral"
onClick={onClose}
type="button"
icon={<XMarkIcon title="Lukk Alert" />}
icon={
<XMarkIcon
title={
["error", "warning"].includes(variant)
? "Lukk varsel"
: "Lukk melding"
}
/>
}
/>
</div>
)}
+82 -171
View File
@@ -1,8 +1,8 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { Meta, StoryFn, StoryObj } from "@storybook/react";
import React from "react";
import { VStack } from "../layout/stack";
import { Link } from "../link";
import { BodyLong, Heading as DsHeading } from "../typography";
import { BodyLong, Heading } from "../typography";
import Alert, { AlertProps } from "./Alert";
const meta: Meta<typeof Alert> = {
@@ -25,11 +25,10 @@ const variants: AlertProps["variant"][] = [
];
export const Controls: Story = {
render: (props) => <Alert {...props} />,
args: {
children: "Id elit esse enim reprehenderit enim nisi veniam nostrud.",
fullWidth: false,
contentMaxWidth: true,
inline: false,
variant: "info",
size: "medium",
@@ -38,7 +37,7 @@ export const Controls: Story = {
argTypes: {
variant: {
control: { type: "radio" },
options: ["info", "error", "warning", "success"],
options: variants,
},
size: {
control: { type: "radio" },
@@ -47,28 +46,28 @@ export const Controls: Story = {
},
};
export const Variants = () => {
export const Variants: StoryFn = () => {
return (
<div className="colgap">
<VStack gap="4">
{variants.map((variant, i) => (
<Alert key={variant} variant={variant}>
{new Array(i + 1).fill(
"Id elit esse enim reprehenderit enim nisi veniam nostrud.",
"Id elit esse enim reprehenderit enim nisi veniam nostrud. ",
)}
</Alert>
))}
{variants.map((variant, i) => (
<Alert key={variant} variant={variant} size="small">
{new Array(i + 1).fill(
"Id elit esse enim reprehenderit enim nisi veniam nostrud.",
"Id elit esse enim reprehenderit enim nisi veniam nostrud. ",
)}
</Alert>
))}
</div>
</VStack>
);
};
export const FullWidth = () => {
export const FullWidth: StoryFn = () => {
return (
<VStack gap="4">
<Alert variant="info" fullWidth>
@@ -81,10 +80,16 @@ export const FullWidth = () => {
);
};
export const Inline = () => {
export const ContentMaxWidthOff: StoryFn = () => (
<Alert variant="info" contentMaxWidth={false}>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi.
</Alert>
);
export const Inline: StoryFn = () => {
return (
<VStack gap="4">
<h2>Inline</h2>
<Alert variant="info" inline>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
@@ -107,13 +112,13 @@ export const Inline = () => {
);
};
export const Heading = () => {
export const WithHeading: StoryFn = () => {
return (
<div className="colgap">
<VStack gap="4">
<Alert variant="info">
<DsHeading spacing size="small" level="3">
<Heading spacing size="small" level="3">
Aliquip duis est in commodo pariatur
</DsHeading>
</Heading>
<BodyLong>
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
@@ -121,184 +126,90 @@ export const Heading = () => {
</BodyLong>
</Alert>
<Alert variant="info" size="small">
<DsHeading spacing size="xsmall" level="3">
<Heading spacing size="xsmall" level="3">
Aliquip duis est in commodo pariatur
</DsHeading>
</Heading>
<BodyLong>
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
sint commodo consequat eu aute.
</BodyLong>
</Alert>
</div>
</VStack>
);
};
export const CloseButton: Story = {
render: () => {
return (
<VStack gap="4">
<h2>CloseButton</h2>
<Alert variant="info" closeButton>
export const CloseButton: StoryFn = () => {
return (
<VStack gap="4">
<Alert variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="success" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim reprehenderit
enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="error" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="warning" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim reprehenderit
enim nisi veniam nostrud.
</Alert>
<Alert variant="info" closeButton>
<Heading size="small" level="3">
Id elit esse enim reprehenderit
</Heading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="info" closeButton>
</BodyLong>
</Alert>
<Alert variant="info" closeButton size="small">
<Heading size="xsmall" level="3">
Id elit esse enim reprehenderit
</Heading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" closeButton>
<DsHeading size="small" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
<Alert variant="info" closeButton size="small">
<DsHeading size="xsmall" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
</VStack>
);
},
</BodyLong>
</Alert>
</VStack>
);
};
export const Chromatic: Story = {
render: () => (
<VStack gap="6">
<VStack gap="2">
<h2>Variant</h2>
{variants.map((variant, i) => (
<Alert key={variant} variant={variant}>
{new Array(i + 1).fill(
"Id elit esse enim reprehenderit enim nisi veniam nostrud.",
)}
</Alert>
))}
</VStack>
<VStack gap="2">
<h2>Small</h2>
<Alert variant="info" size="small">
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" size="small">
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
</VStack>
<VStack gap="2">
<h2>FullWidth</h2>
<Alert variant="info" fullWidth>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" size="small" fullWidth>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
</VStack>
<VStack gap="2">
<h2>Inline</h2>
<Alert variant="info" inline>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" inline>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" size="small" inline>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" size="small" inline>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
</VStack>
<VStack gap="2">
<h2>Heading</h2>
<Alert variant="info">
<DsHeading size="small" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
<Alert variant="info" size="small">
<DsHeading size="xsmall" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong size="small">
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
</VStack>
<VStack gap="2">
<h2>CloseButton</h2>
<Alert variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert size="small" variant="info" closeButton>
Id elit esse enim reprehenderit enim nisi veniam nostrud. Id elit esse
enim reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud. Id elit esse enim
reprehenderit enim nisi veniam nostrud.
</Alert>
<Alert variant="info" closeButton>
<DsHeading size="small" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong>
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
<Alert variant="info" size="small" closeButton>
<DsHeading size="xsmall" level="3">
Id elit esse enim reprehenderit
</DsHeading>
<BodyLong size="small">
Id elit esse enim reprehenderit enim nisi veniam nostrud.
</BodyLong>
</Alert>
</VStack>
<div>
<h2>Variants</h2>
<Variants />
<h2>FullWidth</h2>
<FullWidth />
<h2>ContentMaxWidthOff</h2>
<ContentMaxWidthOff />
<h2>Inline</h2>
<Inline />
<h2>Heading</h2>
<WithHeading />
<h2>CloseButton</h2>
<CloseButton />
<h2>Links</h2>
<VStack gap="2">
<h2>Links</h2>
{variants.map((variant) => (
<Alert key={variant} variant={variant}>
<Link href="#">Id elit esse enim reprehenderit</Link>
</Alert>
))}
</VStack>
</VStack>
</div>
),
parameters: {
chromatic: { disable: false },
@@ -61,28 +61,28 @@ export const Controls: Story = {
variant: {
control: {
type: "radio",
options: [
"primary",
"secondary",
"tertiary",
"danger",
"primary-neutral",
"secondary-neutral",
"tertiary-neutral",
],
},
options: [
"primary",
"secondary",
"tertiary",
"danger",
"primary-neutral",
"secondary-neutral",
"tertiary-neutral",
],
},
size: {
control: {
type: "radio",
options: ["medium", "small", "xsmall"],
},
options: ["medium", "small", "xsmall"],
},
iconPosition: {
control: {
type: "radio",
options: ["left", "right"],
},
options: ["left", "right"],
},
},
};
@@ -1,5 +1,5 @@
import React, { forwardRef } from "react";
import { Slot } from "../../util/Slot";
import { Slot } from "../../slot/Slot";
import { useCollapsibleContext } from "../Collapsible.context";
export interface CollapsibleContentProps
@@ -1,5 +1,5 @@
import React, { forwardRef } from "react";
import { Slot } from "../../util/Slot";
import { Slot } from "../../slot/Slot";
import { composeEventHandlers } from "../../util/composeEventHandlers";
import { useCollapsibleContext } from "../Collapsible.context";
@@ -8,6 +8,9 @@ import { Tooltip } from "../tooltip";
const meta: Meta<typeof CopyButton> = {
title: "ds-react/CopyButton",
component: CopyButton,
parameters: {
chromatic: { disable: true },
},
};
export default meta;
@@ -32,9 +35,6 @@ export const Default: Story = {
text: "",
activeText: "",
},
parameters: {
chromatic: { disable: true },
},
};
export const Interaction: Story = {
@@ -69,7 +69,7 @@ export interface DatePickerDefaultProps
dropdownCaption?: boolean;
/**
* Apply the disabled modifier to the matching days.
* https://react-day-picker.js.org/api/types/Matcher
* https://react-day-picker.js.org/api/type-aliases/Matcher
*/
disabled?: Matcher[];
/**
@@ -33,7 +33,7 @@ export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
*/
dropdownCaption?: boolean;
/**
* Apply the disabled modifier to the matching months. Uses a subset of React Day Picker Matcher type. https://react-day-picker.js.org/api/types/Matcher
* Apply the disabled modifier to the matching months. Uses a subset of React Day Picker Matcher type. https://react-day-picker.js.org/api/type-aliases/Matcher
*/
disabled?: Matcher[];
/**
@@ -10,7 +10,10 @@ import { ComboboxProps } from "./types";
export const Combobox = forwardRef<
HTMLInputElement,
Omit<ComboboxProps, "onChange" | "options" | "size" | "onClear" | "value">
Omit<
ComboboxProps,
"onChange" | "options" | "size" | "onClear" | "value" | "disabled"
>
>((props, ref) => {
const { className, hideLabel = false, description, label, ...rest } = props;
@@ -35,6 +35,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
allowNewValues = false,
children,
defaultValue,
disabled,
error,
errorId,
filteredOptions: externalFilteredOptions,
@@ -60,6 +61,8 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
<InputContextProvider
value={{
defaultValue,
description: rest.description,
disabled,
error,
errorId,
id,
@@ -4,11 +4,18 @@ const normalizeText = (text: string): string =>
typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
const isPartOfText = (value: string, text: string) =>
normalizeText(text).startsWith(normalizeText(value ?? ""));
normalizeText(text).includes(normalizeText(value ?? ""));
const getMatchingValuesFromList = (value: string, list: ComboboxOption[]) =>
list.filter((listItem) => isPartOfText(value, listItem.label));
const getFirstValueStartingWith = (text: string, list: ComboboxOption[]) => {
const normalizedText = normalizeText(text);
return list.find((listItem) =>
normalizeText(listItem.label).startsWith(normalizedText),
);
};
const getFilteredOptionsId = (comboboxId: string) =>
`${comboboxId}-filtered-options`;
@@ -37,4 +44,5 @@ export default {
getIsLoadingId,
getNoHitsId,
getMaxSelectedOptionsId,
getFirstValueStartingWith,
};
@@ -106,16 +106,19 @@ const FilteredOptionsProvider = ({
}, [allowNewValues, customOptions, id, options, value]);
useClientLayoutEffect(() => {
const autoCompleteCandidate =
filteredOptionsUtils.getFirstValueStartingWith(
searchTerm,
filteredOptions,
)?.label;
if (
shouldAutocomplete &&
filteredOptionsUtils.normalizeText(searchTerm) !== "" &&
(previousSearchTerm?.length || 0) < searchTerm.length &&
filteredOptions.length > 0
autoCompleteCandidate &&
(previousSearchTerm?.length || 0) < searchTerm.length
) {
setValue(
`${searchTerm}${filteredOptions[0].label.substring(searchTerm.length)}`,
`${searchTerm}${autoCompleteCandidate.substring(searchTerm.length)}`,
);
setSearchTerm(searchTerm);
}
}, [
filteredOptions,
@@ -1,11 +1,4 @@
import React, {
ChangeEvent,
ChangeEventHandler,
useCallback,
useMemo,
useRef,
useState,
} from "react";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { createContext } from "../../../util/create-context";
import { useClientLayoutEffect } from "../../../util/hooks";
import { FormFieldType, useFormField } from "../../useFormField";
@@ -13,12 +6,12 @@ import { ComboboxProps } from "../types";
interface InputContextValue extends FormFieldType {
clearInput: NonNullable<ComboboxProps["onClear"]>;
error?: string;
error?: ComboboxProps["error"];
focusInput: () => void;
inputRef: React.RefObject<HTMLInputElement>;
value: string;
setValue: (text: string) => void;
onChange: ChangeEventHandler<HTMLInputElement>;
onChange: (newValue: string) => void;
searchTerm: string;
setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
shouldAutocomplete?: boolean;
@@ -31,7 +24,24 @@ const [InputContextProvider, useInputContext] =
errorMessage: "useInputContext must be used within an InputContextProvider",
});
const InputProvider = ({ children, value: props }) => {
interface Props {
children: React.ReactNode;
value: {
defaultValue: ComboboxProps["defaultValue"];
description: ComboboxProps["description"];
disabled: ComboboxProps["disabled"];
error: ComboboxProps["error"];
errorId: ComboboxProps["errorId"];
id: ComboboxProps["id"];
value: ComboboxProps["value"];
onChange: ComboboxProps["onChange"];
onClear: ComboboxProps["onClear"];
shouldAutocomplete: ComboboxProps["shouldAutocomplete"];
size: ComboboxProps["size"];
};
}
const InputProvider = ({ children, value: props }: Props) => {
const {
defaultValue = "",
description,
@@ -68,30 +78,22 @@ const InputProvider = ({ children, value: props }) => {
const [searchTerm, setSearchTerm] = useState(value);
const onChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const newValue = event.currentTarget.value;
(newValue: string) => {
externalValue ?? setInternalValue(newValue);
externalOnChange?.(event);
setSearchTerm(newValue);
externalOnChange?.(newValue);
},
[externalValue, externalOnChange],
);
const setValue = useCallback(
(text) => {
setInternalValue(text);
},
[setInternalValue],
);
const clearInput = useCallback(
(event: React.PointerEvent | React.KeyboardEvent | React.MouseEvent) => {
onClear?.(event);
externalOnChange?.(null, "");
setValue("");
externalOnChange?.("");
setInternalValue("");
setSearchTerm("");
},
[externalOnChange, onClear, setValue],
[externalOnChange, onClear, setInternalValue],
);
const focusInput = useCallback(() => {
@@ -111,7 +113,7 @@ const InputProvider = ({ children, value: props }) => {
focusInput,
inputRef,
value,
setValue,
setValue: setInternalValue,
onChange,
searchTerm,
setSearchTerm,
@@ -1,18 +1,19 @@
import cl from "clsx";
import React, {
ChangeEvent,
InputHTMLAttributes,
forwardRef,
useCallback,
useRef,
} from "react";
import { omit } from "../../../util";
import { useMergeRefs } from "../../../util/hooks";
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util";
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { useInputContext } from "./Input.context";
interface InputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "disabled"> {
ref: React.Ref<HTMLInputElement>;
inputClassName?: string;
value?: string;
@@ -20,7 +21,17 @@ interface InputProps
const Input = forwardRef<HTMLInputElement, InputProps>(
({ inputClassName, ...rest }, ref) => {
const { clearInput, inputProps, onChange, size, value } = useInputContext();
const internalRef = useRef<HTMLInputElement>(null);
const mergedRefs = useMergeRefs(ref, internalRef);
const {
clearInput,
inputProps,
onChange,
size,
value,
searchTerm,
setValue,
} = useInputContext();
const {
selectedOptions,
removeSelectedOption,
@@ -56,7 +67,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
if (!isMultiSelect && !isTextInSelectedOptions(currentOption.label)) {
toggleIsListOpen(false);
}
} else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
} else if (isTextInSelectedOptions(value)) {
event.preventDefault();
// Trying to set the same value that is already set, so just clearing the input
clearInput(event);
@@ -67,13 +78,13 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
allowNewValues && isValueNew
? { label: value, value }
: filteredOptions[0];
if (!selectedValue) {
return;
}
toggleOption(selectedValue, event);
if (
!isMultiSelect &&
!isTextInSelectedOptions(
filteredOptions[0].label || selectedValue.label,
)
) {
if (!isMultiSelect && !isTextInSelectedOptions(selectedValue.label)) {
toggleIsListOpen(false);
}
}
@@ -96,10 +107,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault();
switch (e.key) {
case "Escape":
clearInput(e);
toggleIsListOpen(false);
break;
case "Enter":
case "Accept":
onEnter(e);
@@ -118,7 +125,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
};
const handleKeyDown = useCallback(
(e) => {
(e: React.KeyboardEvent<HTMLInputElement>) => {
setIsMouseLastUsedInputDevice(false);
if (e.key === "Backspace") {
if (value === "") {
@@ -132,17 +139,34 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
if (activeDecendantId || value) {
e.preventDefault();
}
} else if (e.key === "ArrowDown") {
// Check that cursor position is at the end of the input field,
// so we don't interfere with text editing
if (e.target.selectionStart === value?.length) {
e.preventDefault();
if (virtualFocus.activeElement === null || !isListOpen) {
toggleIsListOpen(true);
}
virtualFocus.moveFocusDown();
} else if (e.key === "Escape") {
if (isListOpen || value) {
e.preventDefault(); // Prevents closing an encasing Modal, as Combobox reacts on keyup.
clearInput(e);
toggleIsListOpen(false);
}
} else if (["ArrowLeft", "ArrowRight"].includes(e.key)) {
/**
* In case user has an active selection and 'completes' the selection with ArrowLeft or ArrowRight
* we need to make sure to update the filter.
*/
if (value !== "" && value !== searchTerm) {
onChange(value);
}
} else if (e.key === "ArrowDown") {
// Reset the value to the search term to cancel autocomplete
// if the user moves focus down to the FilteredOptions
if (value !== searchTerm) {
setValue(searchTerm);
}
if (virtualFocus.activeElement === null || !isListOpen) {
toggleIsListOpen(true);
}
virtualFocus.moveFocusDown();
} else if (e.key === "ArrowUp") {
if (value !== "" && value !== searchTerm) {
onChange(value);
}
// Check that the FilteredOptions list is open and has virtual focus.
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
if (isListOpen && activeDecendantId) {
@@ -161,13 +185,17 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
isListOpen,
activeDecendantId,
setIsMouseLastUsedInputDevice,
clearInput,
toggleIsListOpen,
onChange,
virtualFocus,
setValue,
searchTerm,
],
);
const onChangeHandler = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
(event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
if (newValue && newValue !== "") {
toggleIsListOpen(true);
@@ -175,7 +203,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
toggleIsListOpen(false);
}
virtualFocus.moveFocusToTop();
onChange(event);
onChange(newValue);
},
[filteredOptions.length, virtualFocus, onChange, toggleIsListOpen],
);
@@ -184,10 +212,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
<input
{...rest}
{...omit(inputProps, ["aria-invalid"])}
ref={ref}
ref={mergedRefs}
value={value}
onBlur={() => virtualFocus.moveFocusToTop()}
onChange={onChangeHandler}
onClick={() => value !== searchTerm && onChange(value)}
onInput={onChangeHandler}
type="text"
role="combobox"
onKeyUp={handleKeyUp}
@@ -24,6 +24,7 @@ export const InputController = forwardRef<
| "size"
| "onClear"
| "value"
| "disabled"
>
>((props, ref) => {
const {
@@ -8,6 +8,7 @@ import { UNSAFE_Combobox } from "../index";
const options = [
"banana",
"apple",
"apple pie",
"tangerine",
"pear",
"grape",
@@ -71,86 +72,193 @@ describe("Render combobox", () => {
});
});
test("Should show loading icon when loading (used for async search)", async () => {
render(<App options={[]} isListOpen isLoading />);
describe("Combobox state-handling", () => {
test("Should show loading icon when loading (used for async search)", async () => {
render(<App options={[]} isListOpen isLoading />);
expect(await screen.findByText("Søker...")).toBeInTheDocument();
});
});
expect(await screen.findByText("Søker...")).toBeInTheDocument();
});
describe("Combobox state-handling", () => {
test("Should not select previous focused element when closes", async () => {
render(<App options={options} />);
test("Should not select previous focused element when closes", async () => {
render(<App options={options} />);
await act(async () => {
await userEvent.click(
screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
await act(async () => {
await userEvent.click(
screen.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
}),
);
});
await act(async () => {
await userEvent.type(
screen.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
}),
"ban",
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowUp}");
await userEvent.keyboard("{Enter}");
});
expect(screen.queryByRole("button", { name: "banana slett" })).toBeNull();
});
test("Should reset list when resetting input (ESC)", async () => {
render(<App options={options} />);
await act(async () => {
await userEvent.click(
screen.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
}),
);
});
await act(async () => {
await userEvent.type(
screen.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
}),
"apple",
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Escape}");
await userEvent.keyboard("{ArrowDown}");
});
expect(
await screen.findByRole("option", { name: "banana" }),
).toBeInTheDocument();
});
test("Should handle complex options with label and value", async () => {
const onToggleSelected = vi.fn();
render(
<App
options={[
{ label: "Hjelpemidler [HJE]", value: "HJE" },
{ label: "Oppfølging [OPP]", value: "OPP" },
{ label: "Sykepenger [SYK]", value: "SYK" },
{ label: "Sykemelding [SYM]", value: "SYM" },
]}
onToggleSelected={onToggleSelected}
/>,
);
});
await act(async () => {
await userEvent.type(
screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
"ban",
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowUp}");
await userEvent.keyboard("{Enter}");
});
expect(screen.queryByRole("button", { name: "banana slett" })).toBeNull();
});
test("Should reset list when resetting input (ESC)", async () => {
render(<App options={options} />);
await act(async () => {
await userEvent.click(
screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
);
});
await act(async () => {
await userEvent.type(
screen.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
"apple",
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Escape}");
await userEvent.keyboard("{ArrowDown}");
});
expect(
await screen.findByRole("option", { name: "banana" }),
).toBeInTheDocument();
});
test("Should handle complex options with label and value", async () => {
const onToggleSelected = vi.fn();
render(
<App
options={[
{ label: "Hjelpemidler [HJE]", value: "HJE" },
{ label: "Oppfølging [OPP]", value: "OPP" },
{ label: "Sykepenger [SYK]", value: "SYK" },
{ label: "Sykemelding [SYM]", value: "SYM" },
]}
onToggleSelected={onToggleSelected}
/>,
);
expect(screen.getByRole("combobox")).toBeInTheDocument();
const bananaOption = screen.getByRole("option", {
name: "Hjelpemidler [HJE]",
selected: false,
});
await act(async () => {
await userEvent.click(bananaOption);
});
expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false);
expect(
screen.getByRole("option", {
expect(screen.getByRole("combobox")).toBeInTheDocument();
const bananaOption = screen.getByRole("option", {
name: "Hjelpemidler [HJE]",
selected: true,
}),
).toBeInTheDocument();
selected: false,
});
await act(async () => {
await userEvent.click(bananaOption);
});
expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false);
expect(
screen.getByRole("option", {
name: "Hjelpemidler [HJE]",
selected: true,
}),
).toBeInTheDocument();
});
test("should trigger onChange for every character typed or removed", async () => {
const onChange = vi.fn();
const onToggleSelected = vi.fn();
render(
<App
options={["Apple", "Orange", "Banana", "Lemon"]}
onChange={onChange}
onToggleSelected={onToggleSelected}
shouldAutocomplete
/>,
);
const combobox = screen.getByRole("combobox");
expect(combobox).toBeInTheDocument();
await act(async () => {
await userEvent.click(combobox);
await userEvent.type(combobox, "Lemon");
});
expect(onChange).toHaveBeenNthCalledWith(1, "L");
expect(onChange).toHaveBeenNthCalledWith(2, "Le");
expect(onChange).toHaveBeenNthCalledWith(3, "Lem");
expect(onChange).toHaveBeenNthCalledWith(4, "Lemo");
expect(onChange).toHaveBeenNthCalledWith(5, "Lemon");
});
test("should trigger onChange while typing and on accepting autocomplete suggestions", async () => {
const onChange = vi.fn();
const onToggleSelected = vi.fn();
render(
<App
options={[
"Hjelpemidler [HJE]",
"Oppfølging [OPP]",
"Sykepenger [SYK]",
"Sykemelding [SYM]",
]}
onChange={onChange}
onToggleSelected={onToggleSelected}
shouldAutocomplete
/>,
);
const combobox = screen.getByRole("combobox");
expect(combobox).toBeInTheDocument();
await act(async () => {
await userEvent.click(combobox);
await userEvent.type(combobox, "Syke");
await userEvent.keyboard("{ArrowRight}");
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Enter}");
});
expect(onChange).toHaveBeenNthCalledWith(1, "S");
expect(onChange).toHaveBeenNthCalledWith(2, "Sy");
expect(onChange).toHaveBeenNthCalledWith(3, "Syk");
expect(onChange).toHaveBeenNthCalledWith(4, "Syke");
expect(onChange).toHaveBeenNthCalledWith(5, "Sykepenger [SYK]");
expect(onChange).toHaveBeenCalledWith("");
expect(onToggleSelected).toHaveBeenCalledOnce();
expect(onToggleSelected).toHaveBeenCalledWith(
"Sykepenger [SYK]",
true,
false,
);
});
});
describe("search", () => {
test("should find matched anywhere in the label", async () => {
render(<App options={options} />);
const combobox = screen.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
});
await act(async () => {
await userEvent.click(combobox);
await userEvent.type(combobox, "p");
});
const searchHits = [
"apple",
"apple pie",
"pear",
"grape",
"passion fruit",
"pineapple",
"grape fruit",
];
searchHits.forEach((label) => {
expect(screen.getByRole("option", { name: label })).toBeInTheDocument();
});
screen.getAllByRole("option").forEach((option) => {
expect(
option.textContent && searchHits.includes(option.textContent),
).toBe(true);
});
});
});
});
@@ -1,17 +1,21 @@
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, within } from "@storybook/test";
import React, { useMemo, useRef, useState } from "react";
import { Meta, StoryFn } from "@storybook/react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { Button } from "../../button";
import { Chips } from "../../chips";
import { VStack } from "../../layout/stack";
import { Modal } from "../../modal";
import { TextField } from "../textfield";
import { ComboboxProps, UNSAFE_Combobox } from "./index";
import { UNSAFE_Combobox } from "./index";
export default {
title: "ds-react/Combobox",
component: UNSAFE_Combobox,
decorators: [(story) => <div style={{ width: "300px" }}>{story()}</div>],
parameters: {
chromatic: { disable: true },
},
} satisfies Meta<typeof UNSAFE_Combobox>;
type StoryObject = StoryObj<typeof UNSAFE_Combobox>;
type StoryFunction = StoryFn<typeof UNSAFE_Combobox>;
const options = [
@@ -41,8 +45,15 @@ Default.args = {
isLoading: false,
isMultiSelect: false,
allowNewValues: false,
onChange: console.log,
};
Default.argTypes = {
description: {
control: { type: "text" },
},
disabled: {
control: { type: "boolean" },
},
isListOpen: {
control: { type: "boolean" },
},
@@ -56,31 +67,36 @@ Default.argTypes = {
},
};
export const MultiSelect: StoryFunction = (props) => {
export const MultiSelect: StoryFn = () => {
const [selectedOptions, setSelectedOptions] = useState<string[]>([
"pear",
"kiwi",
]);
return (
<UNSAFE_Combobox
id="combobox-with-multiselect"
label="Komboboks - velg flere"
options={props.options}
isMultiSelect={props.isMultiSelect}
size={props.size}
options={options}
isMultiSelect={true}
selectedOptions={selectedOptions}
onToggleSelected={(option, isSelected) =>
isSelected
? setSelectedOptions([...selectedOptions, option])
: setSelectedOptions(selectedOptions.filter((o) => o !== option))
}
/>
);
};
MultiSelect.args = {
options,
isMultiSelect: true,
size: "medium",
};
export const MultiSelectWithComplexOptions: StoryFunction = (props) => {
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
export const MultiSelectWithComplexOptions: StoryFn = () => {
const [selectedOptions, setSelectedOptions] = useState<string[]>([
"HJE",
"OPP",
]);
return (
<>
<UNSAFE_Combobox
{...props}
options={props.options.map((option) => ({
options={complexOptions.map((option) => ({
...option,
label: `${option.label} [${option.value}]`,
}))}
@@ -93,74 +109,81 @@ export const MultiSelectWithComplexOptions: StoryFunction = (props) => {
: setSelectedOptions(selectedOptions.filter((o) => o !== value))
}
selectedOptions={selectedOptions}
isMultiSelect
/>
</>
);
};
MultiSelectWithComplexOptions.args = {
options: [
{ label: "Hjelpemidler", value: "HJE" },
{ label: "Oppfølging", value: "OPP" },
{ label: "Sykepenger", value: "SYK" },
{ label: "Sykemelding", value: "SYM" },
{ label: "Foreldre- og svangerskapspenger", value: "FOR" },
{ label: "Arbeidsavklaringspenger", value: "AAP" },
{ label: "Uføretrygd", value: "UFO" },
{ label: "Pensjon", value: "PEN" },
{ label: "Barnetrygd", value: "BAR" },
{ label: "Kontantstøtte", value: "KON" },
{ label: "Bostøtte", value: "BOS" },
{ label: "Barnebidrag", value: "BBI" },
{ label: "Bidragsforskudd", value: "BIF" },
{ label: "Grunn- og hjelpestønad", value: "GRU" },
],
isMultiSelect: true,
size: "medium",
};
const complexOptions = [
{ label: "Hjelpemidler", value: "HJE" },
{ label: "Oppfølging", value: "OPP" },
{ label: "Sykepenger", value: "SYK" },
{ label: "Sykemelding", value: "SYM" },
{ label: "Foreldre- og svangerskapspenger", value: "FOR" },
{ label: "Arbeidsavklaringspenger", value: "AAP" },
{ label: "Uføretrygd", value: "UFO" },
{ label: "Pensjon", value: "PEN" },
{ label: "Barnetrygd", value: "BAR" },
{ label: "Kontantstøtte", value: "KON" },
{ label: "Bostøtte", value: "BOS" },
{ label: "Barnebidrag", value: "BBI" },
{ label: "Bidragsforskudd", value: "BIF" },
{ label: "Grunn- og hjelpestønad", value: "GRU" },
];
export const WithAddNewOptions: StoryFunction = (props) => {
export const WithAddNewOptions: StoryFn = ({ open }: { open?: boolean }) => {
const [value, setValue] = useState<string | undefined>("hello");
const comboboxRef = useRef<HTMLInputElement>(null);
return (
<UNSAFE_Combobox
id="combobox-with-add-new-options"
label="Komboboks med mulighet for å legge til nye verdier"
options={props.options}
allowNewValues={props.allowNewValues}
shouldAutocomplete={props.shouldAutocomplete}
options={options}
allowNewValues={true}
shouldAutocomplete={true}
value={value}
onChange={(newValue) => setValue(newValue)}
isListOpen={open ?? (comboboxRef.current ? true : undefined)}
ref={comboboxRef}
/>
);
};
WithAddNewOptions.args = {
options,
allowNewValues: true,
shouldAutocomplete: true,
};
export const MultiSelectWithAddNewOptions: StoryFunction = (props) => {
export const MultiSelectWithAddNewOptions: StoryFn = ({
open,
}: {
open?: boolean;
}) => {
const [value, setValue] = useState<string | undefined>("world");
const [selectedOptions, setSelectedOptions] = useState<string[]>(["hello"]);
const comboboxRef = useRef<HTMLInputElement>(null);
return (
<UNSAFE_Combobox
id="combobox-with-multiselect-and-add-new-options"
isMultiSelect={props.isMultiSelect}
isMultiSelect={true}
label="Multiselect komboboks med mulighet for å legge til nye verdier"
options={props.options}
allowNewValues={props.allowNewValues}
options={options}
allowNewValues={true}
value={value}
selectedOptions={selectedOptions}
onChange={(newValue) => setValue(newValue)}
onToggleSelected={(option, isSelected) =>
isSelected
? setSelectedOptions([...selectedOptions, option])
: setSelectedOptions(selectedOptions.filter((o) => o !== option))
}
isListOpen={open ?? (comboboxRef.current ? true : undefined)}
ref={comboboxRef}
/>
);
};
MultiSelectWithAddNewOptions.args = {
allowNewValues: true,
isMultiSelect: true,
options,
shouldAutocomplete: false,
};
export const MultiSelectWithExternalChips: StoryFn<{
controlled: boolean;
options: ComboboxProps["options"];
}> = (props) => {
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
export const MultiSelectWithExternalChips: StoryFn = () => {
const [selectedOptions, setSelectedOptions] = useState<string[]>([
"kiwi",
"pear",
]);
const [value, setValue] = useState("");
const toggleSelected = (option: string) =>
@@ -184,16 +207,12 @@ export const MultiSelectWithExternalChips: StoryFn<{
)}
<UNSAFE_Combobox
id="combobox-with-external-chips"
options={props.options}
options={options}
selectedOptions={selectedOptions}
onToggleSelected={(option) => toggleSelected(option)}
isMultiSelect
value={props.controlled ? value : undefined}
onChange={(event) =>
props.controlled
? setValue(event?.currentTarget.value || "")
: undefined
}
value={value}
onChange={(newValue) => setValue(newValue || "")}
label="Komboboks"
size="medium"
shouldShowSelectedOptions={false}
@@ -202,58 +221,40 @@ export const MultiSelectWithExternalChips: StoryFn<{
);
};
MultiSelectWithExternalChips.args = {
controlled: false,
options,
};
export const Loading: StoryFunction = (props) => (
export const Loading: StoryFn = () => (
<UNSAFE_Combobox
id="combobox-with-loading-indicator"
label="Komboboks (laster)"
options={[]}
selectedOptions={[]}
isListOpen={props.isListOpen}
isLoading={props.isLoading}
isListOpen={true}
isLoading={true}
/>
);
Loading.args = {
isLoading: true,
isListOpen: true,
};
export const ComboboxWithNoHits: StoryFunction = (props) => {
const [value, setValue] = useState(props.value);
export const ComboboxWithNoHits: StoryFn = () => {
const [value, setValue] = useState<string | undefined>("Orange");
return (
<UNSAFE_Combobox
id="combobox-with-no-hits"
label="Komboboks (uten søketreff)"
options={props.options}
options={options}
value={value}
onChange={(event) => setValue(event?.currentTarget.value)}
onChange={(newValue) => setValue(newValue)}
isListOpen={true}
/>
);
};
ComboboxWithNoHits.args = {
options,
value: "Orange",
};
export const Controlled: StoryFn<{
value: string;
options: string[];
initialSelectedOptions: string[];
}> = (props) => {
const [value, setValue] = useState(props.value);
const [selectedOptions, setSelectedOptions] = useState(
props.initialSelectedOptions,
);
export const Controlled: StoryFn = () => {
const [value, setValue] = useState<string>("apple");
const [selectedOptions, setSelectedOptions] = useState([
"passion fruit",
"grape fruit",
]);
const filteredOptions = useMemo(
() => props.options.filter((option) => option.includes(value)),
[props.options, value],
() => options.filter((option) => option.includes(value)),
[value],
);
const onToggleSelected = (option: string, isSelected: boolean) => {
@@ -277,8 +278,8 @@ export const Controlled: StoryFn<{
id="combobox-controlled"
filteredOptions={filteredOptions}
isMultiSelect
options={props.options}
onChange={(event) => setValue(event?.target.value || "")}
options={options}
onChange={(newValue) => setValue(newValue || "")}
onToggleSelected={onToggleSelected}
selectedOptions={selectedOptions}
value={value}
@@ -287,47 +288,82 @@ export const Controlled: StoryFn<{
);
};
Controlled.args = {
value: "apple",
options,
initialSelectedOptions: ["passion fruit", "grape fruit"],
export const ComboboxSizes = () => {
const [multiSelectedOptions, setMultiSelectedOptions] = useState<string[]>([
"pear",
"kiwi",
]);
const [singleSelectedOption, setSingleSelectedOption] = useState<string[]>([
"apple",
]);
return (
<>
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium single-select"
options={options}
selectedOptions={singleSelectedOption}
onToggleSelected={(option, isSelected) =>
isSelected
? setSingleSelectedOption([option])
: setSingleSelectedOption(
singleSelectedOption.filter((o) => o !== option),
)
}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small single-select"
options={options}
size="small"
selectedOptions={singleSelectedOption}
onToggleSelected={(option, isSelected) =>
isSelected
? setSingleSelectedOption([option])
: setSingleSelectedOption(
singleSelectedOption.filter((o) => o !== option),
)
}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium multiselect"
options={options}
isMultiSelect
allowNewValues
selectedOptions={multiSelectedOptions}
onToggleSelected={(option, isSelected) =>
isSelected
? setMultiSelectedOptions([...multiSelectedOptions, option])
: setMultiSelectedOptions(
multiSelectedOptions.filter((o) => o !== option),
)
}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small multiselect"
options={options}
isMultiSelect
size="small"
allowNewValues
selectedOptions={multiSelectedOptions}
onToggleSelected={(option, isSelected) =>
isSelected
? setMultiSelectedOptions([...multiSelectedOptions, option])
: setMultiSelectedOptions(
multiSelectedOptions.filter((o) => o !== option),
)
}
/>
</>
);
};
export const ComboboxSizes = () => (
<>
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium single-select"
options={options}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small single-select"
options={options}
size="small"
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium multiselect"
options={options}
isMultiSelect
allowNewValues
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small multiselect"
options={options}
isMultiSelect
size="small"
allowNewValues
/>
</>
);
export const MaxSelectedOptions: StoryFunction = () => {
export const MaxSelectedOptions: StoryFn = ({ open }: { open?: boolean }) => {
const [value, setValue] = useState<string | undefined>("");
const [selectedOptions, setSelectedOptions] = useState([
options[0],
@@ -348,15 +384,15 @@ export const MaxSelectedOptions: StoryFunction = () => {
}
isMultiSelect
allowNewValues
isListOpen={comboboxRef.current ? undefined : true}
isListOpen={open ?? (comboboxRef.current ? undefined : true)}
value={value}
onChange={(event) => setValue(event?.target.value)}
onChange={(newValue) => setValue(newValue)}
ref={comboboxRef}
/>
);
};
export const WithError: StoryFunction = (props) => {
export const WithError: StoryFn = () => {
const [hasSelectedValue, setHasSelectedValue] = useState(false);
const [isLoading, setIsLoading] = useState(false);
return (
@@ -364,7 +400,7 @@ export const WithError: StoryFunction = (props) => {
filteredOptions={isLoading ? [] : undefined}
options={options}
label="Hva er dine favorittfrukter?"
error={!hasSelectedValue && props.error}
error={!hasSelectedValue && "Du må velge en favorittfrukt."}
isLoading={isLoading}
onChange={() => {
setIsLoading(true);
@@ -374,350 +410,87 @@ export const WithError: StoryFunction = (props) => {
/>
);
};
WithError.args = {
error: "Du må velge en favorittfrukt.",
export const InModal: StoryFn = () => {
const modalRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
modalRef.current?.showModal();
}, []);
return (
<>
<Button onClick={() => modalRef.current?.showModal()}>Åpne modal</Button>
<Modal
ref={modalRef}
header={{ heading: "Overskrift" }}
width="medium"
style={{ height: "auto" }}
>
<Modal.Body style={{ height: "100% " }}>
<p>
Modalen skal ikke lukke seg om man trykker Escape mens virtuelt
fokus er i Combobox sin nedtrekksliste eller om inputfeltet
inneholder tekst.
</p>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
/>
</Modal.Body>
</Modal>
</>
);
};
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export const CancelInputTest: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(1000);
userEvent.keyboard("{Escape}");
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(500);
const banana = canvas.getByText("banana");
userEvent.click(banana);
},
export const Disabled: StoryFn = () => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
disabled
/>
);
};
export const RemoveSelectedMultiSelectTest: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
userEvent.click(input);
await userEvent.type(input, "banana", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
const appleSlett = canvas.getByLabelText("apple slett");
appleSlett.focus();
await sleep(250);
userEvent.click(appleSlett);
await sleep(250);
const appleOption = canvas.getByRole("option", {
name: "apple",
selected: false,
});
expect(appleOption).toBeVisible();
userEvent.keyboard("{Escape}");
await sleep(250);
expect(appleOption).not.toBeVisible();
const bananaSlett = canvas.getByLabelText("banana slett");
expect(bananaSlett).toBeInTheDocument();
const appleSlettAgain = canvas.queryByLabelText("apple slett");
expect(appleSlettAgain).not.toBeInTheDocument();
},
export const Chromatic: StoryFn = () => {
const H2 = (props: { children: string; style?: React.CSSProperties }) => (
<h2 style={{ marginBottom: "-0.25rem", ...props.style }}>
{props.children}
</h2>
);
return (
<VStack gap="2">
<H2>MultiSelect</H2>
<MultiSelect />
<H2>ComboboxWithNoHits</H2>
<ComboboxWithNoHits />
<H2 style={{ marginTop: "5rem" }}>MultiSelectWithComplexOptions</H2>
<MultiSelectWithComplexOptions />
<H2>WithAddNewOptions</H2>
<WithAddNewOptions open />
<H2 style={{ marginTop: "5rem" }}>MultiSelectWithAddNewOptions</H2>
<MultiSelectWithAddNewOptions open />
<H2 style={{ marginTop: "5rem" }}>MultiSelectWithExternalChips</H2>
<MultiSelectWithExternalChips />
<H2>Loading</H2>
<Loading />
<H2 style={{ marginTop: "5rem" }}>Controlled Input Value</H2>
<Controlled />
<H2>ComboboxSizes</H2>
<ComboboxSizes />
<H2>MaxSelectedOptions</H2>
<MaxSelectedOptions open />
<H2 style={{ marginTop: "20rem" }}>WithError</H2>
<WithError />
<H2>Disabled</H2>
<Disabled />
</VStack>
);
};
export const AllowNewValuesTest: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
allowNewValues
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
expect(invalidSelect).not.toBeInTheDocument();
},
};
export const DisallowNewValuesTest: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
expect(invalidSelect).not.toBeInTheDocument();
},
};
export const TestThatCallbacksOnlyFireWhenExpected: StoryObj<{
onChange: ReturnType<typeof fn>;
onClear: ReturnType<typeof fn>;
onToggleSelected: ReturnType<typeof fn>;
}> = {
args: {
onChange: fn(),
onClear: fn(),
onToggleSelected: fn(),
},
render: (props) => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
{...props}
/>
);
},
play: async ({ canvasElement, args }) => {
args.onToggleSelected.mockClear();
args.onClear.mockClear();
args.onChange.mockClear();
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
const searchWord = "tangerine";
userEvent.click(input);
await userEvent.type(input, searchWord, { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
expect(args.onClear.mock.calls).toHaveLength(1);
expect(args.onToggleSelected.mock.calls).toHaveLength(1);
expect(args.onChange.mock.calls).toHaveLength(searchWord.length + 1);
},
};
export const TestCasingWhenAutoCompleting = {
args: {
onChange: fn(),
onClear: fn(),
onToggleSelected: fn(),
},
render: (props) => {
return (
<UNSAFE_Combobox
options={["Camel Case", "lowercase", "UPPERCASE"]}
label="Liker du best store eller små bokstaver?"
shouldAutocomplete
allowNewValues
{...props}
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByRole<HTMLInputElement>("combobox");
// With exisiting option
userEvent.click(input);
await userEvent.type(input, "cAmEl CaSe", { delay: 250 });
await sleep(250);
expect(input.value).toBe("cAmEl CaSe");
await userEvent.type(input, "{Enter}");
await sleep(250);
const chips = canvas.getAllByRole("list")[0];
const selectedUpperCaseChip = within(chips).getAllByRole("listitem")[0];
expect(selectedUpperCaseChip).toHaveTextContent("Camel Case"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
// With custom option
userEvent.click(input);
await userEvent.type(input, "cAmEl{Backspace}", { delay: 250 });
await sleep(250);
expect(input.value).toBe("cAmEl");
await userEvent.type(input, "{Enter}");
await sleep(250);
const selectedNewValueChip = within(chips).getAllByRole("listitem")[0];
expect(selectedNewValueChip).toHaveTextContent("cAmEl"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
},
};
export const TestHoverAndFocusSwitching: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await sleep(500);
const getInput = () =>
canvas.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
});
userEvent.click(getInput());
expect(getInput().getAttribute("aria-expanded")).toEqual("false");
expect(getInput().getAttribute("aria-activedescendant")).toBeNull();
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
const bananaOption = canvas.getByRole("option", { name: "banana" });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
bananaOption.getAttribute("id"),
);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
const appleOption = canvas.getByRole("option", { name: "apple" });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
appleOption.getAttribute("id"),
);
userEvent.hover(bananaOption);
await sleep(250);
expect(getInput().getAttribute("aria-activedescendant")).toBe(
bananaOption.getAttribute("id"),
);
},
};
export const TestEnterNotSubmittingForm: StoryObj<{
onSubmit: ReturnType<typeof fn>;
}> = {
args: {
onSubmit: fn(),
},
render: ({ onSubmit }) => {
return (
<form action="https://www.aksel.nav.no" method="get" onSubmit={onSubmit}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
allowNewValues
/>
</form>
);
},
play: async ({ canvasElement, args }) => {
args.onSubmit.mockClear();
const canvas = within(canvasElement);
const waitTime = 0; // Change for debugging
await sleep(waitTime);
const getInput = () =>
canvas.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
});
const getOption = (name: string, selected: boolean) =>
canvas.getByRole("option", { name, selected });
await userEvent.click(getInput(), { delay: waitTime });
await userEvent.keyboard("{ArrowDown}", { delay: waitTime });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
getOption("banana", false).getAttribute("id"),
);
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
expect(getOption("banana", true)).toBeVisible();
await userEvent.keyboard("{Shift>}{Tab}", { delay: waitTime });
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("test"); // Type option that does not exist
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("{ArrowDown}", { delay: waitTime }); // Select "test" custom option
expect(
canvas.getByRole("option", { name: "test", selected: true }),
).toBeVisible();
await userEvent.keyboard("{Enter}", { delay: waitTime }); // De-select "test"
expect(
canvas.queryByRole("option", { name: "test", selected: false }),
).toBeNull();
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("{Escape}", { delay: waitTime }); // Clear input field
await userEvent.keyboard("{Enter}", { delay: waitTime }); // Enter on empty Input with closed list
expect(args.onSubmit).toHaveBeenCalledOnce();
Chromatic.parameters = {
chromatic: {
disable: false,
},
};
@@ -0,0 +1,404 @@
import { Meta, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, within } from "@storybook/test";
import React from "react";
import { UNSAFE_Combobox } from "./index";
export default {
title: "ds-react/Combobox/Tests",
component: UNSAFE_Combobox,
decorators: [(story) => <div style={{ width: "300px" }}>{story()}</div>],
parameters: {
chromatic: { disable: false },
},
} satisfies Meta<typeof UNSAFE_Combobox>;
type StoryObject = StoryObj<typeof UNSAFE_Combobox>;
const options = [
"banana",
"apple",
"apple pie",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export const CancelInput: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(1000);
userEvent.keyboard("{Escape}");
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(500);
const banana = canvas.getByText("banana");
userEvent.click(banana);
},
};
export const RemoveSelectedMultiSelect: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
userEvent.click(input);
await userEvent.type(input, "banana", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
const appleSlett = canvas.getByLabelText("apple slett");
appleSlett.focus();
await sleep(250);
userEvent.click(appleSlett);
await sleep(250);
const appleOption = canvas.getByRole("option", {
name: "apple",
selected: false,
});
expect(appleOption).toBeVisible();
userEvent.keyboard("{Escape}");
await sleep(250);
expect(appleOption).not.toBeVisible();
const bananaSlett = canvas.getByLabelText("banana slett");
expect(bananaSlett).toBeInTheDocument();
const appleSlettAgain = canvas.queryByLabelText("apple slett");
expect(appleSlettAgain).not.toBeInTheDocument();
},
};
export const AllowNewValuesMultiSelect: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox
isMultiSelect={true}
options={options}
label="Hva er dine favorittfrukter?"
allowNewValues
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
expect(invalidSelect).toBeInTheDocument();
},
};
export const AllowNewValuesSingleSelect: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
allowNewValues
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
expect(
canvas.getByRole("option", { name: "Legg til “aaa”" }),
).toBeVisible();
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
expect(invalidSelect).not.toBeInTheDocument();
},
};
export const DisallowNewValues: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
expect(invalidSelect).not.toBeInTheDocument();
},
};
export const CallbacksOnlyFireWhenExpected: StoryObj<{
onChange: ReturnType<typeof fn>;
onClear: ReturnType<typeof fn>;
onToggleSelected: ReturnType<typeof fn>;
}> = {
args: {
onChange: fn(),
onClear: fn(),
onToggleSelected: fn(),
},
render: (props) => {
return (
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
{...props}
/>
);
},
play: async ({ canvasElement, args }) => {
args.onToggleSelected.mockClear();
args.onClear.mockClear();
args.onChange.mockClear();
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
const searchWord = "tangerine";
userEvent.click(input);
await userEvent.type(input, searchWord, { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
expect(args.onClear.mock.calls).toHaveLength(1);
expect(args.onToggleSelected.mock.calls).toHaveLength(1);
expect(args.onChange.mock.calls).toHaveLength(searchWord.length + 1);
},
};
export const CorrectCasingWhenAutoCompleting = {
args: {
onChange: fn(),
onClear: fn(),
onToggleSelected: fn(),
},
render: (props) => {
return (
<UNSAFE_Combobox
options={["Camel Case", "lowercase", "UPPERCASE"]}
label="Liker du best store eller små bokstaver?"
shouldAutocomplete
allowNewValues
{...props}
/>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByRole<HTMLInputElement>("combobox");
// With exisiting option
userEvent.click(input);
await userEvent.type(input, "cAmEl CaSe", { delay: 250 });
await sleep(250);
expect(input.value).toBe("cAmEl CaSe");
await userEvent.type(input, "{Enter}");
await sleep(250);
const chips = canvas.getAllByRole("list")[0];
const selectedUpperCaseChip = within(chips).getAllByRole("listitem")[0];
expect(selectedUpperCaseChip).toHaveTextContent("Camel Case"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
// With custom option
userEvent.click(input);
await userEvent.type(input, "cAmEl{Backspace}", { delay: 250 });
await sleep(250);
expect(input.value).toBe("cAmEl");
await userEvent.type(input, "{Enter}");
await sleep(250);
const selectedNewValueChip = within(chips).getAllByRole("listitem")[0];
expect(selectedNewValueChip).toHaveTextContent("cAmEl"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
},
};
export const HoverAndFocusSwitching: StoryObject = {
render: () => {
return (
<UNSAFE_Combobox options={options} label="Hva er dine favorittfrukter?" />
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await sleep(500);
const getInput = () =>
canvas.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
});
userEvent.click(getInput());
expect(getInput().getAttribute("aria-expanded")).toEqual("false");
expect(getInput().getAttribute("aria-activedescendant")).toBeNull();
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
const bananaOption = canvas.getByRole("option", { name: "banana" });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
bananaOption.getAttribute("id"),
);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
const appleOption = canvas.getByRole("option", { name: "apple" });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
appleOption.getAttribute("id"),
);
userEvent.hover(bananaOption);
await sleep(250);
expect(getInput().getAttribute("aria-activedescendant")).toBe(
bananaOption.getAttribute("id"),
);
},
};
export const EnterNotSubmittingForm: StoryObj<{
onSubmit: ReturnType<typeof fn>;
}> = {
args: {
onSubmit: fn(),
},
render: ({ onSubmit }) => {
return (
<form action="https://www.aksel.nav.no" method="get" onSubmit={onSubmit}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
allowNewValues
/>
</form>
);
},
play: async ({ canvasElement, args }) => {
args.onSubmit.mockClear();
const canvas = within(canvasElement);
const waitTime = 0; // Change for debugging
await sleep(waitTime);
const getInput = () =>
canvas.getByRole("combobox", {
name: "Hva er dine favorittfrukter?",
});
const getOption = (name: string, selected: boolean) =>
canvas.getByRole("option", { name, selected });
await userEvent.click(getInput(), { delay: waitTime });
await userEvent.keyboard("{ArrowDown}", { delay: waitTime });
expect(getInput().getAttribute("aria-activedescendant")).toBe(
getOption("banana", false).getAttribute("id"),
);
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
expect(getOption("banana", true)).toBeVisible();
await userEvent.keyboard("{Shift>}{Tab}", { delay: waitTime });
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("test"); // Type option that does not exist
await userEvent.keyboard("{Enter}", { delay: waitTime });
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("{ArrowDown}", { delay: waitTime }); // Select "test" custom option
expect(
canvas.getByRole("option", { name: "test", selected: true }),
).toBeVisible();
await userEvent.keyboard("{Enter}", { delay: waitTime }); // De-select "test"
expect(
canvas.queryByRole("option", { name: "test", selected: false }),
).toBeNull();
expect(args.onSubmit).not.toHaveBeenCalled();
await userEvent.keyboard("{Escape}", { delay: waitTime }); // Clear input field
await userEvent.keyboard("{Enter}", { delay: waitTime }); // Enter on empty Input with closed list
expect(args.onSubmit).toHaveBeenCalledOnce();
},
};
+11 -7
View File
@@ -1,4 +1,4 @@
import React, { ChangeEvent, InputHTMLAttributes } from "react";
import React, { InputHTMLAttributes } from "react";
import { FormFieldProps } from "../useFormField";
/**
@@ -29,7 +29,10 @@ export type MaxSelected = {
export interface ComboboxProps
extends FormFieldProps,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "value"> {
Omit<
InputHTMLAttributes<HTMLInputElement>,
"size" | "onChange" | "value" | "defaultValue"
> {
/**
* Combobox label.
*/
@@ -89,12 +92,9 @@ export interface ComboboxProps
/**
* Callback function triggered whenever the value of the input field is triggered.
*
* @param event
* @param value The value after change
*/
onChange?: (
event: ChangeEvent<HTMLInputElement> | null,
value?: string,
) => void;
onChange?: (value: string) => void;
/**
* Callback function triggered whenever the input field is cleared.
*
@@ -156,4 +156,8 @@ export interface ComboboxProps
* This converts the input to a controlled input, so you have to use onChange to update the value.
*/
value?: string;
/**
* Initial value of the input field. Only works when the input is uncontrolled.
*/
defaultValue?: string;
}
@@ -10,8 +10,8 @@ export default {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
options: ["medium", "small"],
},
error: {
type: "string",
@@ -20,7 +20,7 @@ export default {
parameters: {
chromatic: { disable: true },
},
} as Meta;
} satisfies Meta<typeof ConfirmationPanel>;
type Story = StoryObj<typeof ConfirmationPanel>;
@@ -76,7 +76,7 @@ export const NoContent = () => {
);
};
export const Error = () => {
export const WithError = () => {
const [checked, setChecked] = useState(false);
return (
<div className="colgap">
@@ -117,8 +117,8 @@ export const Chromatic: Story = {
<NoContent />
</div>
<div>
<h2>Error</h2>
<Error />
<h2>With error</h2>
<WithError />
</div>
</div>
),
@@ -15,8 +15,8 @@ export default {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
options: ["medium", "small"],
},
},
parameters: {
@@ -11,8 +11,8 @@ export default {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
options: ["medium", "small"],
},
description: {
type: "string",
@@ -79,7 +79,7 @@ export const ErrorPropagation: Story = {
},
};
export const Error: Story = {
export const WithError: Story = {
args: {
legend: "Mollit eiusmod",
description:
@@ -129,14 +129,14 @@ export const Chromatic: Story = {
<Fieldset {...ErrorPropagation.args} />
</div>
<div>
<h2>Error</h2>
<h2>WithError</h2>
{/* @ts-expect-error Args are Partial here */}
<Fieldset {...Error.args} />
<Fieldset {...WithError.args} />
</div>
<div>
<h2>Error small</h2>
<h2>WithError small</h2>
{/* @ts-expect-error Args are Partial here */}
<Fieldset {...Error.args} size="small" />
<Fieldset {...WithError.args} size="small" />
</div>
<div>
<h2>Disabled</h2>
@@ -1,4 +1,4 @@
import { Meta, StoryObj } from "@storybook/react";
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import { fireEvent, within } from "@storybook/test";
import React from "react";
import { ImageIcon } from "@navikt/aksel-icons";
@@ -48,8 +48,8 @@ export const Default: StoryObj<typeof FileUpload.Dropzone> = {
parameters: { chromatic: { disable: true } },
};
export const States: StoryObj = {
render: () => (
export const States: StoryFn = () => {
return (
<div>
<h2>Disabled</h2>
<FileUpload.Dropzone
@@ -78,7 +78,7 @@ export const States: StoryObj = {
onSelect={onSelect}
/>
</div>
),
);
};
States.play = ({ canvasElement }) => {
const canvas = within(canvasElement);
@@ -86,8 +86,8 @@ States.play = ({ canvasElement }) => {
fireEvent.dragEnter(button);
};
export const Translation: StoryObj = {
render: () => (
export const Translation: StoryFn = () => {
return (
<div>
<h2>Single file</h2>
<FileUpload.Dropzone
@@ -119,5 +119,5 @@ export const Translation: StoryObj = {
disabled
/>
</div>
),
);
};
@@ -1,4 +1,4 @@
import { Meta, StoryObj } from "@storybook/react";
import { Meta, StoryFn } from "@storybook/react";
import React from "react";
import { FileItem, UNSAFE_FileUpload as FileUpload } from ".";
import { VStack } from "../../layout/stack";
@@ -28,8 +28,8 @@ const filePptx = new File(["abc"], "file.pptx");
const fileWebp = new File(["abc"], "file.webp");
const fileDocx = new File(["abc"], "file.docx");
export const Icons: StoryObj<typeof FileUpload.Item> = {
render: () => (
export const Icons: StoryFn = () => {
return (
<FileUpload>
<VStack gap="5" as="ul">
<FileUpload.Item file={fileTxt} as="li" />
@@ -44,11 +44,11 @@ export const Icons: StoryObj<typeof FileUpload.Item> = {
<FileUpload.Item file={filePptx} as="li" />
</VStack>
</FileUpload>
),
);
};
export const States: StoryObj<typeof FileUpload.Item> = {
render: () => (
export const States: StoryFn = () => {
return (
<div>
<h2>Error</h2>
<FileUpload.Item file={fileTxt} error="Plain error" />
@@ -116,11 +116,11 @@ export const States: StoryObj<typeof FileUpload.Item> = {
}}
/>
</div>
),
);
};
export const Download: StoryObj = {
render: () => (
export const Download: StoryFn = () => {
return (
<VStack gap="5">
<FileUpload.Item
file={{
@@ -144,5 +144,17 @@ export const Download: StoryObj = {
/>
<FileUpload.Item file={fileTxt} />
</VStack>
),
);
};
export const Description: StoryFn = () => {
return (
<VStack gap="5">
<FileUpload.Item file={fileTxt} description="Mottat 11.11.11" />
<FileUpload.Item
file={fileTxt}
description="Lorem ipsum dolor, sit amet consectetur adipisicing elit. Enim officiis nisi beatae quia non iste nihil accusantium nobis amet, officia eius, repellendus a cupiditate, commodi eos! Quis illum repudiandae exercitationem."
/>
</VStack>
);
};
@@ -1,4 +1,4 @@
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import { Meta, StoryFn } from "@storybook/react";
import React, { useEffect, useState } from "react";
import { UploadIcon } from "@navikt/aksel-icons";
import { UNSAFE_FileUpload as FileUpload, FileUploadItemProps } from ".";
@@ -11,6 +11,16 @@ import {
FileRejected,
FileRejectionReason,
} from "./FileUpload.types";
import {
States as DropzoneStates,
Translation as DropzoneTranslation,
} from "./file-upload-dropzone.stories";
import {
Description as ItemDescription,
Download as ItemDownload,
Icons as ItemIcons,
States as ItemStates,
} from "./file-upload-item.stories";
const meta: Meta<typeof FileUpload.Dropzone> = {
title: "ds-react/FileUpload",
@@ -180,7 +190,7 @@ export const Single: StoryFn = () => {
};
Single.parameters = { chromatic: { disable: true } };
export const Translation = () => (
export const Translation: StoryFn = () => (
<FileUpload
translations={{
dropzone: {
@@ -230,19 +240,45 @@ export const Translation = () => (
</FileUpload>
);
export const TriggerWithButton: StoryObj<typeof FileUpload.Trigger> = {
render: (props) => {
return (
<FileUpload.Trigger {...props} onSelect={console.log}>
<Button variant="secondary" icon={<UploadIcon aria-hidden />}>
Last opp filer
</Button>
</FileUpload.Trigger>
);
},
args: {
multiple: true,
accept: "",
maxSizeInBytes: 0,
},
export const TriggerWithButton: StoryFn = (props) => (
<FileUpload.Trigger {...props} onSelect={console.log}>
<Button variant="secondary" icon={<UploadIcon aria-hidden />}>
Last opp filer
</Button>
</FileUpload.Trigger>
);
TriggerWithButton.args = {
multiple: true,
accept: "",
maxSizeInBytes: 0,
};
export const Chromatic: StoryFn = () => (
<div>
<h2>Default</h2>
<Default />
<h2>Single</h2>
<Single />
<h2>Translation</h2>
<Translation />
<h2>TriggerWithButton</h2>
<TriggerWithButton />
<h2>DropzoneStates</h2>
<DropzoneStates />
<h2>DropzoneTranslation</h2>
<DropzoneTranslation />
<h2>ItemDescription</h2>
<ItemDescription />
<h2>ItemDownload</h2>
<ItemDownload />
<h2>ItemIcons</h2>
<ItemIcons />
<h2>ItemStates</h2>
<ItemStates />
</div>
);
Chromatic.parameters = {
chromatic: { disable: false },
};
@@ -1,5 +1,5 @@
import React, { forwardRef } from "react";
import { Slot } from "../../../util/Slot";
import { Slot } from "../../../slot/Slot";
import { FileUploadBaseProps } from "../FileUpload.types";
import { useFileUpload } from "../useFileUpload";
@@ -44,6 +44,11 @@ export interface FileUploadItemProps
* @default "idle"
*/
status?: "downloading" | "uploading" | "idle";
/**
* File description. Replaces file size when status is "idle".
* This is useful for displaying upload date. Should not act as a replacement for error messages.
*/
description?: string;
/**
* Props for the action button.
*/
@@ -71,6 +76,7 @@ export const Item: OverridableComponent<FileUploadItemProps, HTMLDivElement> =
onFileClick,
button,
translations,
description,
...rest
}: FileUploadItemProps,
ref,
@@ -91,7 +97,7 @@ export const Item: OverridableComponent<FileUploadItemProps, HTMLDivElement> =
if (status === "downloading") {
return translate("item.downloading");
}
return formatFileSize(file);
return description ?? formatFileSize(file);
}
return (
@@ -316,50 +316,6 @@ export const Empty: StoryFn<typeof FormSummary> = () => (
</VStack>
);
export const Chromatic: StoryObj<typeof FormSummary> = {
render: () => (
<div>
<div>
<h2>Default</h2>
<Default>{null}</Default>
</div>
<div>
<h2>Long Texts</h2>
<LongTexts>{null}</LongTexts>
</div>
<div>
<h2>No Link</h2>
<NoLink>{null}</NoLink>
</div>
<div>
<h2>Realistic Usage</h2>
<RealisticUsage>{null}</RealisticUsage>
</div>
<div>
<h2>Empty</h2>
<Empty>{null}</Empty>
</div>
</div>
),
parameters: {
chromatic: {
disable: false,
modes: {
default: {
viewport: {
width: 620,
},
},
sm: {
viewport: {
width: 479,
},
},
},
},
},
};
export const CommaSeparated: StoryFn<typeof FormSummary> = () => (
<FormSummary>
<FormSummary.Header>
@@ -386,3 +342,51 @@ export const CommaSeparated: StoryFn<typeof FormSummary> = () => (
</FormSummary.Answers>
</FormSummary>
);
export const Chromatic: StoryObj<typeof FormSummary> = {
render: () => (
<div>
<div>
<h2>Default</h2>
<Default>{null}</Default>
</div>
<div>
<h2>Long Texts</h2>
<LongTexts>{null}</LongTexts>
</div>
<div>
<h2>No Link</h2>
<NoLink>{null}</NoLink>
</div>
<div>
<h2>Realistic Usage</h2>
<RealisticUsage>{null}</RealisticUsage>
</div>
<div>
<h2>Empty</h2>
<Empty>{null}</Empty>
</div>
<div>
<h2>Comma Separated</h2>
<CommaSeparated>{null}</CommaSeparated>
</div>
</div>
),
parameters: {
chromatic: {
disable: false,
modes: {
default: {
viewport: {
width: 620,
},
},
sm: {
viewport: {
width: 479,
},
},
},
},
},
};
@@ -14,14 +14,14 @@ export default {
size: {
control: {
type: "radio",
options: ["medium", "small"],
},
options: ["medium", "small"],
},
variant: {
control: {
type: "radio",
options: ["primary", "secondary", "simple"],
},
options: ["primary", "secondary", "simple"],
},
error: {
control: {
@@ -32,7 +32,7 @@ export default {
parameters: {
chromatic: { disable: true },
},
} as Meta;
} satisfies Meta<typeof Search>;
type Story = StoryObj<typeof Search>;
@@ -75,7 +75,7 @@ export const Description = () => {
);
};
export const Error = () => {
export const WithError = () => {
return (
<div className="colgap">
<Select
@@ -149,8 +149,8 @@ export const Chromatic: StoryObj<typeof Select> = {
<Description />
</div>
<div>
<h2>Error</h2>
<Error />
<h2>WithError</h2>
<WithError />
</div>
<div>
<h2>Disabled</h2>
Loaded 100 of 355 files, more files were not shown because too many files have changed in this diff. Show more