Compare commits

...
Author SHA1 Message Date
KenandCopilot a16f60ae40 refactor: Restructured pagination
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-21 14:21:33 +02:00
Ken 38ad0297b8 fix: Update chips exports and sync skill 2026-10-21 14:05:42 +02:00
Ken 0b7d811f23 refactor: Restructured modal, popover 2026-10-21 13:56:59 +02:00
Ken fa58a66da4 refactor: Restructured expansion-card, link-card, list 2026-10-21 13:56:59 +02:00
Ken e888633c82 refactor: Restructured tabs, chat and chips 2026-10-21 13:56:58 +02:00
Ken 24b581c92c refactor: Component object-assign 2026-10-21 13:56:58 +02:00
Ken 7622b0cae4 refactor: Re-structured toggle-group component based on optimal structure 2026-10-21 13:56:58 +02:00
Ken 863b6208d9 fix(core): Nextjs-builds with webpack now works with ds-react (#5422) 2026-10-21 13:55:52 +02:00
dependabot[bot] faa8f96507 ⬆️ Bump @modelcontextprotocol/sdk from 1.31.0 to 1.32.0 (#5427)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-21 13:55:32 +02:00
dependabot[bot] dffaace465 ⬆️ Bump vite from 8.2.4 to 8.3.5 (#5428)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-21 13:55:08 +02:00
Ken 23963c93b1 feat: Update lint-staged, post-hooks 2026-10-21 13:53:27 +02:00
dependabot[bot] af7c24b5f0 ⬆️ Bump vite from 8.2.2 to 8.2.4 (#5424)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-21 13:44:07 +02:00
Ken 00deddb6f6 fix(CheckboxCard, RadioCard): Updated border-color in unchecked error-state, input-color in checked disabled state (#5425) 2026-10-21 13:38:58 +02:00
dependabot[bot] 47ef2953fa ⬆️ Bump follow-redirects from 1.16.0 to 1.16.1 (#5423)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-21 12:16:51 +02:00
Halvor Haugan bb5939d21e chore(mcp): bump @modelcontextprotocol/inspector to 2.11.0 2026-10-21 12:07:29 +02:00
Halvor Haugan a71d4b849a fix(Combobox2): remove aria-label from Field, and aria-readonly from Trigger 2026-10-21 10:20:38 +02:00
Halvor Haugan d604deb25c fix(website): fix typo in example desc CheckboxCard 2026-10-21 10:02:03 +02:00
Halvor Haugan 3fe9563078 feat(Combobox2): remove virtual focus when editing filter input 2026-10-20 21:55:45 +02:00
Halvor HauganandCopilot Autofix powered by AI 62d2f5af01 fix(website): avoid sandbox error on DataGrid examples (#5421)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-10-20 17:04:57 +02:00
dependabot[bot] 53780d0c67 ⬆️ Bump js-yaml from 5.4.3 to 5.5.0 (#5419)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-20 15:38:18 +02:00
dependabot[bot] d476be6604 ⬆️ Bump postcss-selector-parser from 7.1.6 to 7.1.7 (#5420)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-20 15:38:06 +02:00
KenandHalvor Haugan a0c9494b07 feat: Added support for internal translations (#5413)
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-20 15:19:15 +02:00
Ken bf857007cd fix(KI): Update vitest config and husky to support proper testing and linting while using cplt 2026-10-20 14:14:49 +02:00
Ken e6db21586e fix(sanity): Code-example block in sanity now defaults compact-view to false 2026-10-20 13:47:43 +02:00
Ken 619ebd5088 feat: Added egnet/uegnet til fields to markdown-version of component docs 2026-10-20 13:46:22 +02:00
Ken 1b539a9b13 chore: Added more website examples for CheckboxCard 2026-10-20 13:35:08 +02:00
Ken 8859bbb7ea feat(core): CheckboxCard component (#5364) 2026-10-20 13:26:36 +02:00
Halvor Haugan dff2c96b4b fix(Combobox2): announce no hits + prevent open with arrow when readOnly 2026-10-20 12:55:39 +02:00
Halvor Haugan 2177f3ab64 refactor(Combobox2): separate components for single- and multiselect 2026-10-20 12:14:16 +02:00
Anniken GranbergandKen 51629f9229 feat(ToggleGroup): Add xsmall size (#5411)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2026-10-20 12:05:39 +02:00
dependabot[bot] a2d0ffda0a ⬆️ Bump figlet from 1.11.3 to 1.12.0 (#5404)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:40:46 +02:00
dependabot[bot] 45aa8baf10 ⬆️ Bump tsc-alias from 1.9.1 to 1.9.7 (#5407)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:38:19 +02:00
dependabot[bot] 3f7b72369e ⬆️ Bump react-hook-form from 7.89.0 to 7.90.0 (#5409)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:30:25 +02:00
Halvor Haugan 8009289ca4 feat(website): add desc on ToggleGroup icon example 2026-10-16 10:34:43 +02:00
Ken ee50050d27 feat: Added custom link for navno tema header 2026-10-15 15:20:15 +02:00
Halvor Haugan 4e1cfddd84 feat(Combobox2): open on arrow up/down on trigger 2026-10-15 13:17:36 +02:00
Halvor Haugan 643ab79c8d chore(AI): update copilot-instructions.md and code-review skill (regarding tests) 2026-10-15 13:08:15 +02:00
Halvor Haugan d134397fd0 feat(core): mark Badge and Lookup from /PREVIEW as deprecated (#5401) 2026-10-15 11:44:32 +02:00
Halvor Haugan d8e62db7f1 fix(Listbox): fix issue where options were not announced by VO in Safari (#5399) 2026-10-14 15:34:57 +02:00
dependabot[bot] 0cf700322c ⬆️ Bump react-hook-form from 7.86.0 to 7.89.0 (#5395)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 13:21:32 +02:00
dependabot[bot] 2c468cc8c7 ⬆️ Bump the storybook group with 8 updates (#5393)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 13:21:12 +02:00
Halvor Haugan d722f23a5e fix(Combobox2): fix issue with VO+Safari when selecting option 2026-10-14 13:15:45 +02:00
github-actions[bot] 317940ec17 Version Packages (#5387)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-14 12:46:56 +02:00
fb0330cf9d refactor(Badge): Removed built inn a11y handling trough aria-label, labelledby and aria-hidden (#5397)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-14 10:37:44 +02:00
Halvor Haugan b9eb6568ad feat(website): add data-text-prose on DoDont 2026-10-13 14:21:17 +02:00
cc379525c0 feat(sanity): Store list of last published documents to keep track of postings (#5371)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2026-10-13 12:50:12 +02:00
KenandHalvor Haugan 6bb7becad7 fix(forms): Properly forward disabled/readonly-states, force disabled state when loading true (#5328)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2026-10-13 09:13:19 +02:00
dependabot[bot] 8a513c96cc ⬆️ Bump json5 from 2.2.1 to 2.2.3 (#5396)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 15:25:57 +02:00
Ken b4e5a3b8bd fix: all groups unique keys on dependabot.yml 2026-10-12 11:09:25 +02:00
dependabot[bot]andKen 64dd1a529b ⬆️ Bump next from 16.3.6 to 16.3.8 in /aksel.nav.no/website in the npm_and_yarn group across 1 directory (#5390)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-10-12 11:02:37 +02:00
3f9daed6a0 ⬆️ Bump simple-git from 3.36.0 to 4.0.1 (#5380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-12 10:13:58 +02:00
dependabot[bot] a53637c2d7 ⬆️ Bump http-cache-semantics from 4.2.0 to 4.3.0 (#5389)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 09:44:28 +02:00
Ken c9e48dfbd0 feat(website): Add error-pages to website, fix logging (#5321) 2026-10-12 09:38:45 +02:00
dependabot[bot] ef2e1f88e3 ⬆️ Bump js-yaml from 5.4.2 to 5.4.3 (#5378)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 09:22:37 +02:00
Halvor Haugan 05e88b37b8 fix(Search): icon more visible in forced-colors mode (#5386) 2026-10-09 16:48:25 +02:00
Halvor Haugan 8ec9677d20 fix(Combobox): misc fixes 2026-10-09 16:36:45 +02:00
Halvor Haugan 2b6c530fef fix(Listbox): forced-colors adjustment 2026-10-09 16:33:25 +02:00
github-actions[bot] f784aac8bb Version Packages (#5384)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-09 14:02:16 +02:00
Halvor Haugan 1490249f0c feat(Listbox): no looping (#5383) 2026-10-09 12:29:14 +02:00
Ken 31c4296568 fix(core): Expandable elements now respect media print and auto-opens (#5363) 2026-10-09 12:26:45 +02:00
Halvor Haugan af64a52f83 chore(deps): bump js-yaml from 4.3.1 to 4.3.2 2026-10-08 21:04:27 +02:00
Halvor Haugan 0d6db896c0 chore(mcp): bump @modelcontextprotocol/inspector to 2.9.0 2026-10-08 20:56:15 +02:00
dependabot[bot]andHalvor Haugan 91b4d8827e ⬆️ Bump sharp from 0.35.4 to 0.35.5 (#5374)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-08 20:49:07 +02:00
dependabot[bot] ea9ff5e63d ⬆️ Bump @modelcontextprotocol/sdk from 1.30.0 to 1.31.0 (#5373)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:47:17 +02:00
dependabot[bot] 3ab3498ddb ⬆️ Bump fast-copy from 4.0.4 to 4.1.1 (#5381)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:35:32 +02:00
dependabot[bot] 40e1717fe9 ⬆️ Bump proxy-addr from 2.0.7 to 2.0.8 (#5377)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:34:29 +02:00
dependabot[bot] 06e3234ecd ⬆️ Bump compression from 1.8.1 to 1.8.2 (#5376)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:34:15 +02:00
dependabot[bot] 43f7fc70f0 ⬆️ Bump source-map-js from 1.2.1 to 1.2.2 (#5375)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:33:51 +02:00
github-actions[bot] 2994c8d5ac Version Packages (#5361)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-08 19:03:01 +02:00
Ken 1bca520e92 examples(radiocard): Added missing compositions (#5369) 2026-10-08 12:26:24 +02:00
Ken c5dd3097e5 feat(icons): Added new icon MenuHamburgerMagnifyingGlass (#5368) 2026-10-07 15:00:59 +02:00
Ken a8cca91bcd feat(website): Adjust DS-page template 2026-10-07 11:48:12 +02:00
Ken bdddbd75d1 fix(website): Forvalter-field now has updated spacing 2026-10-06 13:35:35 +02:00
Ken 5e778e84a2 fix(sanity): Egnet til field no longer required 2026-10-06 13:32:23 +02:00
Ken 53dc01c64d chore(AI): Updated copilot-instructions 2026-10-05 14:46:40 +02:00
Ken c4cfbc66e1 chore(website): Upgrade sanity to 6.16 2026-10-05 14:28:53 +02:00
Ken 9c2e1bfbad perf(website): Update cache to use expire 0, making next visit get fresh content 2026-10-05 10:11:30 +02:00
Ken 93982b8572 chore: Upgrade sanity, fix css-layers in studio 2026-10-05 10:11:30 +02:00
dependabot[bot] 771906190c ⬆️ Bump babel-loader from 9.2.1 to 10.1.1 (#5356)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-05 09:21:52 +02:00
Halvor Haugan 8092686b61 feat(Listbox): fall back to selected opt. first if using arrow keys when nothing has virtual focus (#5353) 2026-10-05 09:20:53 +02:00
Halvor Haugan fda0c1686a fix(Listbox): ignore mouseOver on mobile (#5352) 2026-10-05 09:19:22 +02:00
Halvor Haugan 651e8dcc96 chore(website): remove unused dep babel-loader (#5351) 2026-10-05 09:12:39 +02:00
dependabot[bot]andKen 78ef1dac67 ⬆️ Bump figlet from 1.11.0 to 1.11.3 in /@navikt/aksel in the npm_and_yarn group across 1 directory (#5360)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-10-05 09:10:07 +02:00
dependabot[bot]andKen fac620ff17 ⬆️ Bump clipboardy from 5.3.1 to 5.3.2 (#5357)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-10-05 09:05:37 +02:00
dependabot[bot] 6023c4e1ac ⬆️ Bump lint-staged from 17.4.1 to 17.6.0 (#5359)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-05 08:58:24 +02:00
Halvor Haugan 40d3e86678 fix(Combobox2): only force scrollTop=0 when modal is open 2026-10-02 16:20:14 +02:00
Halvor Haugan ba1392f169 fix(Combobox2): don't set virtual focus when opening on mobile 2026-10-02 16:06:34 +02:00
dependabot[bot] 6e8a37a487 ⬆️ Bump undici from 6.28.1 to 6.29.0 (#5349)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-02 12:12:30 +02:00
Halvor Haugan ecbce15748 ci(website): deploy website to prod on changes in playroom (#5350) 2026-10-02 12:08:01 +02:00
Halvor Haugan 3d87a3f50c chore(playroom): bump playroom to 1.4.0 (#5348) 2026-10-02 11:44:40 +02:00
dependabot[bot] a3ecfd5e88 ⬆️ Bump hono from 4.13.5 to 4.13.9 (#5345)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 10:31:27 +02:00
dependabot[bot] d4150ec674 ⬆️ Bump dompurify from 3.4.14 to 3.4.16 (#5346)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-01 10:31:19 +02:00
github-actions[bot] 72df0780d3 Version Packages (#5326)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-01 09:52:34 +02:00
Halvor Haugan 05b1428679 feat(core): Badge, Lookup: available outside of Preview scope (#5344) 2026-10-01 09:32:02 +02:00
Ken 13bf57f2a7 chore(examples): Added RadioCard examples (#5343) 2026-09-30 19:15:43 +02:00
Ken 8b2ec8ca29 fix(website): Resolve hydration-error on frontpage 2026-09-30 15:02:54 +02:00
dependabot[bot]andKen ad6fb1fcf4 ⬆️ Bump the actions group with 2 updates (#5332)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-30 14:32:23 +02:00
dependabot[bot]andKen a133f2a18d ⬆️ Bump the react group across 1 directory with 2 updates (#5334)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-30 14:22:30 +02:00
dependabot[bot]andKen 87c726a96f ⬆️ Bump @next/bundle-analyzer from 16.3.4 to 16.3.6 (#5337)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-30 14:13:39 +02:00
Ken c0e50fc4ef fix(tailwind): add theme inline, fix radius, fix font/text (#5329) 2026-09-30 13:50:33 +02:00
Halvor Haugan 21a79d7fd2 chore(deps): add @types/lodash where missing (#5342) 2026-09-30 13:29:59 +02:00
Ken a710c1d0a2 perf(eslint): Improve eslint correctness and speed (#5331) 2026-09-30 13:17:51 +02:00
dependabot[bot] ba90cf472b ⬆️ Bump tsx from 4.23.12 to 4.23.15 (#5338)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 13:17:37 +02:00
dependabot[bot] 7e7bb85571 ⬆️ Bump brace-expansion from 1.1.18 to 1.1.21 (#5341)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 12:19:23 +02:00
dependabot[bot] c76204d624 ⬆️ Bump markdown-it from 14.3.0 to 14.3.2 (#5340)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-30 12:19:13 +02:00
Ken 9e57079fd1 fix: Removed aksel workspace ref in ds-react (#5339) 2026-09-30 12:19:01 +02:00
dependabot[bot]andKen 494c93f683 ⬆️ Bump @biomejs/biome from 2.5.9 to 2.5.14 (#5336)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-30 11:47:26 +02:00
dependabot[bot] 4de9033080 ⬆️ Bump stylelint from 17.14.1 to 17.15.0 (#5335)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 18:47:07 +02:00
KenandCopilot Autofix powered by AI 812b7cbc3b feat(Radio): New RadioCard component (#5276)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-29 18:46:25 +02:00
Ken 11b5c91372 fix(deps): Sync vitest, update version lint to check grouped versions, update dependabot to handle security and version updates separate 2026-09-29 18:31:28 +02:00
Ken 95b8baaf5a chore(docs): Update copilot and contributing 2026-09-29 18:25:44 +02:00
Ken 470a8207f2 fix(CLI): Now runs outside of git-repo dirs, waits for v8 migrations to finish running async 2026-09-29 15:05:42 +02:00
Ken 0984db737e fix(sanity): Correctly filter published and draft articles in structure view, better slug parsing 2026-09-29 14:54:12 +02:00
Ken 93e79eae93 fix(website): Removed www from sitemap loc 2026-09-29 14:47:16 +02:00
Ken 98aef6d317 perf(MCP): Safeguard server against large queries and requests 2026-09-29 14:34:32 +02:00
Halvor HauganandCopilot Autofix powered by AI b82d24b180 docs(website): update some examples (#5330)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-29 14:31:14 +02:00
KenandCopilot Autofix powered by AI 3d19c6821d chore(templates): Sync 400 and 500 templates with figma (#5314)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-29 14:24:28 +02:00
Ken 6ea5fc4e63 fix(datepicker): Default to single mode, making use without hook interact correctly (#5327) 2026-09-29 12:54:32 +02:00
Ken 69b8c5ec07 feat(core): Actionmenu no longer uses modal=true from Menu, smoother animation (#5296) 2026-09-29 11:38:51 +02:00
KenandHalvor Haugan 845099aa7f fix(CLI): Codemods no longer erase whitespace (#5322)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2026-09-29 11:38:31 +02:00
Ken 3eee1e9689 perf(tests): Improved metadata-test speed by avoiding ts.createProgram 2026-09-29 11:37:18 +02:00
Ken d3cc8ef625 security(workflows): Improve token-permissions, creds and release branch (#5323) 2026-09-29 10:31:17 +02:00
dependabot[bot] d185b1d25b ⬆️ Bump tsdown from 0.22.14 to 0.23.0 (#5316)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:26:33 +02:00
dependabot[bot] 5b3df59f5a ⬆️ Bump @testing-library/user-event from 14.6.6 to 14.6.7 (#5319)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:24:00 +02:00
dependabot[bot] b97a1e7357 ⬆️ Bump zod from 4.4.3 to 4.6.5 (#5320)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:23:40 +02:00
dependabot[bot] f5be404ff4 ⬆️ Bump ip-address from 10.5.0 to 10.7.2 (#5325)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:21:03 +02:00
dependabot[bot] 29a8538a0d ⬆️ Bump fast-uri from 3.1.6 to 3.1.8 (#5324)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-29 10:00:48 +02:00
Ken d90c9be95e chore(website): Removed logging added for testing and instrumentation 2026-09-28 16:05:00 +02:00
Halvor Haugan fce0c8f05b chore(core): bump style-dictionary to 5.5.5 (#5310) 2026-09-28 15:15:40 +02:00
Halvor Haugan e91c8cb68a fix(website): adjust example of icon in ExpansionCard (#5311) 2026-09-28 14:56:51 +02:00
Ken cff31812ec chore(tokens): Add link to color source 2026-09-28 11:59:53 +02:00
dependabot[bot]andKen 1085541c20 ⬆️ Bump postcss-selector-parser from 7.1.5 to 7.1.6 (#5309)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-09-28 09:27:43 +02:00
Halvor Haugan 2a5a0e18cd build(core): exclude meta files from React build (#5301) 2026-09-28 09:18:41 +02:00
github-actions[bot] 3f5153d700 Version Packages (#5303)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-25 23:34:19 +02:00
Ken 9673de4c26 fix(releases): Revert to working changeset setup, force push to another patch 2026-09-25 23:15:56 +02:00
374 changed files with 9955 additions and 5784 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---
Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW
+6
View File
@@ -0,0 +1,6 @@
---
"@navikt/ds-react": minor
"@navikt/ds-css": minor
---
Checkbox: Add `CheckboxCard` and `CheckboxCardGroup` components
+6
View File
@@ -0,0 +1,6 @@
---
"@navikt/ds-react": patch
"@navikt/ds-css": patch
---
ToggleGroup: Add xsmall as size
+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-css": patch
---
RadioCard, CheckboxCard: Updated border-color in unchecked error-state, input-color in checked disabled state
+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---
Build: `next build --webpack` now works when package is part of `optimizePackageImports`
+10 -5
View File
@@ -5,17 +5,18 @@
- Narrow search space; don't replace reading code.
- Before editing: read target file + nearest related files (`index.ts`, story, test, exports).
- Prefer known paths/scripts over broad searches.
- User often edits files between turns. Before reviewing/refactoring, re-read the files (or `git diff`); never rely on earlier reads.
- Ignore release/deploy/changelog/ops scripts unless asked.
## Behavior
- Only respond in same language as the input. Prefer english always.
- Use caveman-skill unless asked not to
- Only respond in same language as the input. Prefer English always.
- Use caveman-skill unless asked not to.
- No apologies, no "I think", no change summaries.
- Answer-first. 0-2 sentences max.
- No thinking aloud; do next edit. Explain only when asked or non-obvious.
- Show only what changes. Minimal diffs; don't paste whole files.
- Edits scoped to one file unless asked. Ask only when truly blocked.
- Edit only the files the change needs (see Definition of done); no unrelated files. Ask only when truly blocked.
- Favor perf/security; avoid re-renders, top-level side effects, unsafe HTML, env var leaks.
## Repo summary
@@ -27,12 +28,13 @@
## Start and validate
1. `corepack enable`
2. `corepack yarn install` (needs `NPM_AUTH_TOKEN` with `read:packages`). Without a token: `corepack yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint`
2. `corepack yarn install` (needs `NPM_AUTH_TOKEN` with `read:packages`). Without a token: `corepack yarn workspaces focus aksel @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint`
3. `corepack yarn boot` (builds all public packages; rerun after `corepack yarn clean`)
- Run agent commands through `corepack yarn`. Order: existing root/workspace script → Yarn-local binary → add a pinned dependency only when new tooling is required.
- Never use `npx`, `pnpx`, `pnpm dlx`, `yarn dlx`, global binaries or curl-piped installers. Public consumer docs (`npx @navikt/aksel`) are intentional; keep them.
- If Corepack/Yarn fails because of user-level configuration, report the exact error. Never edit user-home config.
- Under the `navikt/cplt` sandbox, `Parse error when loading ~/.yarnrc.yml` means the home rc file is unreadable (EPERM), not a repo issue. Stop retrying, report it and ask the user to fix it outside the sandbox. Don't touch the repo `.yarnrc.yml`.
- Choosing checks: use the `aksel-local-validation` skill. Changed-file lint first, then the affected workspace, then root suites only when needed.
## Definition of done
@@ -47,7 +49,8 @@
- `corepack yarn tsc --noEmit --incremental false --project <tsconfig>` - targeted type-check
- `corepack yarn workspace <name> <script>` - e.g. `@navikt/ds-react test` (Vitest + tsc), `website test`
- `corepack yarn lint` / `corepack yarn test` / `corepack yarn boot` - root suites (slow; cross-workspace changes only)
- `corepack yarn storybook` (:6006) / `corepack yarn storybook:aksel` (website examples, :6007, no secrets) / `corepack yarn storybook:test` (play tests, Playwright Firefox) / `corepack yarn dev` (website, :3000, needs Sanity token)
- `corepack yarn storybook` (:6006) / `corepack yarn storybook:aksel` (website examples, :6007, no secrets) / `corepack yarn storybook:test` (play tests, Playwright Firefox; Chromium when `COPILOT_CLI` is set) / `corepack yarn dev` (website, :3000, needs Sanity token)
- Targeted play tests: `corepack yarn vitest run --config ./vitest.config.ts --project=storybook <path/to/X.tests.stories.tsx>` (repo root). Run after changing play tests. Under Copilot CLI this runs Chromium; if the browser can't launch, report tests as not run with the exact command.
## Where to edit
@@ -80,8 +83,10 @@ Path-specific rules load automatically from `.github/instructions/` (see each `a
## Coding defaults
- Small diffs. No drive-by refactors or new deps unless asked.
- Propose the simplest maintainable solution first (explicit lists, member-name checks). No TS compiler API/AST tricks in tests (slow). Ask before escalating complexity.
- Copy nearest existing pattern before writing new code.
- Stable public APIs. Prefer additive props/exports.
- `node:` imports for Node builtins. Never import from `esm/`/`cjs/` output.
- Preserve JSDoc on public props/components when changing public APIs.
- Reuse helpers: `cl`, `composeEventHandlers`, `useId`, `omit`, `useClientLayoutEffect`, `useEventListener`.
- No tests for trivial logic. Only tests for complex logic that has a higher risk of breaking.
+45
View File
@@ -15,7 +15,14 @@ updates:
registries:
- npm-github
groups:
next:
applies-to: version-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity:
applies-to: version-updates
patterns:
- "sanity"
- "sanity-*"
@@ -23,17 +30,55 @@ updates:
- "next-sanity"
- "@portabletext/*"
eslint:
applies-to: version-updates
patterns:
- "eslint"
- "@eslint/*"
react:
applies-to: version-updates
patterns:
- "react"
- "react-dom"
storybook:
applies-to: version-updates
patterns:
- "*storybook*"
vitest:
applies-to: version-updates
patterns:
- "vitest"
- "@vitest/*"
# Security updates ignore version-update groups, so mirror them to keep lockstep packages together.
next-security:
applies-to: security-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity-security:
applies-to: security-updates
patterns:
- "sanity"
- "sanity-*"
- "@sanity/*"
- "next-sanity"
- "@portabletext/*"
eslint-security:
applies-to: security-updates
patterns:
- "eslint"
- "@eslint/*"
react-security:
applies-to: security-updates
patterns:
- "react"
- "react-dom"
storybook-security:
applies-to: security-updates
patterns:
- "*storybook*"
vitest-security:
applies-to: security-updates
patterns:
- "vitest"
- "@vitest/*"
+1 -1
View File
@@ -12,4 +12,4 @@ applyTo: "**/*.test.ts,**/*.test.tsx,**/*.stories.tsx"
- Query by role and accessible name (`getByRole("button", { name })`). Avoid snapshots unless nearby tests already use them.
- Cover the main path plus 1-2 meaningful edge cases. A behavior change updates the nearest story and test in the same change.
- ds-react unit tests run with `TZ=UTC`. Date logic must not depend on the local timezone.
- Browser tests need Playwright Firefox. Yarn has `enableScripts: false`, so if the browser is missing, report it instead of installing globally.
- Browser tests need Playwright Firefox (Chromium under Copilot CLI, see `vitest.config.ts`). Yarn has `enableScripts: false`, so if the browser is missing, report it instead of installing globally.
+1 -1
View File
@@ -69,7 +69,7 @@ Skip anything ESLint, Stylelint, Biome, Prettier or TypeScript already catches.
### Tests and docs (🟡)
- A behavior change has a story `play` test or Vitest test proving the user-facing contract. Queries use role and accessible name.
- Complex logic with a higher risk of breaking has a story `play` test or Vitest test proving the user-facing contract. Queries use role and accessible name.
- Tests are deterministic: no dependence on timezone, current time or randomness.
- Edge cases that are easy to miss: controlled vs. uncontrolled, `ref`/`className`/`...rest` forwarding, `as` prop, empty and very long content.
- Website examples (`aksel.nav.no/website/pages/eksempler/`) are updated when usage changes.
@@ -95,7 +95,7 @@ Move stories to component root dir. If stories import from internal paths, updat
### 5. Update `index.ts`
All exports are **named** — no `default` exports anywhere. The component `index.ts` re-exports the compound component, its sub-components, and their types from the root file:
All exports are **named** — no `default` exports anywhere. For compound components, the root file is the single export source: it assembles the compound component, imports every publicly exported sub-component and its prop types, and exports all public values and types together. The component `index.ts` only re-exports these names from `./root/<Component>Root` — never directly from sub-component files or shared type files.
```ts
"use client";
@@ -111,13 +111,18 @@ export type {
} from "./root/<Component>Root";
```
Group values first, then types. If types live in `<Component>.types.ts`, re-export them from there.
Group values first, then types. If types live in `<Component>.types.ts`, import them into the root file and include them in its type export block. The `index.ts` still re-exports them from the root file.
#### Compound root file export pattern
In `<Component>Root.tsx`, assemble the compound component with `Object.assign` — **do not** use an `as <Component>Component` cast or a dedicated `<Component>Component` interface:
```tsx
import {
<Component>Trigger,
type <Component>TriggerProps,
} from "../trigger/<Component>Trigger";
const <Component>Root = forwardRef<HTMLDivElement, <Component>Props>((props, ref) => {
// ...
});
@@ -138,6 +143,8 @@ export { <Component>, <Component>Trigger };
export type { <Component>Props, <Component>TriggerProps };
```
Follow `accordion/root/AccordionRoot.tsx`: export the imported sub-component values and prop types alongside the compound component at the bottom of the root file. Do not add internal-only sub-components to the public export chain. Sub-components must not import from the root file or component `index.ts`; use shared type/context/helper files to avoid circular dependencies.
Why `Object.assign` instead of `as`: the cast types the const as an interface that has no runtime `valueDeclaration`, so `react-docgen-typescript` (used by `yarn docgen:meta`) fails to extract props and can't document the component when it is exported via a grouped `export { }` statement. `Object.assign` lets TypeScript infer the intersection type from the value, so metadata generation works with named, bottom-of-file exports.
Sub-component files follow the same rule — named value export plus named type export at the bottom, no inline `export interface` and no default:
@@ -173,6 +180,8 @@ Concretely: grep for `export` lines in both versions and diff them. All componen
**Package root.** Verify `@navikt/core/react/src/index.ts` still re-exports the component. No changes needed there unless the component's `index.ts` path changed (it shouldn't).
**Root-only re-exports.** For compound components, verify every value and type export in the component `index.ts` comes from `./root/<Component>Root`, and every forwarded name is exported by that root file.
**No case-duplicate tracked paths.** This is the most common restructure bug. On case-insensitive filesystems (macOS, Windows) git can end up tracking **two** entries that differ only in casing (e.g. `accordion.stories.tsx` **and** `Accordion.stories.tsx`) while the working tree shows only one file. The stale entry then breaks tests and checkout on other machines. `ls` / file search will NOT reveal it — you must ask git. Run:
```sh
@@ -281,6 +290,7 @@ dialog/
- **No breaking changes.** Public export names and prop shapes must be identical after restructuring.
- **Named exports only.** No `default` exports; group named exports at the bottom of each file (values first, then types).
- **Compound exports flow through the root.** Component `index.ts` re-exports public values and types only from `root/<Component>Root.tsx`, not directly from sub-component or shared type files.
- **Compound roots use `Object.assign`.** No `as <Component>Component` cast or `<Component>Component` interface — it breaks `yarn docgen:meta` prop extraction.
- Component rules (React 17 compatibility, `"use client"`, `forwardRef`/`className`/`...rest`/`as`, tokens) live in `.github/instructions/ds-react-component.instructions.md`. They still apply.
- Keep JSDoc on public props and components. The component-level JSDoc block sits directly above the `Object.assign` call; sub-component `@see` tags go on the keys inside the `Object.assign` object literal.
@@ -289,6 +299,6 @@ dialog/
- Canonical example: `@navikt/core/react/src/dialog/`
- Package public API: `@navikt/core/react/src/index.ts`
- Component `index.ts` pattern: `@navikt/core/react/src/dialog/index.ts`
- Component export pattern: `@navikt/core/react/src/accordion/root/AccordionRoot.tsx` and `@navikt/core/react/src/accordion/index.ts`
- Context pattern: `@navikt/core/react/src/dialog/root/DialogRoot.context.ts`
- Meta: `@navikt/core/react/src/accordion/Accordion.meta.ts`
+1
View File
@@ -17,6 +17,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+8 -3
View File
@@ -14,15 +14,15 @@ concurrency:
cancel-in-progress: true
permissions:
actions: read
contents: read
packages: write
id-token: write
jobs:
prebuild-app-for-playwright:
name: Build app for Playwright
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # Push E2E image to GHCR
env:
IMAGE_NAME: ${{ github.repository }}-e2e
REGISTRY: ghcr.io
@@ -31,6 +31,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -61,6 +62,9 @@ jobs:
needs: [prebuild-app-for-playwright]
name: Playwright shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: read # Pull E2E image from GHCR
container:
image: mcr.microsoft.com/playwright:v1.61.1-noble
services:
@@ -81,6 +85,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+4
View File
@@ -24,6 +24,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -41,6 +42,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -58,6 +60,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -78,6 +81,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -22,14 +22,14 @@ jobs:
name: Run Chromatic
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
pull-requests: write
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+3
View File
@@ -33,6 +33,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
ref: main
- name: Setup project
@@ -61,6 +62,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -87,6 +89,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+1
View File
@@ -33,6 +33,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+2
View File
@@ -12,6 +12,7 @@ jobs:
upload:
name: Build and upload token config
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: "read"
@@ -22,6 +23,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+3
View File
@@ -12,6 +12,7 @@ jobs:
upload:
name: Upload fonts to CDN
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
@@ -20,6 +21,8 @@ jobs:
steps:
- name: Checkout code
uses: "actions/checkout@v7"
with:
persist-credentials: false
- name: Upload fonts to CDN
id: upload
+2 -3
View File
@@ -15,15 +15,14 @@ jobs:
name: Build and deploy to dev-gcp
runs-on: ubuntu-latest
permissions:
packages: "write"
contents: "read"
id-token: "write"
deployments: "write"
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -46,7 +45,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to dev-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: aksel.nav.no/nais-dev.yaml
CLUSTER: dev-gcp
+2 -1
View File
@@ -26,6 +26,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -48,7 +49,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: apps/mcp-server/.nais/app.yaml
CLUSTER: prod-gcp
+5 -3
View File
@@ -6,6 +6,7 @@ on:
- main
paths:
- "aksel.nav.no/**"
- "apps/playroom/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -23,6 +24,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -42,15 +45,14 @@ jobs:
name: Build and deploy to prod-gcp
runs-on: ubuntu-latest
permissions:
packages: "write"
contents: "read"
id-token: "write"
deployments: "write"
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -76,7 +78,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
+2 -2
View File
@@ -16,7 +16,6 @@ jobs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/v7-aksel.nav.no'
permissions:
packages: "write"
contents: "read"
id-token: "write"
steps:
@@ -24,6 +23,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
@@ -54,7 +54,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: v7-aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
+20 -12
View File
@@ -14,7 +14,8 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
name: Release packages
if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }}
# Only publish from main, also when dispatched manually
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: "write"
@@ -41,31 +42,38 @@ jobs:
- name: Create release Pull Request or publish to NPM
id: changesets
uses: changesets/action@v2
uses: changesets/action@v1
with:
version-script: yarn create-version
publish-script: yarn release
create-github-releases: false
version: yarn create-version
publish: yarn release
createGithubReleases: false
env:
# Use OIDC for npm authentication instead of NPM_TOKEN
# https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868
NPM_TOKEN: ""
# Needed for push to main
github-token: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create .npmrc for GPR
run: |
cat << EOF > "$HOME/.npmrc"
@navikt:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=$NPM_TOKEN
EOF
env:
NPM_TOKEN: ${{ secrets.GIT_REPO }}
- name: Publish to GPR
if: steps.changesets.outputs.published == 'true'
id: changesets-gpr
uses: changesets/action@v2
uses: changesets/action@v1
with:
publish-script: yarn changeset publish
create-github-releases: false
publish: yarn changeset publish
createGithubReleases: false
env:
NPM_TOKEN: ""
# changeset v3 publishes with `yarn npm publish`, which reads the @navikt scope (GPR) from .yarnrc.yml
NPM_AUTH_TOKEN: ${{ secrets.GIT_REPO }}
# Needed for push to main
github-token: ${{ secrets.GIT_REPO }}
GITHUB_TOKEN: ${{ secrets.GIT_REPO }}
- name: Generate icon.zip
if: steps.changesets.outputs.published == 'true'
+10 -3
View File
@@ -1,6 +1,13 @@
CHANGED=`git diff $1 $2 --stat -- yarn.lock | wc -l`
if [ $CHANGED -gt 0 ];
then
echo
echo "*** 🟡 yarn.lock has changed 🟡 ***"
fi
printf '\n\033[1;33m'
printf ' ╔══════════════════════════════════════════╗\n'
printf ' ║ ║\n'
printf ' ║ !! yarn.lock has changed !! ║\n'
printf ' ║ ║\n'
printf ' ║ Run: yarn install ║\n'
printf ' ║ ║\n'
printf ' ╚══════════════════════════════════════════╝\n'
printf '\033[0m\n'
fi
+10 -3
View File
@@ -1,6 +1,13 @@
CHANGED=`git diff HEAD@{1} --stat -- yarn.lock | wc -l`
if [ $CHANGED -gt 0 ];
then
echo
echo "*** 🟡 yarn.lock has changed 🟡 ***"
fi
printf '\n\033[1;33m'
printf ' ╔══════════════════════════════════════════╗\n'
printf ' ║ ║\n'
printf ' ║ !! yarn.lock has changed !! ║\n'
printf ' ║ ║\n'
printf ' ║ Run: yarn install ║\n'
printf ' ║ ║\n'
printf ' ╚══════════════════════════════════════════╝\n'
printf '\033[0m\n'
fi
+2 -1
View File
@@ -1 +1,2 @@
yarn lint-staged
# Runs the local lint-staged checks before committing
./node_modules/.bin/lint-staged
+18
View File
@@ -1,5 +1,23 @@
# @navikt/aksel-icons
## 8.19.2
## 8.19.1
## 8.19.0
### Minor Changes
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
## 8.18.0
## 8.17.2
### Patch Changes
- Package: Fixed regression where package was published without dist-files ([`9673de4`](https://github.com/navikt/aksel/commit/9673de4c264479dd8ca04897003532595acad255))
## 8.17.1
## 8.17.0
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5 3.25C17.9518 3.25 20.75 6.04822 20.75 9.5C20.75 11.6221 19.6911 13.4953 18.0742 14.625L20.667 19.6562C20.8567 20.0244 20.7119 20.4773 20.3438 20.667C19.9756 20.8567 19.5227 20.7119 19.333 20.3438L16.751 15.3301C16.0524 15.6 15.2938 15.75 14.5 15.75C11.0482 15.75 8.25 12.9518 8.25 9.5C8.25 6.04822 11.0482 3.25 14.5 3.25ZM15.8994 17.25C15.993 17.25 16.0792 17.3026 16.1221 17.3857L16.6377 18.3857C16.723 18.552 16.6021 18.75 16.415 18.75H3.5C3.08579 18.75 2.75 18.4142 2.75 18C2.75 17.5858 3.08579 17.25 3.5 17.25H15.8994ZM14.5 4.75C11.8766 4.75 9.75 6.87665 9.75 9.5C9.75 12.1234 11.8766 14.25 14.5 14.25C17.1234 14.25 19.25 12.1234 19.25 9.5C19.25 6.87665 17.1234 4.75 14.5 4.75ZM6.75195 11.25C6.86836 11.25 6.96896 11.3306 6.99805 11.4434C7.08182 11.7679 7.1863 12.084 7.30957 12.3906C7.37768 12.56 7.2558 12.75 7.07324 12.75H3.5C3.08579 12.75 2.75 12.4142 2.75 12C2.75 11.5858 3.08579 11.25 3.5 11.25H6.75195ZM7.55664 5.25C7.75557 5.25 7.8765 5.47207 7.77734 5.64453C7.60333 5.94718 7.4499 6.26318 7.31738 6.58984C7.27831 6.68596 7.18482 6.75 7.08105 6.75H3.5C3.08579 6.75 2.75 6.41421 2.75 6C2.75 5.58579 3.08579 5.25 3.5 5.25H7.55664Z" fill="#202733"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,12 @@
name: MenuHamburgerMagnifyingGlass
category: Interface
sub_category: Menu
keywords:
- Menu
- meny
- hamburger
- search
- søk
- forstørrelsesglass
variant: Stroke
created_at: 07.10.2026
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "8.17.1",
"version": "8.19.2",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
@@ -75,19 +75,20 @@
"@figma/rest-api-spec": "^0.41.0",
"@svgr/cli": "8.1.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "4.17.25",
"@types/react": "19.2.14",
"adm-zip": "^0.6.1",
"concurrently": "10.0.4",
"copyfiles": "^2.4.1",
"fast-glob": "3.3.3",
"hast-util-select": "6.0.4",
"js-yaml": "^5.4.2",
"js-yaml": "^5.5.0",
"lodash": "4.18.1",
"prettier": "3.9.6",
"react": "19.2.8",
"react": "19.3.0",
"rehype-parse": "9.0.1",
"tsc-alias": "1.9.1",
"tsx": "^4.23.1",
"tsc-alias": "1.9.7",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"unified": "11.0.5",
"vitest": "4.1.11"
+10
View File
@@ -1,5 +1,15 @@
# @navikt/aksel-stylelint
## 8.19.2
## 8.19.1
## 8.19.0
## 8.18.0
## 8.17.2
## 8.17.1
## 8.17.0
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "8.17.1",
"version": "8.19.2",
"type": "module",
"engines": {
"node": ">=18.12.0"
@@ -43,14 +43,14 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^8.17.1",
"@navikt/ds-tokens": "^8.17.1",
"@navikt/ds-css": "^8.19.2",
"@navikt/ds-tokens": "^8.19.2",
"concurrently": "10.0.4",
"postcss-selector-parser": "^7.1.5",
"postcss-selector-parser": "^7.1.7",
"postcss-value-parser": "^4.2.0",
"stylelint": "^17.14.1",
"stylelint": "^17.15.0",
"stylelint-test-rule-node": "^1.2.0",
"tsx": "^4.23.1",
"tsx": "^4.23.15",
"typescript": "6.0.3"
}
}
+22
View File
@@ -1,5 +1,27 @@
# @navikt/aksel
## 8.19.2
## 8.19.1
## 8.19.0
### Patch Changes
- CLI: Update clipboardy to v5.3.2 ([#5357](https://github.com/navikt/aksel/pull/5357))
- CLI: Update figlet to 1.11.3 ([#5360](https://github.com/navikt/aksel/pull/5360))
## 8.18.0
### Patch Changes
- Codemod: Migrations now run in folders that are not git repositories, and a missing git install gets its own error message. `codemod v8` no longer stops before `v8-tokens` because of changes made by earlier migrations in the same run. ([`470a820`](https://github.com/navikt/aksel/commit/470a8207f23aee71243b986ef2836967c2498e71))
- Codemod: `v8-tokens` no longer removes whitespace before replaced tokens or rewrites longer names that contain a token name. ([#5322](https://github.com/navikt/aksel/pull/5322))
## 8.17.2
## 8.17.1
## 8.17.0
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "8.17.1",
"version": "8.19.2",
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
"author": "Aksel | Nav designsystem team",
"license": "MIT",
@@ -42,11 +42,11 @@
"dependencies": {
"chalk": "5.6.2",
"cli-progress": "3.12.0",
"clipboardy": "5.3.1",
"clipboardy": "5.3.2",
"commander": "15.0.0",
"enquirer": "2.4.1",
"fast-glob": "3.3.3",
"figlet": "1.11.0",
"figlet": "1.12.0",
"is-git-clean": "1.1.0",
"jscodeshift": "17.4.0",
"lodash": "4.18.1"
@@ -55,6 +55,7 @@
"@types/cli-progress": "3.11.6",
"@types/is-git-clean": "1.1.2",
"@types/jscodeshift": "17.3.0",
"@types/lodash": "4.17.25",
"rimraf": "6.1.3",
"typescript": "6.0.3",
"vitest": "4.1.11"
@@ -0,0 +1,91 @@
import type { Command } from "commander";
import isGitClean from "is-git-clean";
import { afterEach, describe, expect, test, vi } from "vitest";
import { validateGit } from "../validation";
vi.mock("is-git-clean", () => ({ default: { sync: vi.fn() } }));
const gitSync = vi.mocked(isGitClean.sync);
function createProgram() {
const error = vi.fn((message: string) => {
throw new Error(message);
});
return { program: { error } as unknown as Command, error };
}
function gitError(props: Record<string, unknown>) {
return Object.assign(new Error("Command failed: git status"), props);
}
describe("validateGit", () => {
afterEach(() => {
vi.clearAllMocks();
});
test("passes when the working tree is clean", () => {
gitSync.mockReturnValue(true);
const { program, error } = createProgram();
validateGit({}, program);
expect(error).not.toHaveBeenCalled();
});
test("fails when the working tree has changes", () => {
gitSync.mockReturnValue(false);
const { program, error } = createProgram();
expect(() => validateGit({}, program)).toThrow(/stash or commit/);
expect(error).toHaveBeenCalledTimes(1);
});
test.each([
"fatal: not a git repository (or any of the parent directories): .git\n",
"fatal: Not a git repository (or any of the parent directories): .git\n",
])("passes outside a git repository (%j)", (stderr) => {
gitSync.mockImplementation(() => {
throw gitError({ status: 128, stderr });
});
const { program, error } = createProgram();
validateGit({}, program);
expect(error).not.toHaveBeenCalled();
});
test("reports a missing git binary instead of asking to stash", () => {
gitSync.mockImplementation(() => {
throw gitError({ code: "ENOENT" });
});
const { program, error } = createProgram();
expect(() => validateGit({}, program)).toThrow(/git/i);
expect(error.mock.calls[0][0]).not.toMatch(/stash or commit/);
expect(error.mock.calls[0][0]).toMatch(/--force/);
});
test("fails on other git errors", () => {
gitSync.mockImplementation(() => {
throw gitError({
status: 128,
stderr: "fatal: detected dubious ownership",
});
});
const { program } = createProgram();
expect(() => validateGit({}, program)).toThrow(/stash or commit/);
});
test.each([{ dryRun: true }, { force: true }])(
"skips the check with force",
(options) => {
const { program, error } = createProgram();
validateGit(options, program);
expect(gitSync).not.toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
},
);
});
@@ -0,0 +1,29 @@
/* @ts-expect-error No avaliable types for testUtils */
import { applyTransform } from "jscodeshift/dist/testUtils";
import { describe, expect, test } from "vitest";
import * as transform from "../update-js-tokens";
const run = (source: string) =>
applyTransform({ ...transform, parser: "tsx" }, {}, { source });
describe("update-js-tokens keeps boundaries intact", () => {
test("does not replace inside longer identifiers", () => {
expect(
run(
`import { NavdsGlobalColorGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [NavdsGlobalColorGray100, MyNavdsGlobalColorGray100, NavdsGlobalColorGray1000];`,
),
).toBe(
`import { AGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [AGray100, MyNavdsGlobalColorGray100, NavdsGlobalColorGray1000];`,
);
});
test("does not replace similar names when aliased", () => {
expect(
run(
`import { NavdsGlobalColorGray100 as gray } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [gray, grayish, mygray];`,
),
).toBe(
`import { AGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [AGray100, grayish, mygray];`,
);
});
});
@@ -1,5 +1,6 @@
import type { API, JSCodeshift } from "jscodeshift";
import { tokens } from "../../../tokens-map.js";
import { createIdentifierRegex } from "../../../utils/identifier-regex";
import {
getImportSpecifier,
getImportSpecifierName,
@@ -60,8 +61,7 @@ export default function transformer(file: JSCodeshift, api: API) {
let code = root.toSource(getLineTerminator(file.source));
const rgx = new RegExp(`(${localName})`, "gm");
code = code.replace(rgx, out);
code = code.replace(createIdentifierRegex(String(localName)), out);
root = j(code);
}
});
-1
View File
@@ -2,7 +2,6 @@
import { applyTransform } from "jscodeshift/dist/testUtils";
import fs from "node:fs";
import path from "node:path";
// eslint-disable-next-line import/default
import prettier from "prettier";
import { describe, expect, test } from "vitest";
@@ -0,0 +1,11 @@
/**
* Matches a JS identifier as a whole word.
* Lookarounds are zero-width, so replacing the match never removes surrounding whitespace,
* and longer identifiers containing the name (`MyToken`, `Token2`, `$Token`) are left untouched.
*/
function createIdentifierRegex(identifier: string) {
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(`(?<![\\w$])${escaped}(?![\\w$])`, "gm");
}
export { createIdentifierRegex };
@@ -218,8 +218,25 @@ describe("getTokenRegex JS", () => {
expect("ATextRed200").not.toMatch(regex);
expect("AATextRed200").not.toMatch(regex);
expect("AATextRed").not.toMatch(regex);
expect("$ATextRed").not.toMatch(regex);
});
/* Since we cant do a positive lookbehind, these cases will be matched */
expect("AATextRed").toMatch(regex);
test("should not include surrounding whitespace in the match", () => {
const regex = getTokenRegex("--a-text-red", "js");
expect(" ATextRed ".match(regex)).toEqual(["ATextRed"]);
});
});
describe("getTokenRegex does not consume leading whitespace", () => {
test.each([
["css", " var(--a-text-red)", "--a-text-red"],
["scss", "margin: 0 $a-text-red", "$a-text-red"],
["less", "margin: 0 @a-text-red", "@a-text-red"],
] as const)("%s", (format, input, expected) => {
expect(input.match(getTokenRegex("--a-text-red", format))).toEqual([
expected,
]);
});
});
@@ -39,26 +39,29 @@ function getFrameworkRegexes({
return regexes;
}
/**
* Transforms replace the full match. We use lookarounds so that the regex does not consume surrounding characters.
*/
function getTokenRegex(
variable: string,
format: "css" | "scss" | "less" | "js",
) {
switch (format) {
case "css":
return new RegExp(`(\\s|^)?(${variable})(?=\\s|$|[^\\w-])`, "gm");
return new RegExp(`(?<![\\w-])(${variable})(?![\\w-])`, "gm");
case "scss":
return new RegExp(
`(\\s|^)?(\\${translateToken(variable, "scss")})(?=\\s|$|[^\\w-])`,
`(?<![\\w-])(\\${translateToken(variable, "scss")})(?![\\w-])`,
"gm",
);
case "less":
return new RegExp(
`(\\s|^)?(${translateToken(variable, "less")})(?=\\s|$|[^\\w-])`,
`(?<![\\w-])(${translateToken(variable, "less")})(?![\\w-])`,
"gm",
);
case "js":
return new RegExp(
`(\\s|^)?(${translateToken(variable, "js")})(?=\\s|$|[^\\w-])`,
`(?<![\\w$])(${translateToken(variable, "js")})(?![\\w$])`,
"gm",
);
}
+5 -2
View File
@@ -4,7 +4,7 @@ import { runTooling } from "./run-tooling.js";
const program = new Command();
function v8TokensCommand() {
function v8TokensCommand({ skipGitCheck = false } = {}) {
program.allowExcessArguments();
program.name(`${chalk.blueBright(`npx @navikt/aksel v8-tokens`)}`);
@@ -24,7 +24,10 @@ function v8TokensCommand() {
program.parse();
const options = program.opts();
runTooling(options as Parameters<typeof runTooling>["0"], program);
return runTooling(
{ ...(options as Parameters<typeof runTooling>["0"]), skipGitCheck },
program,
);
}
export { v8TokensCommand };
@@ -31,6 +31,11 @@ type ToolingOptions = {
dryRun: boolean;
glob: string;
ext: string;
/**
* Set when the caller has already validated git (e.g. `codemod v8`),
* since earlier migrations in the same run leave the tree dirty.
*/
skipGitCheck?: boolean;
};
type CodeshiftOptions = {
@@ -90,6 +95,15 @@ export async function runTooling(
// Show initial status
const initialStatus = getStatus(filepaths);
// Only check git before the first write: later tasks see our own changes.
let gitChecked = !!options.skipGitCheck;
const ensureGitChecked = () => {
if (!gitChecked) {
validateGit(options, program);
gitChecked = true;
}
};
// Task execution loop
let task: TaskName = await getNextTask(initialStatus.status);
let currentStatus = initialStatus;
@@ -105,6 +119,7 @@ export async function runTooling(
program,
currentStatus,
() => getStatus(filepaths, "no-print"),
ensureGitChecked,
);
} catch (error) {
program.error(
@@ -117,8 +132,6 @@ export async function runTooling(
task = await getNextTask(currentStatus.status);
}
process.exit(0);
}
/**
@@ -131,6 +144,7 @@ async function executeTask(
program: Command,
statusStore: TokenStatus,
updateStatus: () => TokenStatus,
ensureGitChecked: () => void,
): Promise<TokenStatus> {
switch (task) {
case "status":
@@ -147,9 +161,7 @@ async function executeTask(
case "less-tokens":
case "js-tokens":
case "tailwind-tokens": {
if (!options.force) {
validateGit(options, program);
}
ensureGitChecked();
const scopedFiles = getScopedFilesForTask(
task,
filepaths,
@@ -179,9 +191,7 @@ async function executeTask(
"tailwind-tokens",
] as const;
if (!options.force) {
validateGit(options, program);
}
ensureGitChecked();
let currentStatus = statusStore;
const summaryData: {
@@ -0,0 +1,73 @@
import type { Command } from "commander";
import Enquirer from "enquirer";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { validateGit } from "../../validation";
import { TokenStatus } from "../config/TokenStatus";
import { runTooling } from "../run-tooling";
vi.mock("enquirer", () => ({ default: { prompt: vi.fn() } }));
vi.mock("fast-glob", () => ({ default: vi.fn(async () => []) }));
vi.mock("jscodeshift/src/Runner", () => ({
run: vi.fn(async () => ({ error: 0, ok: 0, nochange: 0, skip: 0 })),
}));
vi.mock("../../validation", () => ({ validateGit: vi.fn() }));
vi.mock("../tasks/status", () => ({
getStatus: vi.fn(() => new TokenStatus()),
}));
vi.mock("../tasks/print-remaining", () => ({ printRemaining: vi.fn() }));
const prompt = vi.mocked(Enquirer.prompt);
const program = { error: vi.fn() } as unknown as Command;
const baseOptions = { force: false, dryRun: false, glob: "**/*.css", ext: "" };
function selectTasks(...tasks: string[]) {
for (const task of [...tasks, "exit"]) {
prompt.mockResolvedValueOnce({ task });
}
}
describe("runTooling", () => {
beforeEach(() => {
vi.spyOn(console, "info").mockImplementation(() => {});
vi.spyOn(process, "exit").mockImplementation((code) => {
throw new Error(`process.exit(${code})`);
});
});
afterEach(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
});
test("returns instead of exiting the process when the user exits", async () => {
selectTasks();
await expect(runTooling(baseOptions, program)).resolves.toBeUndefined();
expect(process.exit).not.toHaveBeenCalled();
});
test("checks git once, even when several migrations run in a row", async () => {
selectTasks("css-tokens", "scss-tokens", "run-all-migrations");
await runTooling(baseOptions, program);
expect(validateGit).toHaveBeenCalledTimes(1);
expect(program.error).not.toHaveBeenCalled();
});
test("skips the git check when the caller already did it", async () => {
selectTasks("css-tokens", "run-all-migrations");
await runTooling({ ...baseOptions, skipGitCheck: true }, program);
expect(validateGit).not.toHaveBeenCalled();
});
test("does not check git for read-only tasks", async () => {
selectTasks("status", "print-remaining-tokens");
await runTooling(baseOptions, program);
expect(validateGit).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,85 @@
/* @ts-expect-error No avaliable types for testUtils */
import { applyTransform } from "jscodeshift/dist/testUtils";
import { describe, expect, test } from "vitest";
import * as jsTransform from "../v8-tokens-js";
import lessTransform from "../v8-tokens-less";
import scssTransform from "../v8-tokens-scss";
/*
* Fixture tests are compared after Prettier, which hides whitespace bugs.
* These tests compare the exact output.
*/
const runJs = (source: string) =>
applyTransform({ ...jsTransform, parser: "tsx" }, {}, { source });
const runStyle = (
transformer: typeof scssTransform | typeof lessTransform,
source: string,
) => transformer({ path: "test", source });
describe("v8-tokens-js keeps boundaries intact", () => {
const importLine = `import { ASurfaceDefault } from "@navikt/ds-tokens/dist/tokens";\n`;
const expectedImport = `import { BgDefault } from "@navikt/ds-tokens/js";\n`;
test.each([
["return ASurfaceDefault;", "return BgDefault;"],
["const a = ASurfaceDefault;", "const a = BgDefault;"],
["const a = [ASurfaceDefault];", "const a = [BgDefault];"],
["const a = {...ASurfaceDefault};", "const a = {...BgDefault};"],
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing token inside template literal
["const a = `${ASurfaceDefault}`;", "const a = `${BgDefault}`;"],
])("%s", (input, output) => {
expect(runJs(`${importLine}\n${input}`)).toBe(
`${expectedImport}\n${output}`,
);
});
test("does not replace inside longer identifiers", () => {
expect(
runJs(
`${importLine}\nconst a = [MyASurfaceDefault, ASurfaceDefault2, $ASurfaceDefault];`,
),
).toBe(
`${expectedImport}\nconst a = [MyASurfaceDefault, ASurfaceDefault2, $ASurfaceDefault];`,
);
});
test("replaces aliased imports without touching similar names", () => {
expect(
runJs(
`import { ASurfaceDefault as surf } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [surf, surface, mysurf];`,
),
).toBe(`${expectedImport}\nconst a = [BgDefault, surface, mysurf];`);
});
});
describe("v8-tokens-scss keeps whitespace", () => {
test.each([
["margin: 0 $a-spacing-4;", "margin: 0 $ax-space-16;"],
["color: $a-text-default;", "color: $ax-text-neutral;"],
["color:$a-text-default;", "color:$ax-text-neutral;"],
["$a-text-default", "$ax-text-neutral"],
["\n\t$a-text-default", "\n\t$ax-text-neutral"],
["color: common.$a-text-default;", "color: common.$ax-text-neutral;"],
[
"padding: $a-spacing-4 $a-spacing-4;",
"padding: $ax-space-16 $ax-space-16;",
],
])("%s", (input, output) => {
expect(runStyle(scssTransform, input)).toBe(output);
});
});
describe("v8-tokens-less keeps whitespace", () => {
test.each([
["margin: 0 @a-spacing-4;", "margin: 0 @ax-space-16;"],
["color: @a-text-default;", "color: @ax-text-neutral;"],
["color:@a-text-default;", "color:@ax-text-neutral;"],
[
"padding: @a-spacing-4 @a-spacing-4;",
"padding: @ax-space-16 @ax-space-16;",
],
])("%s", (input, output) => {
expect(runStyle(lessTransform, input)).toBe(output);
});
});
@@ -1,4 +1,5 @@
import type { API, FileInfo } from "jscodeshift";
import { createIdentifierRegex } from "../../utils/identifier-regex";
import { getImportSpecifier } from "../../utils/imports";
import { getLineTerminator } from "../../utils/lineterminator";
import moveAndRenameImport from "../../utils/packageImports";
@@ -63,8 +64,9 @@ export default function transformer(file: FileInfo, api: API) {
let code = root.toSource(getLineTerminator(src));
const rgx = new RegExp(`(\\s|^)?(${localName})(?=\\s|$|[^\\w-])`, "gm");
code = code.replace(rgx, jsToken);
if (localName) {
code = code.replace(createIdentifierRegex(localName), jsToken);
}
src = code;
root = j(code);
+8 -1
View File
@@ -43,7 +43,14 @@ export function validateGit(options: any, program: Command) {
try {
clean = isGitClean.sync(process.cwd());
} catch (err: any) {
if (err?.stderr && err.stderr.indexOf("Not a git repository") >= 0) {
if (err?.code === "ENOENT") {
program.error(
`${chalk.yellow(
"\nCould not find git, so we can't check for uncommitted changes.",
)}${"\nInstall git, or use the --force flag to override this safety check."}`,
);
}
if (/not a git repository/i.test(String(err?.stderr ?? ""))) {
clean = true;
}
}
+5 -4
View File
@@ -27,13 +27,14 @@ async function run() {
if (args[0] === "codemod") {
if (args.includes("v8-tokens")) {
v8TokensCommand();
return;
await v8TokensCommand();
process.exit(0);
}
codemodCommand((migration) => {
codemodCommand(async (migration) => {
if (migration === "v8-tokens") {
v8TokensCommand();
/* `codemod v8` has already checked git before running earlier migrations */
await v8TokensCommand({ skipGitCheck: true });
}
});
return;
+26
View File
@@ -1,5 +1,31 @@
# @navikt/ds-css
## 8.19.2
### Patch Changes
- Search: Icon more visible in forced-colors mode ([#5386](https://github.com/navikt/aksel/pull/5386))
## 8.19.1
### Patch Changes
- Accordion: `@media print` now auto-opens every Accordion ([#5363](https://github.com/navikt/aksel/pull/5363))
- ExpansionCard: `@media print` now auto-opens every ExpansionCard ([#5363](https://github.com/navikt/aksel/pull/5363))
- ReadMore: `@media print` now auto-opens every ReadMore ([#5363](https://github.com/navikt/aksel/pull/5363))
## 8.19.0
## 8.18.0
### Patch Changes
- ActionMenu: Faster and smoother open transition. ([#5296](https://github.com/navikt/aksel/pull/5296))
## 8.17.2
## 8.17.1
## 8.17.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "8.17.1",
"version": "8.19.2",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -32,12 +32,12 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^8.17.1",
"@navikt/ds-tokens": "^8.19.2",
"browserslist": "^4.28.7",
"esbuild": "^0.28.2",
"fast-glob": "3.3.3",
"lightningcss": "^1.33.0",
"tsx": "^4.23.1"
"tsx": "^4.23.15"
},
"browserslist": [
">= 0.2% in NO",
+17
View File
@@ -123,6 +123,11 @@
.aksel-accordion--indent > .aksel-accordion__item > .aksel-accordion__content {
box-shadow: -2px 0 0 0 var(--ax-border-neutral-subtleA);
@media print {
border-left: 1px solid black;
margin-left: calc(var(--ax-space-24) - 2px);
}
}
.aksel-accordion__item {
@@ -163,4 +168,16 @@
border-bottom: 1px solid var(--ax-border-neutral-subtleA);
}
}
@media print {
& > .aksel-accordion__content {
visibility: visible;
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
margin-block: var(--ax-space-8);
margin-block-end: var(--ax-space-24);
opacity: 1;
}
}
}
+4 -4
View File
@@ -21,13 +21,13 @@
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
transform-origin: var(--__axc-action-menu-content-transform-origin);
transition:
transform 150ms ease allow-discrete,
opacity 150ms ease allow-discrete;
transform 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94) allow-discrete,
opacity 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94) allow-discrete;
&[data-state="open"] {
@starting-style {
opacity: 0.8;
transform: scale3d(0.94, 0.94, 0.94);
opacity: 0.1;
transform: scale3d(0.95, 0.95, 0.95);
}
}
}
+15
View File
@@ -19,6 +19,10 @@
box-shadow: 0 0 0 1px var(--ax-border-default);
border-color: var(--ax-border-default);
}
@media print {
border-color: black;
}
}
/* ------------------------ ExpansionCard Small-size ------------------------ */
@@ -166,6 +170,17 @@
opacity: 1;
}
}
@media print {
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
visibility: visible;
padding-block: var(--__axc-expansioncard-padding-block);
& .aksel-expansioncard__content-inner {
opacity: 1;
}
}
}
.aksel-expansioncard__content-inner {
+11 -4
View File
@@ -35,7 +35,7 @@
color: var(--ax-text-neutral);
width: 100%;
min-height: 3rem;
display: inline-block;
display: block;
position: relative;
padding: var(--ax-space-8);
padding-right: var(--ax-space-40);
@@ -81,10 +81,11 @@
.aksel-combobox2__field-chevron {
position: absolute;
font-size: 1.5rem;
right: var(--ax-space-8);
top: 0;
margin-top: 0.75rem; /* We don't use dynamic centering because we don't want it to move if height changes */
font-size: 1.5rem;
pointer-events: none;
margin-top: 12px; /* We don't use dynamic centering because we don't want it to move if height changes */
color: var(--ax-text-neutral);
@media (forced-colors: active) {
@@ -92,7 +93,7 @@
}
.aksel-combobox2[data-size="small"] & {
margin-top: 4px;
margin-top: 0.25rem;
}
}
@@ -236,6 +237,12 @@
.aksel-combobox2__option:is([aria-selected="true"], [aria-selected="true"][data-virtual-focus="true"])
.aksel-listbox__option-inner {
background-color: var(--ax-bg-moderate-pressedA);
@media (forced-colors: active) {
background-color: SelectedItem;
color: SelectedItemText;
forced-color-adjust: none;
}
}
.aksel-combobox2__option-checkmark {
+87 -3
View File
@@ -19,6 +19,46 @@
}
}
/* Checkbox and Radio card */
.aksel-checkbox-card,
.aksel-radio-card {
width: 100%;
align-content: flex-start;
padding: var(--ax-space-16) var(--ax-space-20);
cursor: pointer;
background-color: var(--ax-bg-default);
border: 1px solid var(--ax-border-neutral-subtleA);
border-radius: var(--ax-radius-8);
&:focus-within::after,
&:is(.aksel-checkbox--small, .aksel-radio--small):focus-within::after {
inset: 0;
border-radius: calc(var(--ax-radius-8) - 1px);
outline-offset: 4px;
}
}
/* Checkbox and Radio card group */
.aksel-checkbox-card-group > .aksel-checkboxes,
.aksel-radio-card-group > .aksel-radio-buttons {
display: flex;
flex-direction: column;
gap: var(--ax-space-12);
> .aksel-hgrid:not(.aksel-hgrid-gap) {
gap: var(--ax-space-12);
}
}
.aksel-checkbox-card-group.aksel-checkbox-group--small > .aksel-checkboxes,
.aksel-radio-card-group.aksel-radio-group--small > .aksel-radio-buttons {
gap: var(--ax-space-8);
> .aksel-hgrid:not(.aksel-hgrid-gap) {
gap: var(--ax-space-8);
}
}
/* Small */
.aksel-checkbox--small,
.aksel-radio--small {
@@ -30,6 +70,11 @@
}
}
.aksel-checkbox-card.aksel-checkbox--small,
.aksel-radio-card.aksel-radio--small {
padding: var(--ax-space-12) var(--ax-space-16);
}
/* Label */
.aksel-checkbox__label,
.aksel-radio__label {
@@ -203,9 +248,38 @@
--__axc-radio-checkbox-marker-border: 7px;
}
/* Card border in hover and checked states */
.aksel-checkbox-card:is(
.aksel-checkbox--error:not(:where(.aksel-checkbox--disabled, .aksel-checkbox--readonly)),
:hover:not(:where(.aksel-checkbox--disabled, .aksel-checkbox--readonly, .aksel-checkbox--error)),
:has(.aksel-checkbox__input:checked):not(:where(.aksel-checkbox--disabled, .aksel-checkbox--readonly))
),
.aksel-radio-card:is(
.aksel-radio--error:not(:where(.aksel-radio--disabled, .aksel-radio--readonly)),
:hover:not(:where(.aksel-radio--disabled, .aksel-radio--readonly, .aksel-radio--error)),
:has(> .aksel-radio__input:checked):not(:where(.aksel-radio--disabled, .aksel-radio--readonly))
) {
border-color: var(--ax-border-default);
box-shadow: 0 0 0 1px var(--ax-border-default);
}
/* Card background in checked state */
.aksel-checkbox-card:has(.aksel-checkbox__input:checked):not(
:where(.aksel-checkbox--disabled, .aksel-checkbox--readonly, .aksel-checkbox--error)
),
.aksel-radio-card:has(> .aksel-radio__input:checked):not(
:where(.aksel-radio--disabled, .aksel-radio--readonly, .aksel-radio--error)
) {
background-color: var(--ax-bg-accent-moderateA);
}
/* Marker colors in unchecked hover state (unless disabled) */
.aksel-checkbox__input:hover:not(:disabled, :checked, :indeterminate),
.aksel-radio__input:hover:not(:disabled, :checked) {
.aksel-radio__input:hover:not(:disabled, :checked),
.aksel-checkbox-card:hover:not(:where(.aksel-checkbox--disabled, .aksel-checkbox--readonly))
.aksel-checkbox__input:not(:disabled, :checked, :indeterminate),
.aksel-radio-card:hover:not(:where(.aksel-radio--disabled, .aksel-radio--readonly))
> .aksel-radio__input:not(:disabled, :checked) {
border-color: var(--ax-border-strong);
background-color: var(--ax-bg-moderate-hoverA);
}
@@ -222,15 +296,25 @@
opacity: var(--ax-opacity-disabled);
}
/* Card background in readonly state */
.aksel-checkbox-card:where(.aksel-checkbox--readonly),
.aksel-radio-card:where(.aksel-radio--readonly) {
background-color: var(--ax-bg-neutral-moderate);
}
/* Cursor in disabled state on otherwise clickable elements */
.aksel-checkbox--disabled :where(.aksel-checkbox__input, .aksel-checkbox__label),
.aksel-radio--disabled > :where(.aksel-radio__input, .aksel-radio__label) {
.aksel-radio--disabled > :where(.aksel-radio__input, .aksel-radio__label),
.aksel-checkbox-card.aksel-checkbox--disabled,
.aksel-radio-card.aksel-radio--disabled {
cursor: not-allowed;
}
/* Cursor in readonly state on otherwise clickable elements */
.aksel-checkbox--readonly :where(.aksel-checkbox__input, .aksel-checkbox__label),
.aksel-radio--readonly > :where(.aksel-radio__input, .aksel-radio__label) {
.aksel-radio--readonly > :where(.aksel-radio__input, .aksel-radio__label),
.aksel-checkbox-card.aksel-checkbox--readonly,
.aksel-radio-card.aksel-radio--readonly {
cursor: default;
}
+4
View File
@@ -81,6 +81,10 @@
pointer-events: none;
font-size: 1.5rem;
@media (forced-colors: active) {
color: CanvasText;
}
.aksel-search--disabled & {
opacity: var(--ax-opacity-disabled);
}
+4
View File
@@ -49,5 +49,9 @@
outline: 2px solid var(--ax-border-focus);
outline-offset: -2px;
background-color: var(--ax-bg-neutral-moderate-hoverA);
@media (forced-colors: active) {
outline-color: Highlight;
}
}
}
+4
View File
@@ -86,6 +86,10 @@
.aksel-read-more[data-state="closed"] & {
display: none;
@media print {
display: block;
}
}
}
+12
View File
@@ -87,6 +87,18 @@
}
}
/* Modifier xsmall size */
.aksel-toggle-group--xsmall {
& .aksel-toggle-group__button {
padding: var(--ax-space-1) var(--ax-space-8);
min-height: 1.5rem;
}
& .aksel-toggle-group__button-inner > svg {
font-size: var(--ax-font-size-large);
}
}
/* High contrast */
@media (forced-colors: active) {
.aksel-toggle-group {
+64
View File
@@ -1,5 +1,69 @@
# @navikt/ds-react
## 8.19.2
### Patch Changes
- TextField: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Textarea: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Fieldset: Nested Fieldsets now inherits the parent's `disabled` state. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Button: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Switch: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Search.Button: `loading={true}` and `disabled={false}` now disable SearchButton. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Badge: No longer sets aria-hidden internally automatically. ([#5397](https://github.com/navikt/aksel/pull/5397))
- Updated dependencies []:
- @navikt/ds-tokens@8.19.2
- @navikt/aksel-icons@8.19.2
## 8.19.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@8.19.1
- @navikt/aksel-icons@8.19.1
## 8.19.0
### Patch Changes
- Updated dependencies [[`c5dd309`](https://github.com/navikt/aksel/commit/c5dd3097e51ef2a9f79964248398bd158660bd3b)]:
- @navikt/aksel-icons@8.19.0
- @navikt/ds-tokens@8.19.0
## 8.18.0
### Minor Changes
- Radio: Add RadioCard and RadioCardGroup components ([#5276](https://github.com/navikt/aksel/pull/5276))
### Patch Changes
- Badge, Lookup: now available outside of Preview scope ([#5344](https://github.com/navikt/aksel/pull/5344))
- DatePicker: Use without hook now correctly closes picker on date-selection ([#5327](https://github.com/navikt/aksel/pull/5327))
- ActionMenu: Menu is no longer modal, Page scroll and interaction outside the menu are no longer blocked while open. ([#5296](https://github.com/navikt/aksel/pull/5296))
- Updated dependencies []:
- @navikt/ds-tokens@8.18.0
- @navikt/aksel-icons@8.18.0
## 8.17.2
### Patch Changes
- Updated dependencies [[`9673de4`](https://github.com/navikt/aksel/commit/9673de4c264479dd8ca04897003532595acad255)]:
- @navikt/aksel-icons@8.17.2
- @navikt/ds-tokens@8.17.2
## 8.17.1
### Patch Changes
+56 -17
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "8.17.1",
"version": "8.19.2",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -68,22 +68,42 @@
},
"./PREVIEW/Lookup": {
"import": {
"types": "./esm/lookup/index.d.ts",
"default": "./esm/lookup/index.js"
"types": "./esm/lookup/index-deprecated.d.ts",
"default": "./esm/lookup/index-deprecated.js"
},
"require": {
"types": "./cjs/lookup/index.d.ts",
"default": "./cjs/lookup/index.js"
"types": "./cjs/lookup/index-deprecated.d.ts",
"default": "./cjs/lookup/index-deprecated.js"
}
},
"./PREVIEW/Badge": {
"import": {
"types": "./esm/badge/index.d.ts",
"default": "./esm/badge/index.js"
"types": "./esm/badge/index-deprecated.d.ts",
"default": "./esm/badge/index-deprecated.js"
},
"require": {
"types": "./cjs/badge/index.d.ts",
"default": "./cjs/badge/index.js"
"types": "./cjs/badge/index-deprecated.d.ts",
"default": "./cjs/badge/index-deprecated.js"
}
},
"./PREVIEW/RadioCard": {
"import": {
"types": "./esm/form/radio-card/index.d.ts",
"default": "./esm/form/radio-card/index.js"
},
"require": {
"types": "./cjs/form/radio-card/index.d.ts",
"default": "./cjs/form/radio-card/index.js"
}
},
"./PREVIEW/CheckboxCard": {
"import": {
"types": "./esm/form/checkbox-card/index.d.ts",
"default": "./esm/form/checkbox-card/index.js"
},
"require": {
"types": "./cjs/form/checkbox-card/index.d.ts",
"default": "./cjs/form/checkbox-card/index.js"
}
},
"./types/theme": {
@@ -719,6 +739,26 @@
"default": "./cjs/dialog/index.js"
}
},
"./Lookup": {
"import": {
"types": "./esm/lookup/index.d.ts",
"default": "./esm/lookup/index.js"
},
"require": {
"types": "./cjs/lookup/index.d.ts",
"default": "./cjs/lookup/index.js"
}
},
"./Badge": {
"import": {
"types": "./esm/badge/index.d.ts",
"default": "./esm/badge/index.js"
},
"require": {
"types": "./cjs/badge/index.d.ts",
"default": "./cjs/badge/index.js"
}
},
"./package.json": "./package.json",
"./Theme": {
"import": {
@@ -748,8 +788,8 @@
"dependencies": {
"@floating-ui/react": "0.27.20",
"@floating-ui/react-dom": "^2.1.9",
"@navikt/aksel-icons": "^8.17.1",
"@navikt/ds-tokens": "^8.17.1",
"@navikt/aksel-icons": "^8.19.2",
"@navikt/ds-tokens": "^8.19.2",
"date-fns": "^4.0.0",
"react-day-picker": "10.0.1"
},
@@ -757,23 +797,22 @@
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.5.2",
"@testing-library/user-event": "^14.6.7",
"@types/jscodeshift": "17.3.0",
"@types/node": "^24.10.9",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"aksel": "workspace:^",
"concurrently": "10.0.4",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"jscodeshift": "17.4.0",
"jsdom": "29.1.1",
"react": "19.2.8",
"react-dom": "19.2.8",
"react": "19.3.0",
"react-dom": "19.3.0",
"rimraf": "6.1.3",
"swr": "^2.4.2",
"tsc-alias": "1.9.1",
"tsx": "^4.23.1",
"tsc-alias": "1.9.7",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"vitest": "4.1.11"
},
@@ -1,10 +1,10 @@
import { describe, expect, test } from "vitest";
import { type ParsedMeta, parseMetaFiles } from "./parse-meta";
import { type ParsedMeta, readMetaFiles } from "./parse-meta";
import { validateMetas } from "./validate-meta";
describe("component metadata files", () => {
test("all meta files satisfy the metadata spec", () => {
const metas = parseMetaFiles();
const metas = readMetaFiles();
expect(validateMetas(metas)).toEqual([]);
});
});
+150 -87
View File
@@ -1,4 +1,5 @@
import fg from "fast-glob";
import { readFileSync } from "node:fs";
import path from "node:path";
import ts from "typescript";
import { META_GLOB, packageRoot, tsconfigPath } from "./paths";
@@ -34,6 +35,21 @@ interface ParsedMeta {
utils: ResolvedEntry[];
}
/**
* Syntax-only contents of one `*.meta.ts` file. `components`/`utils` hold the
* map labels, e.g. `"Accordion.Item"`, without resolving their declarations.
*/
interface MetaSummary extends Omit<ParsedMeta, "components" | "utils"> {
components: string[];
utils: string[];
}
/** Syntactic parts of a meta file, shared by the summary and full parsers. */
interface MetaSource extends Omit<ParsedMeta, "components" | "utils"> {
componentsMap: ts.ObjectLiteralExpression;
utilsMap: ts.ObjectLiteralExpression | undefined;
}
/**
* Extracts exported "metadata" object through AST
*/
@@ -146,14 +162,122 @@ function createTsProgram(metaFiles: string[]): ts.Program {
});
}
/**
* Returns the display label of a `components`/`utils` map entry, or
* `undefined` for entries that are neither `Name` nor `"Label": Name`.
*/
function getEntryLabel(entry: ts.ObjectLiteralElementLike) {
if (ts.isShorthandPropertyAssignment(entry)) {
return entry.name.text;
}
if (ts.isPropertyAssignment(entry) && ts.isIdentifier(entry.initializer)) {
return ts.isStringLiteral(entry.name)
? entry.name.text
: entry.name.getText();
}
return undefined;
}
/**
* Reads a `components`/`utils` map from the metadata object. Throws when the
* property exists but is not an object literal.
*/
function readEntryMap(
metadataNode: ts.ObjectLiteralExpression,
key: "components" | "utils",
relMeta: string,
) {
const prop = getObjectProperty(metadataNode, key);
if (!prop) {
return undefined;
}
if (
!ts.isPropertyAssignment(prop) ||
!ts.isObjectLiteralExpression(prop.initializer)
) {
throw new Error(`[${relMeta}] metadata.${key} must be an object literal.`);
}
return prop.initializer;
}
/**
* Reads the syntactic parts of a meta file. Needs no type information, so it
* works on a standalone `ts.SourceFile`.
*/
function readMetaSource(
sourceFile: ts.SourceFile,
metaFile: string,
): MetaSource {
const relMeta = path.relative(packageRoot, metaFile);
const metadataNode = findMetadataObject(sourceFile);
if (!metadataNode) {
throw new Error(`[${relMeta}] Could not find a "metadata" object.`);
}
const name = readStringLiteralProperty(metadataNode, "name");
if (!name) {
throw new Error(`[${relMeta}] metadata.name must be a string literal.`);
}
const componentsMap = readEntryMap(metadataNode, "components", relMeta);
if (!componentsMap) {
throw new Error(
`[${relMeta}] metadata.components must be an object literal.`,
);
}
return {
name,
dir: path.relative(packageRoot, path.dirname(metaFile)),
metaFile: relMeta,
keywords: readStringArrayProperty(metadataNode, "keywords"),
related: readStringArrayProperty(metadataNode, "related"),
componentsMap,
utilsMap: readEntryMap(metadataNode, "utils", relMeta),
};
}
function findMetaFiles() {
return fg.sync(META_GLOB, { cwd: packageRoot, absolute: true }).sort();
}
/**
* Reads every `*.meta.ts` file under the package without building a
* TypeScript program. Much faster than {@link parseMetaFiles}, but entries are
* not resolved to declarations. Use for spec validation only.
*/
function readMetaFiles(): MetaSummary[] {
const getLabels = (map: ts.ObjectLiteralExpression) =>
map.properties
.map(getEntryLabel)
.filter((label): label is string => Boolean(label));
return findMetaFiles().map((metaFile) => {
const sourceFile = ts.createSourceFile(
metaFile,
readFileSync(metaFile, "utf8"),
ts.ScriptTarget.Latest,
true,
);
const { componentsMap, utilsMap, ...meta } = readMetaSource(
sourceFile,
metaFile,
);
return {
...meta,
components: getLabels(componentsMap),
utils: utilsMap ? getLabels(utilsMap) : [],
};
});
}
/**
* Reads every `*.meta.ts` file under the package and resolves its
* `components`/`utils` identifiers to their declarations.
*/
function parseMetaFiles(): ParsedMeta[] {
const metaFiles = fg
.sync(META_GLOB, { cwd: packageRoot, absolute: true })
.sort();
const metaFiles = findMetaFiles();
if (metaFiles.length === 0) {
return [];
@@ -177,30 +301,19 @@ function parseMetaFiles(): ParsedMeta[] {
const entries: ResolvedEntry[] = [];
for (const entry of mapExpression.properties) {
let label: string | undefined;
let symbol: ts.Symbol | undefined;
if (ts.isShorthandPropertyAssignment(entry)) {
label = entry.name.text;
symbol = resolveAliasedSymbol(
typeChecker.getShorthandAssignmentValueSymbol(entry),
);
} else if (
ts.isPropertyAssignment(entry) &&
ts.isIdentifier(entry.initializer)
) {
label = ts.isStringLiteral(entry.name)
? entry.name.text
: entry.name.getText();
symbol = resolveAliasedSymbol(
typeChecker.getSymbolAtLocation(entry.initializer),
);
}
const label = getEntryLabel(entry);
if (!label) {
continue;
}
let symbol: ts.Symbol | undefined;
if (ts.isShorthandPropertyAssignment(entry)) {
symbol = typeChecker.getShorthandAssignmentValueSymbol(entry);
} else if (ts.isPropertyAssignment(entry)) {
symbol = typeChecker.getSymbolAtLocation(entry.initializer);
}
symbol = resolveAliasedSymbol(symbol);
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
if (!symbol || !declaration) {
throw new Error(
@@ -222,74 +335,24 @@ function parseMetaFiles(): ParsedMeta[] {
return entries;
}
/**
* Reads a `components`/`utils` map from the metadata object and resolves its
* entries. Throws when a required map is missing or either map is not an
* object literal.
*/
function resolveEntryMap(
metadataNode: ts.ObjectLiteralExpression,
key: "components" | "utils",
metaFile: string,
relMeta: string,
required: boolean,
): ResolvedEntry[] {
const prop = getObjectProperty(metadataNode, key);
if (!prop) {
if (required) {
throw new Error(
`[${relMeta}] metadata.${key} must be an object literal.`,
);
}
return [];
}
if (
!ts.isPropertyAssignment(prop) ||
!ts.isObjectLiteralExpression(prop.initializer)
) {
throw new Error(
`[${relMeta}] metadata.${key} must be an object literal.`,
);
}
return resolveEntries(prop.initializer, metaFile);
}
function parseMetaFile(metaFile: string): ParsedMeta {
const relMeta = path.relative(packageRoot, metaFile);
return metaFiles.map((metaFile) => {
const sourceFile = tsProgram.getSourceFile(metaFile);
if (!sourceFile) {
throw new Error(`Could not load meta file: ${relMeta}`);
throw new Error(
`Could not load meta file: ${path.relative(packageRoot, metaFile)}`,
);
}
const metadataNode = findMetadataObject(sourceFile);
if (!metadataNode) {
throw new Error(`[${relMeta}] Could not find a "metadata" object.`);
}
const name = readStringLiteralProperty(metadataNode, "name");
if (!name) {
throw new Error(`[${relMeta}] metadata.name must be a string literal.`);
}
const { componentsMap, utilsMap, ...meta } = readMetaSource(
sourceFile,
metaFile,
);
return {
name,
dir: path.relative(packageRoot, path.dirname(metaFile)),
metaFile: relMeta,
keywords: readStringArrayProperty(metadataNode, "keywords"),
related: readStringArrayProperty(metadataNode, "related"),
components: resolveEntryMap(
metadataNode,
"components",
metaFile,
relMeta,
true,
),
utils: resolveEntryMap(metadataNode, "utils", metaFile, relMeta, false),
...meta,
components: resolveEntries(componentsMap, metaFile),
utils: utilsMap ? resolveEntries(utilsMap, metaFile) : [],
};
}
return metaFiles.map(parseMetaFile);
});
}
export { parseMetaFiles };
export type { ResolvedEntry, ParsedMeta };
export { parseMetaFiles, readMetaFiles };
export type { ResolvedEntry, ParsedMeta, MetaSummary };
@@ -1,4 +1,4 @@
import type { ParsedMeta } from "./parse-meta";
import type { MetaSummary, ParsedMeta } from "./parse-meta";
/** Characters allowed in a Sanity document `_id`. */
const SANITY_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
@@ -9,7 +9,7 @@ const SANITY_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
* valid). Kept pure so it can be exercised both by the extractor (which throws
* on any error) and by the test-suite.
*/
function validateMetas(metas: ParsedMeta[]): string[] {
function validateMetas(metas: (ParsedMeta | MetaSummary)[]): string[] {
const errors: string[] = [];
const nameToMeta = new Map<string, string>();
@@ -1,5 +1,5 @@
import React, { useRef } from "react";
import { useModalContext } from "../../modal/Modal.context";
import { useModalContext } from "../../modal/root/ModalRoot.context";
import { useId } from "../../utils-external";
import {
Menu,
@@ -102,7 +102,7 @@ const ActionMenuRoot = ({
rootElement={rootElement}
size={size}
>
<Menu open={open} onOpenChange={setOpen} modal>
<Menu open={open} onOpenChange={setOpen} modal={false}>
{children}
</Menu>
</ActionMenuProvider>
+10 -10
View File
@@ -19,12 +19,12 @@ export default meta;
type Story = StoryObj<typeof Badge>;
const statuses: { color: AkselColor; label: string }[] = [
{ color: "danger", label: "Avslått" },
{ color: "success", label: "Innvilget" },
{ color: "info", label: "Under behandling" },
{ color: "warning", label: "Mangler dokumentasjon" },
{ color: "neutral", label: "Ikke startet" },
const statuses: AkselColor[] = [
"danger",
"success",
"info",
"warning",
"neutral",
];
export const Default: Story = {
@@ -37,8 +37,8 @@ export const Default: Story = {
export const Dot: Story = {
render: () => (
<HStack gap="space-16" align="center">
{statuses.map(({ color, label }) => (
<Badge key={color} data-color={color} aria-label={label} />
{statuses.map((color) => (
<Badge key={color} data-color={color} />
))}
</HStack>
),
@@ -58,13 +58,13 @@ export const Count: Story = {
export const Anchored: Story = {
render: () => (
<HStack gap="space-32" align="center">
<Badge data-color="danger" count={42}>
<Badge data-color="danger" count={42} aria-hidden>
<Button
icon={<InboxIcon aria-hidden />}
aria-label="Innboks, 42 nye meldinger"
/>
</Badge>
<Badge data-color="danger" aria-label="Nytt varsel">
<Badge data-color="danger" aria-hidden>
<Button icon={<InboxIcon aria-hidden />} aria-label="Innboks" />
</Badge>
</HStack>
+13 -20
View File
@@ -35,9 +35,8 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
* Use standalone for inline indicators/counts, or pass the element you want to
* anchor it to as `children` to pin the badge to one of its corners.
*
* Accessibility: A badge with no label is treated as decorative when it is a
* dot or anchored to another element. When anchoring to an interactive element,
* fold the state into that element's accessible name
* When anchoring to an interactive element,
* set `aria-hidden` on the badge and fold the count into that element's accessible name
* (e.g. `aria-label="Inbox, 42 new messages"`).
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/badge)
@@ -46,11 +45,11 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
* @example
* ```jsx
* // Standalone
* <Badge data-color="success" aria-label="Active" />
* <Badge data-color="success" />
* <Badge data-color="danger" count={42} />
*
* // Anchored to an element
* <Badge data-color="danger" count={42}>
* <Badge data-color="danger" count={42} aria-hidden>
* <Button icon={<InboxIcon />} aria-label="Inbox, 42 new messages" />
* </Badge>
* ```
@@ -61,10 +60,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
children,
className,
"data-color": color = "danger",
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledby,
role,
"aria-hidden": ariaHidden,
count,
maxCount = 99,
...rest
@@ -75,10 +70,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
count == null ? undefined : normalizeCount(count, 0);
const normalizedMaxCount = normalizeCount(maxCount, 99);
const isDot = normalizedCount == null;
const hasLabel =
Boolean(ariaLabel?.trim()) || Boolean(ariaLabelledby?.trim());
const isDecorative =
role == null && !hasLabel && (isDot || children != null);
const badge = (
<BodyShort
@@ -89,10 +80,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
weight="semibold"
data-color={color}
data-dot={isDot || undefined}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
role={role}
aria-hidden={ariaHidden ?? (isDecorative || undefined)}
className={cl("aksel-badge", className)}
>
{isDot
@@ -120,10 +107,16 @@ function normalizeCount(value: number, fallback: number) {
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
}
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Badge {
export type Props = BadgeProps;
}
// eslint-disable-next-line import/export
export { Badge };
/** @deprecated Badge is no longer in preview. Import it from `@navikt/ds-react` instead. */
const BadgeDeprecated = Badge;
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace BadgeDeprecated {
export type Props = BadgeProps;
}
export { Badge, BadgeDeprecated };
@@ -0,0 +1,2 @@
"use client";
export { BadgeDeprecated as Badge } from "./Badge";
+2 -2
View File
@@ -105,9 +105,9 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
className={cl(className, "aksel-button", `aksel-button--${size}`, {
"aksel-button--loading": loading,
"aksel-button--icon-only": !!icon && !children,
"aksel-button--disabled": disabled ?? loading,
"aksel-button--disabled": disabled || loading,
})}
disabled={(disabled ?? loading) ? true : undefined}
disabled={disabled || loading ? true : undefined}
>
{icon && iconPosition === "left" && (
<span className="aksel-button__icon">{icon}</span>
+1 -1
View File
@@ -3,7 +3,7 @@ import React from "react";
import { PersonIcon } from "@navikt/aksel-icons";
import { VStack } from "../primitives/stack";
import { renderStoriesForChromatic } from "../utils/renderStoriesForChromatic";
import Chat, { POSITIONS, SIZES, VARIANTS } from "./Chat";
import { Chat, POSITIONS, SIZES, VARIANTS } from "./root/ChatRoot";
export default {
title: "ds-react/Chat",
@@ -1,8 +1,8 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { Detail, Heading, type HeadingProps } from "../typography";
import { cl } from "../utils/helpers";
import { Detail, Heading, type HeadingProps } from "../../typography";
import { cl } from "../../utils/helpers";
export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
/**
* Bubble text.
*/
@@ -26,7 +26,7 @@ export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
toptextHeadingLevel?: Exclude<HeadingProps["level"], "1">;
}
const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
const ChatBubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
(
{
children,
@@ -65,4 +65,5 @@ const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
},
);
export default Bubble;
export { ChatBubble };
export type { ChatBubbleProps };
+2 -2
View File
@@ -1,3 +1,3 @@
"use client";
export { default as Chat, ChatBubble } from "./Chat";
export type { ChatProps, ChatBubbleProps } from "./Chat";
export { Chat, ChatBubble } from "./root/ChatRoot";
export type { ChatProps, ChatBubbleProps } from "./root/ChatRoot";
@@ -1,13 +1,13 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import type { AkselStatusColorRole } from "@navikt/ds-tokens/types";
import type { AkselColor } from "../types";
import { BodyLong, type HeadingProps } from "../typography";
import { cl } from "../utils/helpers";
import Bubble, { type ChatBubbleProps } from "./Bubble";
import type { AkselColor } from "../../types";
import { BodyLong, type HeadingProps } from "../../typography";
import { cl } from "../../utils/helpers";
import { ChatBubble, type ChatBubbleProps } from "../bubble/ChatBubble";
export const VARIANTS = ["subtle", "info", "neutral"] as const;
export const POSITIONS = ["left", "right"] as const;
export const SIZES = ["medium", "small"] as const;
const VARIANTS = ["subtle", "info", "neutral"] as const;
const POSITIONS = ["left", "right"] as const;
const SIZES = ["medium", "small"] as const;
interface ChatProps extends HTMLAttributes<HTMLDivElement> {
/**
@@ -62,33 +62,7 @@ interface ChatProps extends HTMLAttributes<HTMLDivElement> {
"data-color"?: Exclude<AkselColor, AkselStatusColorRole>;
}
interface ChatComponent extends React.ForwardRefExoticComponent<
ChatProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link ChatBubbleProps}
*/
Bubble: typeof Bubble;
}
/**
* A component for communicating a dialog between two or more parties.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/chat)
* @see 🏷️ {@link ChatProps}
*
* @example
* ```jsx
* <Chat avatar="A" name="Alice" timestamp="01.01.21 14:00">
* <Chat.Bubble>Hello!</Chat.Bubble>
* <Chat.Bubble>How can I help you?</Chat.Bubble>
* </Chat>
* <Chat avatar="B" name="Bob" timestamp="01.01.21 14:01" position="right">
* <Chat.Bubble>Hi there!</Chat.Bubble>
* </Chat>
* ```
*/
export const Chat = forwardRef<HTMLDivElement, ChatProps>(
const ChatRoot = forwardRef<HTMLDivElement, ChatProps>(
(
{
children,
@@ -143,7 +117,7 @@ export const Chat = forwardRef<HTMLDivElement, ChatProps>(
</div>
);
},
) as ChatComponent;
);
function variantToColor(variant: ChatProps["variant"]): AkselColor {
switch (variant) {
@@ -158,8 +132,29 @@ function variantToColor(variant: ChatProps["variant"]): AkselColor {
}
}
Chat.Bubble = Bubble;
/**
* A component for communicating a dialog between two or more parties.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/chat)
* @see 🏷️ {@link ChatProps}
*
* @example
* ```jsx
* <Chat avatar="A" name="Alice" timestamp="01.01.21 14:00">
* <Chat.Bubble>Hello!</Chat.Bubble>
* <Chat.Bubble>How can I help you?</Chat.Bubble>
* </Chat>
* <Chat avatar="B" name="Bob" timestamp="01.01.21 14:01" position="right">
* <Chat.Bubble>Hi there!</Chat.Bubble>
* </Chat>
* ```
*/
const Chat = Object.assign(ChatRoot, {
/**
* @see 🏷️ {@link ChatBubbleProps}
*/
Bubble: ChatBubble,
});
export default Chat;
export { Bubble as ChatBubble };
export { Chat, ChatBubble, VARIANTS, POSITIONS, SIZES };
export type { ChatProps, ChatBubbleProps };
+6 -6
View File
@@ -1,7 +1,7 @@
"use client";
export { default as Chips, type ChipsProps } from "./root/ChipsRoot";
export {
ChipsRemovable,
type ChipsRemovableProps,
} from "./removable/ChipsRemovable";
export { ChipsToggle, type ChipsToggleProps } from "./toggle/ChipsToggle";
export { Chips, ChipsRemovable, ChipsToggle } from "./root/ChipsRoot";
export type {
ChipsProps,
ChipsRemovableProps,
ChipsToggleProps,
} from "./root/ChipsRoot";
@@ -4,7 +4,7 @@ import type { AkselColor } from "../../types";
import { cl, composeEventHandlers } from "../../utils/helpers";
import { useI18n } from "../../utils/i18n/i18n.hooks";
export interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: string;
/**
* @deprecated Use `data-color` prop instead.
@@ -22,10 +22,7 @@ export interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButt
"data-color"?: AkselColor;
}
export const ChipsRemovable = forwardRef<
HTMLButtonElement,
ChipsRemovableProps
>(
const ChipsRemovable = forwardRef<HTMLButtonElement, ChipsRemovableProps>(
(
{
children,
@@ -76,4 +73,5 @@ function variantToColor(
}
}
export default ChipsRemovable;
export { ChipsRemovable };
export type { ChipsRemovableProps };
+39 -41
View File
@@ -1,7 +1,10 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { cl } from "../../utils/helpers";
import { ChipsRemovable } from "../removable/ChipsRemovable";
import { ChipsToggle } from "../toggle/ChipsToggle";
import {
ChipsRemovable,
type ChipsRemovableProps,
} from "../removable/ChipsRemovable";
import { ChipsToggle, type ChipsToggleProps } from "../toggle/ChipsToggle";
interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
children: React.ReactNode;
@@ -12,22 +15,24 @@ interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
size?: "medium" | "small";
}
interface ChipsComponent extends React.ForwardRefExoticComponent<
ChipsProps & React.RefAttributes<HTMLUListElement>
> {
/**
* Toggle between selected-states.
* @see 🏷️ {@link ToggleChipsProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Toggle: typeof ChipsToggle;
/**
* Remove filter or the likes on click.
* @see 🏷️ {@link RemovableChipsProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Removable: typeof ChipsRemovable;
}
const ChipsRoot = forwardRef<HTMLUListElement, ChipsProps>(
({ className, size = "medium", children, ...rest }, ref) => {
return (
<ul
{...rest}
ref={ref}
className={cl("aksel-chips", className, `aksel-chips--${size}`, {
"aksel-body-short aksel-body-short--small": size === "medium",
"aksel-detail aksel-detail--small": size === "small",
})}
>
{React.Children.map(children, (chip, index) => {
return <li key={chip?.toString() || index}>{chip}</li>;
})}
</ul>
);
},
);
/**
* A component that displays a list of items as chips.
@@ -56,27 +61,20 @@ interface ChipsComponent extends React.ForwardRefExoticComponent<
* </Chips>
* ```
*/
export const Chips = forwardRef<HTMLUListElement, ChipsProps>(
({ className, size = "medium", children, ...rest }, ref) => {
return (
<ul
{...rest}
ref={ref}
className={cl("aksel-chips", className, `aksel-chips--${size}`, {
"aksel-body-short aksel-body-short--small": size === "medium",
"aksel-detail aksel-detail--small": size === "small",
})}
>
{React.Children.map(children, (chip, index) => {
return <li key={chip?.toString() || index}>{chip}</li>;
})}
</ul>
);
},
) as ChipsComponent;
const Chips = Object.assign(ChipsRoot, {
/**
* Toggle between selected-states.
* @see 🏷️ {@link ChipsToggleProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Toggle: ChipsToggle,
/**
* Remove filter or the likes on click.
* @see 🏷️ {@link ChipsRemovableProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Removable: ChipsRemovable,
});
Chips.Toggle = ChipsToggle;
Chips.Removable = ChipsRemovable;
export default Chips;
export type { ChipsProps };
export { Chips, ChipsRemovable, ChipsToggle };
export type { ChipsProps, ChipsRemovableProps, ChipsToggleProps };
@@ -3,7 +3,7 @@ import type { AkselColor } from "../../types";
import type { OverridableComponent } from "../../utils-external";
import { cl } from "../../utils/helpers";
export interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: string;
/**
* Toggles aria-pressed and visual changes
@@ -26,68 +26,66 @@ export interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonE
checkmark?: boolean;
}
export const ChipsToggle: OverridableComponent<
ChipsToggleProps,
HTMLButtonElement
> = forwardRef(
(
{
className,
children,
selected,
variant,
checkmark = true,
as: Component = "button",
"data-color": color,
...rest
const ChipsToggle: OverridableComponent<ChipsToggleProps, HTMLButtonElement> =
forwardRef(
(
{
className,
children,
selected,
variant,
checkmark = true,
as: Component = "button",
"data-color": color,
...rest
},
ref,
) => {
return (
<Component
data-color={color ?? variantToColor(variant)}
{...rest}
ref={ref}
className={cl("aksel-chips__chip aksel-chips__toggle", className, {
"aksel-chips__toggle--with-checkmark": checkmark,
})}
aria-pressed={selected}
data-pressed={selected}
>
{checkmark && (
<svg
aria-hidden
className="aksel-chips__toggle-icon"
width="1.25em"
height="1.25em"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
>
{selected ? (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 18.125C14.4873 18.125 18.125 14.4873 18.125 10C18.125 5.51269 14.4873 1.875 10 1.875C5.51269 1.875 1.875 5.51269 1.875 10C1.875 14.4873 5.51269 18.125 10 18.125ZM14.128 7.72904C14.3695 7.44357 14.3339 7.01635 14.0485 6.7748C13.763 6.53326 13.3358 6.56886 13.0942 6.85432L8.60428 12.1606L6.41627 9.97263C6.15185 9.70822 5.72315 9.70822 5.45873 9.97263C5.19431 10.2371 5.19431 10.6658 5.45873 10.9302L8.16706 13.6385C8.30095 13.7724 8.48479 13.8441 8.67397 13.8362C8.86316 13.8284 9.0404 13.7416 9.16271 13.5971L14.128 7.72904Z"
fill="currentColor"
/>
) : (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3.125C6.20304 3.125 3.125 6.20304 3.125 10C3.125 13.797 6.20304 16.875 10 16.875C13.797 16.875 16.875 13.797 16.875 10C16.875 6.20304 13.797 3.125 10 3.125ZM1.875 10C1.875 5.51269 5.51269 1.875 10 1.875C14.4873 1.875 18.125 5.51269 18.125 10C18.125 14.4873 14.4873 18.125 10 18.125C5.51269 18.125 1.875 14.4873 1.875 10Z"
/* After removing old fallbacks, change to currentColor */
fill="var(--ax-text-default)"
/>
)}
</svg>
)}
<span className="aksel-chips__chip-text">{children}</span>
</Component>
);
},
ref,
) => {
return (
<Component
data-color={color ?? variantToColor(variant)}
{...rest}
ref={ref}
className={cl("aksel-chips__chip aksel-chips__toggle", className, {
"aksel-chips__toggle--with-checkmark": checkmark,
})}
aria-pressed={selected}
data-pressed={selected}
>
{checkmark && (
<svg
aria-hidden
className="aksel-chips__toggle-icon"
width="1.25em"
height="1.25em"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable={false}
>
{selected ? (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 18.125C14.4873 18.125 18.125 14.4873 18.125 10C18.125 5.51269 14.4873 1.875 10 1.875C5.51269 1.875 1.875 5.51269 1.875 10C1.875 14.4873 5.51269 18.125 10 18.125ZM14.128 7.72904C14.3695 7.44357 14.3339 7.01635 14.0485 6.7748C13.763 6.53326 13.3358 6.56886 13.0942 6.85432L8.60428 12.1606L6.41627 9.97263C6.15185 9.70822 5.72315 9.70822 5.45873 9.97263C5.19431 10.2371 5.19431 10.6658 5.45873 10.9302L8.16706 13.6385C8.30095 13.7724 8.48479 13.8441 8.67397 13.8362C8.86316 13.8284 9.0404 13.7416 9.16271 13.5971L14.128 7.72904Z"
fill="currentColor"
/>
) : (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3.125C6.20304 3.125 3.125 6.20304 3.125 10C3.125 13.797 6.20304 16.875 10 16.875C13.797 16.875 16.875 13.797 16.875 10C16.875 6.20304 13.797 3.125 10 3.125ZM1.875 10C1.875 5.51269 5.51269 1.875 10 1.875C14.4873 1.875 18.125 5.51269 18.125 10C18.125 14.4873 14.4873 18.125 10 18.125C5.51269 18.125 1.875 14.4873 1.875 10Z"
/* After removing old fallbacks, change to currentColor */
fill="var(--ax-text-default)"
/>
)}
</svg>
)}
<span className="aksel-chips__chip-text">{children}</span>
</Component>
);
},
);
);
function variantToColor(
variant?: ChipsToggleProps["variant"],
@@ -102,4 +100,5 @@ function variantToColor(
}
}
export default ChipsToggle;
export { ChipsToggle };
export type { ChipsToggleProps };
@@ -221,12 +221,11 @@ function isFieldVisible(
return fields?.[key] !== false;
}
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGridPreferencesRoot {
export type Props = DataGridPreferencesProps;
}
export type { DataGridPreferencesProps };
// eslint-disable-next-line import/export
export { DataGridPreferencesRoot };
export default DataGridPreferencesRoot;
@@ -140,7 +140,7 @@ const DataGrid = DataGridInternal as unknown as DataGridComponent;
DataGrid.Table = DataGridTable;
DataGrid.Preferences = DataGridPreferences;
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGrid {
export type Props<T = unknown> = DataGridProps<T>;
export type Columns<T = unknown> = ColumnDefinitions<T>;
@@ -166,6 +166,5 @@ export namespace DataGrid {
}
}
// eslint-disable-next-line import/export
export { DataGrid, DataGridInternal }; // DataGridRoot needs to be exported b.c. of docgen
export default DataGrid;
@@ -553,7 +553,7 @@ const DataGridTable = DataGridTableInternal as <RowT>(
props: DataGridTableProps<RowT> & React.RefAttributes<HTMLTableElement>,
) => React.ReactElement | null;
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGridTable {
export type Props<T = unknown> = DataGridTableProps<T>;
export type Sorting = TableSortOptions;
@@ -566,7 +566,6 @@ export namespace DataGridTable {
}
// docgen doesn't work well with type params, so we let it use DataGridTableInternal instead
// eslint-disable-next-line import/export
export { DataGridTable, DataGridTableInternal };
export type { DataGridTableProps };
export default DataGridTable;
@@ -86,7 +86,9 @@ const AutoSuggest = forwardRef<HTMLInputElement, AutoSuggestProps>(
<Floating>
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Floating.Anchor>
<Listbox.InputSlot>
<Listbox.InputSlot
virtuallyFocusedOptionId={virtuallyFocusedOptionId}
>
<Search
label="Tabellsøk"
variant="simple"
@@ -3,7 +3,7 @@ import { isSameDay } from "date-fns";
import React, { useState } from "react";
import { Button } from "../button";
import { Dialog } from "../dialog";
import Modal from "../modal/Modal";
import { Modal } from "../modal";
import { Box } from "../primitives/box";
import { HGrid } from "../primitives/grid";
import { HStack, Spacer, VStack } from "../primitives/stack";
@@ -64,7 +64,7 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
onClose,
onOpenToggle,
strategy,
mode,
mode: _mode,
...rest
},
ref,
@@ -83,6 +83,8 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
value: _open,
});
const mode = _mode ?? "single";
const datePickerOpener = useRef<"from" | "to" | null>(null);
/* We use state here to insure that anchor is defined if open is true on initial render */
@@ -142,7 +144,7 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
}}
locale={locale}
translate={translate}
variant={mode ?? "single"}
variant={mode}
popoverProps={{
id: ariaId,
strategy,
@@ -1,5 +1,5 @@
import React, { forwardRef } from "react";
import { useModalContext } from "../../modal/Modal.context";
import { useModalContext } from "../../modal/root/ModalRoot.context";
import { Portal, type PortalProps } from "../../portal";
import { DialogBackdropInternal } from "../backdrop/DialogBackdropInternal";
import { useDialogContext } from "../root/DialogRoot.context";
@@ -1,10 +1,10 @@
import React, { forwardRef, useContext } from "react";
import { useThemeInternal } from "../theme/Theme";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { ExpansionCardContext } from "./context";
import { useThemeInternal } from "../../theme/Theme";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
@@ -44,4 +44,5 @@ const ExpansionCardContent = forwardRef<
);
});
export default ExpansionCardContent;
export { ExpansionCardContent };
export type { ExpansionCardContentProps };
@@ -1,13 +0,0 @@
import { createContext } from "react";
export type ExpansionCardContextProps = {
open: boolean;
toggleOpen: () => void;
size: "medium" | "small";
};
export const ExpansionCardContext = createContext<ExpansionCardContextProps>({
open: false,
toggleOpen: () => {},
size: "medium",
});
@@ -1,13 +1,13 @@
import React, { forwardRef, useContext } from "react";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { ExpansionCardContext } from "./context";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
children: React.ReactNode;
}
export const ExpansionCardDescription = forwardRef<
const ExpansionCardDescription = forwardRef<
HTMLParagraphElement,
ExpansionCardDescriptionProps
>(({ className, ...rest }, ref) => {
@@ -31,4 +31,5 @@ export const ExpansionCardDescription = forwardRef<
);
});
export default ExpansionCardDescription;
export { ExpansionCardDescription };
export type { ExpansionCardDescriptionProps };
@@ -1,10 +1,10 @@
import React, { forwardRef, useContext } from "react";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { cl } from "../utils/helpers";
import { useI18n } from "../utils/i18n/i18n.hooks";
import { ExpansionCardContext } from "./context";
import { cl } from "../../utils/helpers";
import { useI18n } from "../../utils/i18n/i18n.hooks";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
@@ -46,4 +46,5 @@ const ExpansionCardHeader = forwardRef<
);
});
export default ExpansionCardHeader;
export { ExpansionCardHeader };
export type { ExpansionCardHeaderProps };
@@ -1,9 +1,9 @@
"use client";
export {
type ExpansionCardProps,
default as ExpansionCard,
} from "./ExpansionCard";
export { default as ExpansionCardContent } from "./ExpansionCardContent";
export { default as ExpansionCardHeader } from "./ExpansionCardHeader";
export { default as ExpansionCardTitle } from "./ExpansionCardTitle";
export { default as ExpansionCardDescription } from "./ExpansionCardDescription";
ExpansionCard,
ExpansionCardContent,
ExpansionCardHeader,
ExpansionCardTitle,
ExpansionCardDescription,
} from "./root/ExpansionCardRoot";
export type { ExpansionCardProps } from "./root/ExpansionCardRoot";
@@ -0,0 +1,16 @@
import { createContext } from "react";
type ExpansionCardContextProps = {
open: boolean;
toggleOpen: () => void;
size: "medium" | "small";
};
const ExpansionCardContext = createContext<ExpansionCardContextProps>({
open: false,
toggleOpen: () => {},
size: "medium",
});
export { ExpansionCardContext };
export type { ExpansionCardContextProps };
@@ -1,51 +1,24 @@
import React, { forwardRef } from "react";
import type { AkselColor } from "../types";
import type { OverridableComponent } from "../utils-external";
import { cl } from "../utils/helpers";
import { useControllableState } from "../utils/hooks";
import ExpansionCardContent, {
import type { AkselColor } from "../../types";
import { cl } from "../../utils/helpers";
import { useControllableState } from "../../utils/hooks";
import {
ExpansionCardContent,
type ExpansionCardContentProps,
} from "./ExpansionCardContent";
} from "../content/ExpansionCardContent";
import {
ExpansionCardDescription,
type ExpansionCardDescriptionProps,
} from "./ExpansionCardDescription";
import ExpansionCardHeader, {
} from "../description/ExpansionCardDescription";
import {
ExpansionCardHeader,
type ExpansionCardHeaderProps,
} from "./ExpansionCardHeader";
} from "../header/ExpansionCardHeader";
import {
ExpansionCardTitle,
type ExpansionCardTitleProps,
} from "./ExpansionCardTitle";
import { ExpansionCardContext } from "./context";
interface ExpansionCardComponent extends React.ForwardRefExoticComponent<
ExpansionCardProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link ExpansionCardHeaderProps}
*/
Header: React.ForwardRefExoticComponent<
ExpansionCardHeaderProps & React.RefAttributes<HTMLDivElement>
>;
/**
* @see 🏷️ {@link ExpansionCardTitleProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Title: OverridableComponent<ExpansionCardTitleProps, HTMLHeadingElement>;
/**
* @see 🏷️ {@link ExpansionCardDescriptionProps}
*/
Description: React.ForwardRefExoticComponent<
ExpansionCardDescriptionProps & React.RefAttributes<HTMLParagraphElement>
>;
/**
* @see 🏷️ {@link ExpansionCardContentProps}
*/
Content: React.ForwardRefExoticComponent<
ExpansionCardContentProps & React.RefAttributes<HTMLDivElement>
>;
}
} from "../title/ExpansionCardTitle";
import { ExpansionCardContext } from "./ExpansionCardRoot.context";
interface ExpansionCardCommonProps extends Omit<
React.HTMLAttributes<HTMLDivElement>,
@@ -93,28 +66,10 @@ type ExpansionCardConditionalProps =
"aria-labelledby": string;
};
export type ExpansionCardProps = ExpansionCardCommonProps &
type ExpansionCardProps = ExpansionCardCommonProps &
ExpansionCardConditionalProps;
/**
* A component that displays an expandable card.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
* @see 🏷️ {@link ExpansionCardProps}
*
* @example
* ```jsx
* <ExpansionCard aria-label="Utbetaling av sykepenger">
* <ExpansionCard.Header>
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
* </ExpansionCard.Header>
* <ExpansionCard.Content>
* <Innhold />
* </ExpansionCard.Content>
* </ExpansionCard>
* ```
*/
export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
const ExpansionCardRoot = forwardRef<HTMLDivElement, ExpansionCardProps>(
(
{
className,
@@ -154,11 +109,57 @@ export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
</ExpansionCardContext.Provider>
);
},
) as ExpansionCardComponent;
);
ExpansionCard.Header = ExpansionCardHeader;
ExpansionCard.Content = ExpansionCardContent;
ExpansionCard.Title = ExpansionCardTitle;
ExpansionCard.Description = ExpansionCardDescription;
/**
* A component that displays an expandable card.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
* @see 🏷️ {@link ExpansionCardProps}
*
* @example
* ```jsx
* <ExpansionCard aria-label="Utbetaling av sykepenger">
* <ExpansionCard.Header>
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
* </ExpansionCard.Header>
* <ExpansionCard.Content>
* <Innhold />
* </ExpansionCard.Content>
* </ExpansionCard>
* ```
*/
const ExpansionCard = Object.assign(ExpansionCardRoot, {
/**
* @see 🏷️ {@link ExpansionCardHeaderProps}
*/
Header: ExpansionCardHeader,
/**
* @see 🏷️ {@link ExpansionCardTitleProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Title: ExpansionCardTitle,
/**
* @see 🏷️ {@link ExpansionCardDescriptionProps}
*/
Description: ExpansionCardDescription,
/**
* @see 🏷️ {@link ExpansionCardContentProps}
*/
Content: ExpansionCardContent,
});
export default ExpansionCard;
export {
ExpansionCard,
ExpansionCardHeader,
ExpansionCardTitle,
ExpansionCardDescription,
ExpansionCardContent,
};
export type {
ExpansionCardProps,
ExpansionCardHeaderProps,
ExpansionCardTitleProps,
ExpansionCardDescriptionProps,
ExpansionCardContentProps,
};
@@ -1,8 +1,8 @@
import React, { forwardRef } from "react";
import type { OverridableComponent } from "../utils-external";
import { cl } from "../utils/helpers";
import type { OverridableComponent } from "../../utils-external";
import { cl } from "../../utils/helpers";
export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
children: React.ReactNode;
/**
* Changes text-sizing
@@ -11,7 +11,7 @@ export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadin
size?: "large" | "medium" | "small";
}
export const ExpansionCardTitle: OverridableComponent<
const ExpansionCardTitle: OverridableComponent<
ExpansionCardTitleProps,
HTMLHeadingElement
> = forwardRef(
@@ -32,4 +32,5 @@ export const ExpansionCardTitle: OverridableComponent<
},
);
export default ExpansionCardTitle;
export { ExpansionCardTitle };
export type { ExpansionCardTitleProps };
@@ -107,6 +107,18 @@ function DisabledFormElements() {
<TextField label="Second field" />
</Fieldset>
<Fieldset legend="Nested Fieldset (outer disabled)" disabled>
<Fieldset legend="Inner fieldset">
<TextField label="Nested field" />
<Switch>Nested switch</Switch>
</Fieldset>
</Fieldset>
<Fieldset legend="Fieldset (readOnly)" readOnly>
<TextField label="TextField" defaultValue="Read-only value" />
<Textarea label="Textarea" defaultValue="Read-only value" />
</Fieldset>
<FileUpload.Dropzone
label="FileUpload Dropzone"
onSelect={console.log}
@@ -0,0 +1,14 @@
import type { ComponentMetadata } from "../../utils/types/metadata";
import { CheckboxCard, CheckboxCardGroup } from "./index";
const metadata: ComponentMetadata = {
name: "CheckboxCard",
components: {
CheckboxCard,
CheckboxCardGroup,
},
keywords: ["checkbox", "avkrysningsboks", "form", "input", "choice", "card"],
related: ["Checkbox"],
};
export { metadata };
@@ -0,0 +1,129 @@
import type { Meta } from "@storybook/react-vite";
import React from "react";
import { HGrid } from "../../primitives/grid";
import { renderStoriesForChromatic } from "../../utils/renderStoriesForChromatic";
import { CheckboxCard } from "./CheckboxCard";
import { CheckboxCardGroup } from "./CheckboxCardGroup";
const meta: Meta<typeof CheckboxCardGroup> = {
title: "ds-react/CheckboxCard",
component: CheckboxCardGroup,
subcomponents: { CheckboxCard },
parameters: {
chromatic: { disable: true },
},
};
export default meta;
export const Default = () => (
<CheckboxCardGroup
legend="Velg varslingskanaler"
description="Du kan velge flere alternativer."
>
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
</CheckboxCardGroup>
);
export const HorizontalLayout = () => (
<CheckboxCardGroup
legend="Velg varslingskanaler"
description="Du kan velge flere alternativer."
defaultValue={["sms"]}
size="small"
>
<HGrid columns={{ xs: 1, md: 3 }}>
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
<CheckboxCard
value="brev"
description="Sendes til folkeregistrert adresse."
>
Brev
</CheckboxCard>
</HGrid>
</CheckboxCardGroup>
);
export const Small = () => (
<CheckboxCardGroup legend="Velg varslingskanaler" size="small">
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
</CheckboxCardGroup>
);
export const ErrorProp = () => (
<CheckboxCardGroup
legend="Velg varslingskanaler"
error="Du må velge minst én varslingskanal."
defaultValue={["sms"]}
>
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
</CheckboxCardGroup>
);
export const Readonly = () => (
<CheckboxCardGroup
legend="Valgte varslingskanaler"
defaultValue={["sms"]}
readOnly
>
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
</CheckboxCardGroup>
);
export const Disabled = () => (
<CheckboxCardGroup
legend="Velg varslingskanaler"
defaultValue={["sms"]}
disabled
>
<CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
SMS
</CheckboxCard>
<CheckboxCard value="epost" description="Sendes til e-postadressen din.">
E-post
</CheckboxCard>
</CheckboxCardGroup>
);
export const Chromatic = renderStoriesForChromatic({
Default,
HorizontalLayout,
Small,
ErrorProp,
Readonly,
Disabled,
});
export const ChromaticDark = renderStoriesForChromatic({
Default,
HorizontalLayout,
Small,
ErrorProp,
Readonly,
Disabled,
});
ChromaticDark.globals = { theme: "dark" };
@@ -0,0 +1,41 @@
import React, { forwardRef } from "react";
import { cl } from "../../utils/helpers";
import { Checkbox, CheckboxCardContextProvider } from "../checkbox/Checkbox";
import type { CheckboxProps } from "../checkbox/types";
type CheckboxCardProps = CheckboxProps;
/**
* Checkbox option styled as a card.
* @see 🏷️ {@link CheckboxCardProps}
* @example
* ```tsx
* <CheckboxCardGroup legend="Velg varslingskanaler">
* <CheckboxCard value="sms" description="Sendes til mobilnummeret ditt.">
* SMS
* </CheckboxCard>
* <CheckboxCard value="epost">E-post</CheckboxCard>
* </CheckboxCardGroup>
* ```
*/
const CheckboxCard = forwardRef<HTMLInputElement, CheckboxCardProps>(
({ className, ...rest }, ref) => {
return (
<CheckboxCardContextProvider>
<Checkbox
{...rest}
ref={ref}
className={cl("aksel-checkbox-card", className)}
/>
</CheckboxCardContextProvider>
);
},
);
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CheckboxCard {
export type Props = CheckboxCardProps;
}
export { CheckboxCard };
export type { CheckboxCardProps };
@@ -0,0 +1,37 @@
import React, { forwardRef } from "react";
import { cl } from "../../utils/helpers";
import CheckboxGroup, {
type CheckboxGroupProps,
} from "../checkbox/CheckboxGroup";
type CheckboxCardGroupProps = CheckboxGroupProps;
/**
* CheckboxGroup for `<CheckboxCard />` elements. Horizontal layout is best suited for a small number of checkbox cards.
* @see 🏷️ {@link CheckboxCardGroupProps}
* @example
* ```tsx
* <CheckboxCardGroup legend="Velg varslingskanaler">
* <CheckboxCard value="sms">SMS</CheckboxCard>
* <CheckboxCard value="epost">E-post</CheckboxCard>
* </CheckboxCardGroup>
* ```
*/
const CheckboxCardGroup = forwardRef<
HTMLFieldSetElement,
CheckboxCardGroupProps
>(({ className, ...rest }, ref) => (
<CheckboxGroup
{...rest}
ref={ref}
className={cl("aksel-checkbox-card-group", className)}
/>
));
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CheckboxCardGroup {
export type Props = CheckboxCardGroupProps;
}
export { CheckboxCardGroup };
export type { CheckboxCardGroupProps };
@@ -0,0 +1,3 @@
"use client";
export { CheckboxCard } from "./CheckboxCard";
export { CheckboxCardGroup } from "./CheckboxCardGroup";
@@ -88,3 +88,22 @@ describe("Checkbox handles controlled-state correctly", () => {
);
});
});
describe("Checkbox outside CheckboxCard", () => {
test("does not toggle when clicking description", () => {
render(
<CheckboxGroup legend="legend">
<Checkbox value="value1" description="Checkbox description">
label1
</Checkbox>
</CheckboxGroup>,
);
fireEvent.click(screen.getByText("Checkbox description"));
expect(
screen.getByRole<HTMLInputElement>("checkbox", { name: "label1" })
.checked,
).toBe(false);
});
});
@@ -1,21 +1,33 @@
import React, { forwardRef } from "react";
import React, { forwardRef, useRef } from "react";
import { BodyShort } from "../../typography";
import { omit, useId } from "../../utils-external";
import { cl } from "../../utils/helpers";
import { cl, createStrictContext } from "../../utils/helpers";
import { useMergeRefs } from "../../utils/hooks";
import { ReadOnlyIconWithTitle } from "../ReadOnlyIcon";
import { CheckboxInput } from "./checkbox-input/CheckboxInput";
import type { CheckboxProps } from "./types";
import useCheckbox from "./useCheckbox";
const {
Provider: CheckboxCardContextProvider,
useContext: useCheckboxCardContext,
} = createStrictContext({ name: "checkbox-card" });
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
(props: CheckboxProps, forwardedRef) => {
const { inputProps, hasError, size, readOnly, nested } = useCheckbox(props);
const descriptionId = useId();
const cardContext = useCheckboxCardContext(false);
const checkboxRef = useRef<HTMLInputElement>(null);
const mergedRefs = useMergeRefs(forwardedRef, checkboxRef);
const { className, description, children, indeterminate, hideLabel } =
props;
return (
// biome-ignore lint/a11y/noStaticElementInteractions: clickable div for checkbox card
// biome-ignore lint/a11y/useKeyWithClickEvents: clickable div for checkbox card
<div
className={cl(className, "aksel-checkbox", `aksel-checkbox--${size}`, {
"aksel-checkbox--error": hasError,
@@ -23,9 +35,26 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
"aksel-checkbox--readonly": readOnly,
})}
data-color={hasError ? "danger" : props["data-color"]}
onClick={(event) => {
if (inputProps.disabled || readOnly || !cardContext) {
return;
}
/* Let input and label handle their own click events */
const target = event.target;
const nativeHandler =
target instanceof Element ? target.closest("label, input") : null;
if (nativeHandler && event.currentTarget.contains(nativeHandler)) {
return;
}
checkboxRef.current?.click();
checkboxRef.current?.focus({ preventScroll: true });
}}
>
<CheckboxInput
ref={forwardedRef}
ref={mergedRefs}
{...omit(props, [
"children",
"size",
@@ -72,3 +101,4 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
);
export default Checkbox;
export { CheckboxCardContextProvider };
Loaded 100 of 374 files, more files were not shown because too many files have changed in this diff. Show more