mirror of
https://github.com/navikt/aksel.git
synced 2026-10-20 20:52:04 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
388ffb6906 | ||
|
|
3fe9563078 | ||
|
|
62d2f5af01 | ||
|
|
53780d0c67 | ||
|
|
d476be6604 | ||
|
|
a0c9494b07 | ||
|
|
bf857007cd | ||
|
|
e6db21586e | ||
|
|
619ebd5088 | ||
|
|
1b539a9b13 | ||
|
|
8859bbb7ea |
No files matched your search
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
"@navikt/ds-css": patch
|
||||
---
|
||||
|
||||
ToggleGroup: Add xsmall as size
|
||||
@@ -49,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
|
||||
|
||||
|
||||
@@ -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.
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
yarn lint-staged
|
||||
# Runs the local lint-staged checks before committing
|
||||
./node_modules/.bin/lint-staged
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"author": "Aksel | Nav",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
@@ -82,7 +82,7 @@
|
||||
"copyfiles": "^2.4.1",
|
||||
"fast-glob": "3.3.3",
|
||||
"hast-util-select": "6.0.4",
|
||||
"js-yaml": "^5.4.3",
|
||||
"js-yaml": "^5.5.0",
|
||||
"lodash": "4.18.1",
|
||||
"prettier": "3.9.6",
|
||||
"react": "19.3.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
@@ -43,10 +43,10 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^8.19.2",
|
||||
"@navikt/ds-tokens": "^8.19.2",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"concurrently": "10.0.4",
|
||||
"postcss-selector-parser": "^7.1.6",
|
||||
"postcss-selector-parser": "^7.1.7",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"stylelint": "^17.15.0",
|
||||
"stylelint-test-rule-node": "^1.2.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Checkbox: Add `CheckboxCard` and `CheckboxCardGroup` components ([#5364](https://github.com/navikt/aksel/pull/5364))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ToggleGroup: Add xsmall as size ([#5411](https://github.com/navikt/aksel/pull/5411))
|
||||
|
||||
## 8.19.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"description": "CSS for Nav Designsystem",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
@@ -32,7 +32,7 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^8.19.2",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"browserslist": "^4.28.7",
|
||||
"esbuild": "^0.28.2",
|
||||
"fast-glob": "3.3.3",
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio card */
|
||||
/* Checkbox and Radio card */
|
||||
.aksel-checkbox-card,
|
||||
.aksel-radio-card {
|
||||
width: 100%;
|
||||
align-content: flex-start;
|
||||
@@ -30,26 +31,30 @@
|
||||
border-radius: var(--ax-radius-8);
|
||||
|
||||
&:focus-within::after,
|
||||
&.aksel-radio--small: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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio card group */
|
||||
.aksel-radio-card-group {
|
||||
& > .aksel-radio-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* 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-hgrid:not(.aksel-hgrid-gap) {
|
||||
gap: var(--ax-space-12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.aksel-radio-group--small > .aksel-radio-buttons > .aksel-hgrid:not(.aksel-hgrid-gap) {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -65,14 +70,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.aksel-checkbox-card.aksel-checkbox--small,
|
||||
.aksel-radio-card.aksel-radio--small {
|
||||
padding: var(--ax-space-12) var(--ax-space-16);
|
||||
}
|
||||
|
||||
.aksel-radio-card-group.aksel-radio-group--small > .aksel-radio-buttons {
|
||||
gap: var(--ax-space-8);
|
||||
}
|
||||
|
||||
/* Label */
|
||||
.aksel-checkbox__label,
|
||||
.aksel-radio__label {
|
||||
@@ -246,7 +248,11 @@
|
||||
--__axc-radio-checkbox-marker-border: 7px;
|
||||
}
|
||||
|
||||
/* Radio card border in hover and checked states */
|
||||
/* Card border in hover and checked states */
|
||||
.aksel-checkbox-card:is(
|
||||
: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(
|
||||
: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))
|
||||
@@ -255,7 +261,10 @@
|
||||
box-shadow: 0 0 0 1px var(--ax-border-default);
|
||||
}
|
||||
|
||||
/* Radio card background in checked state */
|
||||
/* 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)
|
||||
) {
|
||||
@@ -265,6 +274,8 @@
|
||||
/* Marker colors in unchecked hover state (unless disabled) */
|
||||
.aksel-checkbox__input:hover:not(:disabled, :checked, :indeterminate),
|
||||
.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);
|
||||
@@ -283,11 +294,18 @@
|
||||
opacity: var(--ax-opacity-disabled);
|
||||
}
|
||||
|
||||
/* Radio card background in disabled and readonly states */
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Card marker colors in disabled + checked state */
|
||||
.aksel-checkbox-card.aksel-checkbox--disabled .aksel-checkbox__input:where(:checked, :indeterminate) {
|
||||
background-color: var(--ax-bg-neutral-strong);
|
||||
border-color: var(--ax-border-neutral-strong);
|
||||
}
|
||||
|
||||
.aksel-radio-card.aksel-radio--disabled > .aksel-radio__input:checked {
|
||||
border-color: var(--ax-border-neutral-strong);
|
||||
}
|
||||
@@ -295,6 +313,7 @@
|
||||
/* 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-checkbox-card.aksel-checkbox--disabled,
|
||||
.aksel-radio-card.aksel-radio--disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -302,6 +321,7 @@
|
||||
/* 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-checkbox-card.aksel-checkbox--readonly,
|
||||
.aksel-radio-card.aksel-radio--readonly {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Checkbox: Add `CheckboxCard` and `CheckboxCardGroup` components ([#5364](https://github.com/navikt/aksel/pull/5364))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW ([#5401](https://github.com/navikt/aksel/pull/5401))
|
||||
|
||||
- ToggleGroup: Add xsmall as size ([#5411](https://github.com/navikt/aksel/pull/5411))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@8.20.0
|
||||
- @navikt/aksel-icons@8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
||||
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
||||
"license": "MIT",
|
||||
@@ -96,6 +96,16 @@
|
||||
"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": {
|
||||
"types": "./esm/types/theme.d.ts"
|
||||
},
|
||||
@@ -778,8 +788,8 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.27.20",
|
||||
"@floating-ui/react-dom": "^2.1.9",
|
||||
"@navikt/aksel-icons": "^8.19.2",
|
||||
"@navikt/ds-tokens": "^8.19.2",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"date-fns": "^4.0.0",
|
||||
"react-day-picker": "10.0.1"
|
||||
},
|
||||
|
||||
+5
-6
@@ -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";
|
||||
|
||||
interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Bubble text.
|
||||
*/
|
||||
@@ -26,7 +26,7 @@ interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
toptextHeadingLevel?: Exclude<HeadingProps["level"], "1">;
|
||||
}
|
||||
|
||||
const ChatBubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -65,5 +65,4 @@ const ChatBubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export { ChatBubble };
|
||||
export type { ChatBubbleProps };
|
||||
export default Bubble;
|
||||
@@ -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 "./root/ChatRoot";
|
||||
import Chat, { POSITIONS, SIZES, VARIANTS } from "./Chat";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Chat",
|
||||
|
||||
+38
-33
@@ -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 { ChatBubble, type ChatBubbleProps } from "../bubble/ChatBubble";
|
||||
import type { AkselColor } from "../types";
|
||||
import { BodyLong, type HeadingProps } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import Bubble, { type ChatBubbleProps } from "./Bubble";
|
||||
|
||||
const VARIANTS = ["subtle", "info", "neutral"] as const;
|
||||
const POSITIONS = ["left", "right"] as const;
|
||||
const SIZES = ["medium", "small"] as const;
|
||||
export const VARIANTS = ["subtle", "info", "neutral"] as const;
|
||||
export const POSITIONS = ["left", "right"] as const;
|
||||
export const SIZES = ["medium", "small"] as const;
|
||||
|
||||
interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
@@ -62,7 +62,33 @@ interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
||||
"data-color"?: Exclude<AkselColor, AkselStatusColorRole>;
|
||||
}
|
||||
|
||||
const ChatRoot = forwardRef<HTMLDivElement, ChatProps>(
|
||||
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>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -117,7 +143,7 @@ const ChatRoot = forwardRef<HTMLDivElement, ChatProps>(
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
) as ChatComponent;
|
||||
|
||||
function variantToColor(variant: ChatProps["variant"]): AkselColor {
|
||||
switch (variant) {
|
||||
@@ -132,29 +158,8 @@ function variantToColor(variant: ChatProps["variant"]): AkselColor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
Chat.Bubble = Bubble;
|
||||
|
||||
export { Chat, ChatBubble, VARIANTS, POSITIONS, SIZES };
|
||||
export default Chat;
|
||||
export { Bubble as ChatBubble };
|
||||
export type { ChatProps, ChatBubbleProps };
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client";
|
||||
export { Chat, ChatBubble } from "./root/ChatRoot";
|
||||
export type { ChatProps, ChatBubbleProps } from "./root/ChatRoot";
|
||||
export { default as Chat, ChatBubble } from "./Chat";
|
||||
export type { ChatProps, ChatBubbleProps } from "./Chat";
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
export { Chips } from "./root/ChipsRoot";
|
||||
export { ChipsRemovable } from "./removable/ChipsRemovable";
|
||||
export { ChipsToggle } from "./toggle/ChipsToggle";
|
||||
export type { ChipsProps } from "./root/ChipsRoot";
|
||||
export type { ChipsRemovableProps } from "./removable/ChipsRemovable";
|
||||
export type { ChipsToggleProps } from "./toggle/ChipsToggle";
|
||||
export { default as Chips, type ChipsProps } from "./root/ChipsRoot";
|
||||
export {
|
||||
ChipsRemovable,
|
||||
type ChipsRemovableProps,
|
||||
} from "./removable/ChipsRemovable";
|
||||
export { ChipsToggle, type ChipsToggleProps } from "./toggle/ChipsToggle";
|
||||
@@ -4,7 +4,7 @@ import type { AkselColor } from "../../types";
|
||||
import { cl, composeEventHandlers } from "../../utils/helpers";
|
||||
import { useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
|
||||
interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
export interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* @deprecated Use `data-color` prop instead.
|
||||
@@ -22,7 +22,10 @@ interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonEleme
|
||||
"data-color"?: AkselColor;
|
||||
}
|
||||
|
||||
const ChipsRemovable = forwardRef<HTMLButtonElement, ChipsRemovableProps>(
|
||||
export const ChipsRemovable = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ChipsRemovableProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -73,5 +76,4 @@ function variantToColor(
|
||||
}
|
||||
}
|
||||
|
||||
export { ChipsRemovable };
|
||||
export type { ChipsRemovableProps };
|
||||
export default ChipsRemovable;
|
||||
@@ -12,24 +12,22 @@ interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
|
||||
size?: "medium" | "small";
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
},
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that displays a list of items as chips.
|
||||
@@ -58,20 +56,27 @@ const ChipsRoot = forwardRef<HTMLUListElement, ChipsProps>(
|
||||
* </Chips>
|
||||
* ```
|
||||
*/
|
||||
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,
|
||||
});
|
||||
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;
|
||||
|
||||
export { Chips };
|
||||
Chips.Toggle = ChipsToggle;
|
||||
Chips.Removable = ChipsRemovable;
|
||||
|
||||
export default Chips;
|
||||
export type { ChipsProps };
|
||||
@@ -3,7 +3,7 @@ import type { AkselColor } from "../../types";
|
||||
import type { OverridableComponent } from "../../utils-external";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
export interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* Toggles aria-pressed and visual changes
|
||||
@@ -26,66 +26,68 @@ interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
checkmark?: boolean;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
export 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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function variantToColor(
|
||||
variant?: ChipsToggleProps["variant"],
|
||||
@@ -100,5 +102,4 @@ function variantToColor(
|
||||
}
|
||||
}
|
||||
|
||||
export { ChipsToggle };
|
||||
export type { ChipsToggleProps };
|
||||
export default ChipsToggle;
|
||||
+65
-66
@@ -1,24 +1,51 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import type { AkselColor } from "../../types";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useControllableState } from "../../utils/hooks";
|
||||
import {
|
||||
ExpansionCardContent,
|
||||
import type { AkselColor } from "../types";
|
||||
import type { OverridableComponent } from "../utils-external";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { useControllableState } from "../utils/hooks";
|
||||
import ExpansionCardContent, {
|
||||
type ExpansionCardContentProps,
|
||||
} from "../content/ExpansionCardContent";
|
||||
} from "./ExpansionCardContent";
|
||||
import {
|
||||
ExpansionCardDescription,
|
||||
type ExpansionCardDescriptionProps,
|
||||
} from "../description/ExpansionCardDescription";
|
||||
import {
|
||||
ExpansionCardHeader,
|
||||
} from "./ExpansionCardDescription";
|
||||
import ExpansionCardHeader, {
|
||||
type ExpansionCardHeaderProps,
|
||||
} from "../header/ExpansionCardHeader";
|
||||
} from "./ExpansionCardHeader";
|
||||
import {
|
||||
ExpansionCardTitle,
|
||||
type ExpansionCardTitleProps,
|
||||
} from "../title/ExpansionCardTitle";
|
||||
import { ExpansionCardContext } from "./ExpansionCardRoot.context";
|
||||
} 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>
|
||||
>;
|
||||
}
|
||||
|
||||
interface ExpansionCardCommonProps extends Omit<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
@@ -66,10 +93,28 @@ type ExpansionCardConditionalProps =
|
||||
"aria-labelledby": string;
|
||||
};
|
||||
|
||||
type ExpansionCardProps = ExpansionCardCommonProps &
|
||||
export type ExpansionCardProps = ExpansionCardCommonProps &
|
||||
ExpansionCardConditionalProps;
|
||||
|
||||
const ExpansionCardRoot = forwardRef<HTMLDivElement, ExpansionCardProps>(
|
||||
/**
|
||||
* 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>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
@@ -109,57 +154,11 @@ const ExpansionCardRoot = forwardRef<HTMLDivElement, ExpansionCardProps>(
|
||||
</ExpansionCardContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
) as ExpansionCardComponent;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
ExpansionCard.Header = ExpansionCardHeader;
|
||||
ExpansionCard.Content = ExpansionCardContent;
|
||||
ExpansionCard.Title = ExpansionCardTitle;
|
||||
ExpansionCard.Description = ExpansionCardDescription;
|
||||
|
||||
export {
|
||||
ExpansionCard,
|
||||
ExpansionCardHeader,
|
||||
ExpansionCardTitle,
|
||||
ExpansionCardDescription,
|
||||
ExpansionCardContent,
|
||||
};
|
||||
export type {
|
||||
ExpansionCardProps,
|
||||
ExpansionCardHeaderProps,
|
||||
ExpansionCardTitleProps,
|
||||
ExpansionCardDescriptionProps,
|
||||
ExpansionCardContentProps,
|
||||
};
|
||||
export default ExpansionCard;
|
||||
+6
-7
@@ -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 "../root/ExpansionCardRoot.context";
|
||||
import { useThemeInternal } from "../theme/Theme";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
|
||||
interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -44,5 +44,4 @@ const ExpansionCardContent = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export { ExpansionCardContent };
|
||||
export type { ExpansionCardContentProps };
|
||||
export default ExpansionCardContent;
|
||||
+6
-7
@@ -1,13 +1,13 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
|
||||
interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
export interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ExpansionCardDescription = forwardRef<
|
||||
export const ExpansionCardDescription = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
ExpansionCardDescriptionProps
|
||||
>(({ className, ...rest }, ref) => {
|
||||
@@ -31,5 +31,4 @@ const ExpansionCardDescription = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export { ExpansionCardDescription };
|
||||
export type { ExpansionCardDescriptionProps };
|
||||
export default ExpansionCardDescription;
|
||||
+5
-6
@@ -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 "../root/ExpansionCardRoot.context";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { useI18n } from "../utils/i18n/i18n.hooks";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
|
||||
interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -46,5 +46,4 @@ const ExpansionCardHeader = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export { ExpansionCardHeader };
|
||||
export type { ExpansionCardHeaderProps };
|
||||
export default ExpansionCardHeader;
|
||||
+5
-6
@@ -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";
|
||||
|
||||
interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
||||
export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Changes text-sizing
|
||||
@@ -11,7 +11,7 @@ interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElemen
|
||||
size?: "large" | "medium" | "small";
|
||||
}
|
||||
|
||||
const ExpansionCardTitle: OverridableComponent<
|
||||
export const ExpansionCardTitle: OverridableComponent<
|
||||
ExpansionCardTitleProps,
|
||||
HTMLHeadingElement
|
||||
> = forwardRef(
|
||||
@@ -32,5 +32,4 @@ const ExpansionCardTitle: OverridableComponent<
|
||||
},
|
||||
);
|
||||
|
||||
export { ExpansionCardTitle };
|
||||
export type { ExpansionCardTitleProps };
|
||||
export default ExpansionCardTitle;
|
||||
@@ -0,0 +1,13 @@
|
||||
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,9 +1,9 @@
|
||||
"use client";
|
||||
export {
|
||||
ExpansionCard,
|
||||
ExpansionCardContent,
|
||||
ExpansionCardHeader,
|
||||
ExpansionCardTitle,
|
||||
ExpansionCardDescription,
|
||||
} from "./root/ExpansionCardRoot";
|
||||
export type { ExpansionCardProps } from "./root/ExpansionCardRoot";
|
||||
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";
|
||||
@@ -1,16 +0,0 @@
|
||||
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 };
|
||||
@@ -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 };
|
||||
@@ -11,8 +11,12 @@ type ComboboxFilterProps = Pick<
|
||||
|
||||
export const ComboboxFilter = (props: ComboboxFilterProps) => {
|
||||
const { size } = useComboboxRootContext();
|
||||
const { filterString, setFilterString, virtuallyFocusedOptionValue } =
|
||||
useComboboxPopupContext();
|
||||
const {
|
||||
filterString,
|
||||
setFilterString,
|
||||
virtuallyFocusedOptionValue,
|
||||
setVirtuallyFocusedOptionValue,
|
||||
} = useComboboxPopupContext();
|
||||
|
||||
return (
|
||||
<div className="aksel-combobox2__filter">
|
||||
@@ -30,6 +34,11 @@ export const ComboboxFilter = (props: ComboboxFilterProps) => {
|
||||
event.preventDefault(); // Prevents navigation with screen reader
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (["ArrowLeft", "ArrowRight"].includes(event.key)) {
|
||||
setVirtuallyFocusedOptionValue("");
|
||||
}
|
||||
}}
|
||||
aria-autocomplete="list"
|
||||
inputMode="text"
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useComboboxRootContext } from "../root/ComboboxRoot";
|
||||
|
||||
interface ComboboxPopupContextProps {
|
||||
virtuallyFocusedOptionValue: string;
|
||||
setVirtuallyFocusedOptionValue: (value: string) => void;
|
||||
filterString: string;
|
||||
setFilterString: (value: string) => void;
|
||||
setAnnounceText: (text: string) => void;
|
||||
@@ -82,8 +83,12 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
|
||||
>
|
||||
<ComboboxPopupContextProvider
|
||||
virtuallyFocusedOptionValue={virtuallyFocusedOptionValue}
|
||||
setVirtuallyFocusedOptionValue={setVirtuallyFocusedOptionValue}
|
||||
filterString={filterString}
|
||||
setFilterString={setFilterString}
|
||||
setFilterString={(newString) => {
|
||||
setFilterString(newString);
|
||||
setVirtuallyFocusedOptionValue("");
|
||||
}}
|
||||
setAnnounceText={setAnnounceText}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
LinkCardIcon,
|
||||
LinkCardImage,
|
||||
LinkCardTitle,
|
||||
} from "./index";
|
||||
} from "./LinkCard";
|
||||
|
||||
type Story = StoryObj<typeof LinkCard>;
|
||||
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import type { AkselColor } from "../types";
|
||||
import { BodyLong, Heading } from "../typography";
|
||||
import {
|
||||
LinkAnchor,
|
||||
LinkAnchorArrow,
|
||||
LinkAnchorOverlay,
|
||||
type LinkAnchorProps,
|
||||
} from "../utils/components/link-anchor";
|
||||
import { cl, createStrictContext } from "../utils/helpers";
|
||||
|
||||
/* ------------------------------ LinkCard Root ----------------------------- */
|
||||
interface LinkCardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* @default true
|
||||
*/
|
||||
arrow?: boolean;
|
||||
/**
|
||||
* Adjusts arrow position.
|
||||
* @default "baseline"
|
||||
*/
|
||||
arrowPosition?: "baseline" | "center";
|
||||
/**
|
||||
* Changes padding and typo sizes.
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "small" | "medium";
|
||||
/**
|
||||
* Overrides color.
|
||||
*
|
||||
* We recommend avoiding status-colors (`info`, `success`, `warning`, `danger`) in LinkCards.
|
||||
* @default "neutral"
|
||||
* @see 🏷️ {@link AkselColor}
|
||||
* @see [📝 Documentation](https://aksel.nav.no/grunnleggende/styling/farger-tokens)
|
||||
*/
|
||||
"data-color"?: AkselColor;
|
||||
/**
|
||||
* Changes the HTML element used for the root element.
|
||||
*
|
||||
* **When using `section`, provide either `aria-label` or `aria-labelledby` for better accessibility.**
|
||||
* `axe-core` might warn about unique landmarks if you have multiple Accordions on page with the same label.
|
||||
* In those cases consider updating to unique `aria-label` or `aria-labelledby` props.
|
||||
* @see [📝 Landmarks unique](https://dequeuniversity.com/rules/axe/4.6/landmark-unique)
|
||||
*
|
||||
*
|
||||
* **When using `article`, make sure `<LinkCard.Title />` is a heading and not a `span`.**
|
||||
* @default "div"
|
||||
*/
|
||||
as?: "div" | "section" | "article";
|
||||
}
|
||||
|
||||
type LinkCardContextProps = {
|
||||
size: LinkCardProps["size"];
|
||||
};
|
||||
|
||||
const { Provider: LinkCardContextProvider, useContext: useLinkCardContext } =
|
||||
createStrictContext<LinkCardContextProps>({
|
||||
name: "LinkCardContextProvider",
|
||||
});
|
||||
|
||||
interface LinkCardComponent extends React.ForwardRefExoticComponent<
|
||||
LinkCardProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardTitleProps}
|
||||
*/
|
||||
Title: typeof LinkCardTitle;
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardAnchorProps}
|
||||
*/
|
||||
Anchor: typeof LinkCardAnchor;
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardDescriptionProps}
|
||||
*/
|
||||
Description: typeof LinkCardDescription;
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardFooterProps}
|
||||
*/
|
||||
Footer: typeof LinkCardFooter;
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardIconProps}
|
||||
*/
|
||||
Icon: typeof LinkCardIcon;
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardImageProps}
|
||||
*/
|
||||
Image: typeof LinkCardImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessible clickable card as a link.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/linkcard)
|
||||
* @see 🏷️ {@link LinkCardProps}
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <LinkCard>
|
||||
* <LinkCard.Icon>
|
||||
* <IconOrPictogram />
|
||||
* </LinkCard.Icon>
|
||||
* <LinkCard.Title>
|
||||
* <LinkCard.Anchor href="/href">
|
||||
* LinkCard title
|
||||
* </LinkCard.Anchor>
|
||||
* </LinkCard.Title>
|
||||
* <LinkCard.Description>
|
||||
* This is a description of the link card.
|
||||
* </LinkCard.Description>
|
||||
* <LinkCard.Footer>Footer content</LinkCard.Footer>
|
||||
* </LinkCard>
|
||||
* ```
|
||||
*/
|
||||
export const LinkCard = forwardRef<HTMLDivElement, LinkCardProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
arrow = true,
|
||||
arrowPosition = "baseline",
|
||||
size = "medium",
|
||||
as: Component = "div",
|
||||
...restProps
|
||||
}: LinkCardProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<LinkCardContextProvider size={size}>
|
||||
<LinkAnchorOverlay asChild>
|
||||
<BodyLong
|
||||
as={Component}
|
||||
size={size}
|
||||
ref={forwardedRef}
|
||||
data-color="neutral"
|
||||
className={cl(
|
||||
"aksel-link-card",
|
||||
className,
|
||||
`aksel-link-card--${size}`,
|
||||
)}
|
||||
data-align-arrow={arrowPosition}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
{arrow && (
|
||||
<LinkAnchorArrow
|
||||
fontSize={size === "medium" ? "1.75rem" : "1.5rem"}
|
||||
className="aksel-link-card__arrow"
|
||||
/>
|
||||
)}
|
||||
</BodyLong>
|
||||
</LinkAnchorOverlay>
|
||||
</LinkCardContextProvider>
|
||||
);
|
||||
},
|
||||
) as LinkCardComponent;
|
||||
|
||||
/* ---------------------------- LinkCard Title ---------------------------- */
|
||||
type LinkCardTitleProps = HTMLAttributes<HTMLHeadingElement> & {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Heading tag. Use "span" if you want a non header defining card
|
||||
* (eg. you have a lot of them all at once, such as in a grid)
|
||||
* @default "span"
|
||||
*/
|
||||
as?: "span" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
};
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardTitleProps}
|
||||
*/
|
||||
export const LinkCardTitle = forwardRef<HTMLHeadingElement, LinkCardTitleProps>(
|
||||
(
|
||||
{ children, as = "span", className, ...restProps }: LinkCardTitleProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
const context = useLinkCardContext();
|
||||
|
||||
return (
|
||||
<Heading
|
||||
ref={forwardedRef}
|
||||
as={as}
|
||||
size={context.size === "medium" ? "small" : "xsmall"}
|
||||
className={cl("aksel-link-card__title", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/* ---------------------------- LinkCard Anchor ---------------------------- */
|
||||
type LinkCardAnchorProps = LinkAnchorProps;
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardAnchorProps}
|
||||
*/
|
||||
export const LinkCardAnchor = LinkAnchor;
|
||||
|
||||
/* ---------------------------- LinkCard Description ---------------------------- */
|
||||
interface LinkCardDescriptionProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardDescriptionProps}
|
||||
*/
|
||||
export const LinkCardDescription = forwardRef<
|
||||
HTMLDivElement,
|
||||
LinkCardDescriptionProps
|
||||
>(
|
||||
(
|
||||
{ children, className, ...restProps }: LinkCardDescriptionProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__description", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/* ---------------------------- LinkCard Footer ---------------------------- */
|
||||
interface LinkCardFooterProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardFooterProps}
|
||||
*/
|
||||
export const LinkCardFooter = forwardRef<HTMLDivElement, LinkCardFooterProps>(
|
||||
(
|
||||
{ children, className, ...restProps }: LinkCardFooterProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__footer", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/* ---------------------------- LinkCard Icon ---------------------------- */
|
||||
interface LinkCardIconProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardIconProps}
|
||||
*/
|
||||
export const LinkCardIcon = forwardRef<HTMLDivElement, LinkCardIconProps>(
|
||||
({ children, className, ...restProps }: LinkCardIconProps, forwardedRef) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
aria-hidden
|
||||
className={cl("aksel-link-card__icon", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/* ---------------------------- LinkCard Image ---------------------------- */
|
||||
type ImageAspectRatio = "1/1" | "16/9" | "16/10" | "4/3" | (string & {});
|
||||
|
||||
interface LinkCardImageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
|
||||
* This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
|
||||
*/
|
||||
aspectRatio?: ImageAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardImageProps}
|
||||
*/
|
||||
export const LinkCardImage = forwardRef<HTMLDivElement, LinkCardImageProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
aspectRatio,
|
||||
style,
|
||||
...restProps
|
||||
}: LinkCardImageProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__image-container", className)}
|
||||
style={{
|
||||
...style,
|
||||
aspectRatio,
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
LinkCard.Title = LinkCardTitle;
|
||||
LinkCard.Anchor = LinkCardAnchor;
|
||||
LinkCard.Description = LinkCardDescription;
|
||||
LinkCard.Footer = LinkCardFooter;
|
||||
LinkCard.Icon = LinkCardIcon;
|
||||
LinkCard.Image = LinkCardImage;
|
||||
|
||||
export type {
|
||||
LinkCardAnchorProps,
|
||||
LinkCardDescriptionProps,
|
||||
LinkCardFooterProps,
|
||||
LinkCardIconProps,
|
||||
LinkCardImageProps,
|
||||
LinkCardProps,
|
||||
LinkCardTitleProps,
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import {
|
||||
LinkAnchor,
|
||||
type LinkAnchorProps,
|
||||
} from "../../utils/components/link-anchor";
|
||||
|
||||
type LinkCardAnchorProps = LinkAnchorProps;
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardAnchorProps}
|
||||
*/
|
||||
const LinkCardAnchor = LinkAnchor;
|
||||
|
||||
export { LinkCardAnchor };
|
||||
export type { LinkCardAnchorProps };
|
||||
@@ -1,32 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
interface LinkCardDescriptionProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardDescriptionProps}
|
||||
*/
|
||||
const LinkCardDescription = forwardRef<
|
||||
HTMLDivElement,
|
||||
LinkCardDescriptionProps
|
||||
>(
|
||||
(
|
||||
{ children, className, ...restProps }: LinkCardDescriptionProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__description", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { LinkCardDescription };
|
||||
export type { LinkCardDescriptionProps };
|
||||
@@ -1,29 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
interface LinkCardFooterProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardFooterProps}
|
||||
*/
|
||||
const LinkCardFooter = forwardRef<HTMLDivElement, LinkCardFooterProps>(
|
||||
(
|
||||
{ children, className, ...restProps }: LinkCardFooterProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__footer", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { LinkCardFooter };
|
||||
export type { LinkCardFooterProps };
|
||||
@@ -1,27 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
interface LinkCardIconProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardIconProps}
|
||||
*/
|
||||
const LinkCardIcon = forwardRef<HTMLDivElement, LinkCardIconProps>(
|
||||
({ children, className, ...restProps }: LinkCardIconProps, forwardedRef) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
aria-hidden
|
||||
className={cl("aksel-link-card__icon", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { LinkCardIcon };
|
||||
export type { LinkCardIconProps };
|
||||
@@ -1,46 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
type ImageAspectRatio = "1/1" | "16/9" | "16/10" | "4/3" | (string & {});
|
||||
|
||||
interface LinkCardImageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
|
||||
* This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
|
||||
*/
|
||||
aspectRatio?: ImageAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardImageProps}
|
||||
*/
|
||||
const LinkCardImage = forwardRef<HTMLDivElement, LinkCardImageProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
aspectRatio,
|
||||
style,
|
||||
...restProps
|
||||
}: LinkCardImageProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<div
|
||||
ref={forwardedRef}
|
||||
className={cl("aksel-link-card__image-container", className)}
|
||||
style={{
|
||||
...style,
|
||||
aspectRatio,
|
||||
}}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { LinkCardImage };
|
||||
export type { LinkCardImageProps };
|
||||
@@ -7,7 +7,7 @@ export {
|
||||
LinkCardAnchor,
|
||||
LinkCardIcon,
|
||||
LinkCardImage,
|
||||
} from "./root/LinkCardRoot";
|
||||
} from "./LinkCard";
|
||||
|
||||
export type {
|
||||
LinkCardProps,
|
||||
@@ -17,4 +17,4 @@ export type {
|
||||
LinkCardAnchorProps,
|
||||
LinkCardIconProps,
|
||||
LinkCardImageProps,
|
||||
} from "./root/LinkCardRoot";
|
||||
} from "./LinkCard";
|
||||
@@ -1,14 +0,0 @@
|
||||
import { createStrictContext } from "../../utils/helpers";
|
||||
import type { LinkCardProps } from "./LinkCardRoot";
|
||||
|
||||
type LinkCardContextProps = {
|
||||
size: LinkCardProps["size"];
|
||||
};
|
||||
|
||||
const { Provider: LinkCardContextProvider, useContext: useLinkCardContext } =
|
||||
createStrictContext<LinkCardContextProps>({
|
||||
name: "LinkCardContextProvider",
|
||||
});
|
||||
|
||||
export { LinkCardContextProvider, useLinkCardContext };
|
||||
export type { LinkCardContextProps };
|
||||
@@ -1,177 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import type { AkselColor } from "../../types";
|
||||
import { BodyLong } from "../../typography";
|
||||
import {
|
||||
LinkAnchorArrow,
|
||||
LinkAnchorOverlay,
|
||||
} from "../../utils/components/link-anchor";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import {
|
||||
LinkCardAnchor,
|
||||
type LinkCardAnchorProps,
|
||||
} from "../anchor/LinkCardAnchor";
|
||||
import {
|
||||
LinkCardDescription,
|
||||
type LinkCardDescriptionProps,
|
||||
} from "../description/LinkCardDescription";
|
||||
import {
|
||||
LinkCardFooter,
|
||||
type LinkCardFooterProps,
|
||||
} from "../footer/LinkCardFooter";
|
||||
import { LinkCardIcon, type LinkCardIconProps } from "../icon/LinkCardIcon";
|
||||
import { LinkCardImage, type LinkCardImageProps } from "../image/LinkCardImage";
|
||||
import { LinkCardTitle, type LinkCardTitleProps } from "../title/LinkCardTitle";
|
||||
import { LinkCardContextProvider } from "./LinkCardRoot.context";
|
||||
|
||||
interface LinkCardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* @default true
|
||||
*/
|
||||
arrow?: boolean;
|
||||
/**
|
||||
* Adjusts arrow position.
|
||||
* @default "baseline"
|
||||
*/
|
||||
arrowPosition?: "baseline" | "center";
|
||||
/**
|
||||
* Changes padding and typo sizes.
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "small" | "medium";
|
||||
/**
|
||||
* Overrides color.
|
||||
*
|
||||
* We recommend avoiding status-colors (`info`, `success`, `warning`, `danger`) in LinkCards.
|
||||
* @default "neutral"
|
||||
* @see 🏷️ {@link AkselColor}
|
||||
* @see [📝 Documentation](https://aksel.nav.no/grunnleggende/styling/farger-tokens)
|
||||
*/
|
||||
"data-color"?: AkselColor;
|
||||
/**
|
||||
* Changes the HTML element used for the root element.
|
||||
*
|
||||
* **When using `section`, provide either `aria-label` or `aria-labelledby` for better accessibility.**
|
||||
* `axe-core` might warn about unique landmarks if you have multiple Accordions on page with the same label.
|
||||
* In those cases consider updating to unique `aria-label` or `aria-labelledby` props.
|
||||
* @see [📝 Landmarks unique](https://dequeuniversity.com/rules/axe/4.6/landmark-unique)
|
||||
*
|
||||
*
|
||||
* **When using `article`, make sure `<LinkCard.Title />` is a heading and not a `span`.**
|
||||
* @default "div"
|
||||
*/
|
||||
as?: "div" | "section" | "article";
|
||||
}
|
||||
|
||||
const LinkCardRoot = forwardRef<HTMLDivElement, LinkCardProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
arrow = true,
|
||||
arrowPosition = "baseline",
|
||||
size = "medium",
|
||||
as: Component = "div",
|
||||
...restProps
|
||||
}: LinkCardProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
return (
|
||||
<LinkCardContextProvider size={size}>
|
||||
<LinkAnchorOverlay asChild>
|
||||
<BodyLong
|
||||
as={Component}
|
||||
size={size}
|
||||
ref={forwardedRef}
|
||||
data-color="neutral"
|
||||
className={cl(
|
||||
"aksel-link-card",
|
||||
className,
|
||||
`aksel-link-card--${size}`,
|
||||
)}
|
||||
data-align-arrow={arrowPosition}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
{arrow && (
|
||||
<LinkAnchorArrow
|
||||
fontSize={size === "medium" ? "1.75rem" : "1.5rem"}
|
||||
className="aksel-link-card__arrow"
|
||||
/>
|
||||
)}
|
||||
</BodyLong>
|
||||
</LinkAnchorOverlay>
|
||||
</LinkCardContextProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Accessible clickable card as a link.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/linkcard)
|
||||
* @see 🏷️ {@link LinkCardProps}
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <LinkCard>
|
||||
* <LinkCard.Icon>
|
||||
* <IconOrPictogram />
|
||||
* </LinkCard.Icon>
|
||||
* <LinkCard.Title>
|
||||
* <LinkCard.Anchor href="/href">
|
||||
* LinkCard title
|
||||
* </LinkCard.Anchor>
|
||||
* </LinkCard.Title>
|
||||
* <LinkCard.Description>
|
||||
* This is a description of the link card.
|
||||
* </LinkCard.Description>
|
||||
* <LinkCard.Footer>Footer content</LinkCard.Footer>
|
||||
* </LinkCard>
|
||||
* ```
|
||||
*/
|
||||
const LinkCard = Object.assign(LinkCardRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardTitleProps}
|
||||
*/
|
||||
Title: LinkCardTitle,
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardAnchorProps}
|
||||
*/
|
||||
Anchor: LinkCardAnchor,
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardDescriptionProps}
|
||||
*/
|
||||
Description: LinkCardDescription,
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardFooterProps}
|
||||
*/
|
||||
Footer: LinkCardFooter,
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardIconProps}
|
||||
*/
|
||||
Icon: LinkCardIcon,
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardImageProps}
|
||||
*/
|
||||
Image: LinkCardImage,
|
||||
});
|
||||
|
||||
export {
|
||||
LinkCard,
|
||||
LinkCardTitle,
|
||||
LinkCardDescription,
|
||||
LinkCardFooter,
|
||||
LinkCardAnchor,
|
||||
LinkCardIcon,
|
||||
LinkCardImage,
|
||||
};
|
||||
export type {
|
||||
LinkCardProps,
|
||||
LinkCardTitleProps,
|
||||
LinkCardDescriptionProps,
|
||||
LinkCardFooterProps,
|
||||
LinkCardAnchorProps,
|
||||
LinkCardIconProps,
|
||||
LinkCardImageProps,
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { Heading } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useLinkCardContext } from "../root/LinkCardRoot.context";
|
||||
|
||||
type LinkCardTitleProps = HTMLAttributes<HTMLHeadingElement> & {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Heading tag. Use "span" if you want a non header defining card
|
||||
* (eg. you have a lot of them all at once, such as in a grid)
|
||||
* @default "span"
|
||||
*/
|
||||
as?: "span" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
};
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardTitleProps}
|
||||
*/
|
||||
const LinkCardTitle = forwardRef<HTMLHeadingElement, LinkCardTitleProps>(
|
||||
(
|
||||
{ children, as = "span", className, ...restProps }: LinkCardTitleProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
const context = useLinkCardContext();
|
||||
|
||||
return (
|
||||
<Heading
|
||||
ref={forwardedRef}
|
||||
as={as}
|
||||
size={context.size === "medium" ? "small" : "xsmall"}
|
||||
className={cl("aksel-link-card__title", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export { LinkCardTitle };
|
||||
export type { LinkCardTitleProps };
|
||||
+7
-9
@@ -1,14 +1,14 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { consoleWarning } from "../../utils/helpers/consoleWarning";
|
||||
import type { ListItemProps } from "../List.types";
|
||||
import { ListContext } from "../root/ListRoot.context";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { consoleWarning } from "../utils/helpers/consoleWarning";
|
||||
import { ListContext } from "./List.context";
|
||||
import type { ListItemProps } from "./List.types";
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
({ className, children, title, icon, ...rest }, ref) => {
|
||||
const { listType, size } = useContext(ListContext);
|
||||
|
||||
@@ -57,6 +57,4 @@ const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
);
|
||||
|
||||
ListItem.displayName = "List.Item";
|
||||
|
||||
export { ListItem };
|
||||
export type { ListItemProps };
|
||||
export default ListItem;
|
||||
+2
-4
@@ -1,14 +1,12 @@
|
||||
import { createContext } from "react";
|
||||
import type { ListProps } from "../List.types";
|
||||
import type { ListProps } from "./List.types";
|
||||
|
||||
interface ListContextProps {
|
||||
listType: Exclude<ListProps["as"], undefined>;
|
||||
size: Exclude<ListProps["size"], undefined>;
|
||||
}
|
||||
|
||||
const ListContext = createContext<ListContextProps>({
|
||||
export const ListContext = createContext<ListContextProps>({
|
||||
listType: "ul",
|
||||
size: "medium",
|
||||
});
|
||||
|
||||
export { ListContext };
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { Box } from "../primitives/box";
|
||||
import { VStack } from "../primitives/stack";
|
||||
import { BodyLong } from "../typography";
|
||||
import { List } from "./index";
|
||||
import List from "./List";
|
||||
|
||||
export default {
|
||||
title: "ds-react/List",
|
||||
|
||||
+34
-29
@@ -1,11 +1,35 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import type { ListItemProps, ListProps } from "../List.types";
|
||||
import { ListItem } from "../item/ListItem";
|
||||
import { ListContext } from "./ListRoot.context";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ListItem } from "./List.Item";
|
||||
import { ListContext } from "./List.context";
|
||||
import type { ListItemProps, ListProps } from "./List.types";
|
||||
|
||||
const ListRoot = forwardRef<HTMLDivElement, ListProps>(
|
||||
interface ListComponent extends React.ForwardRefExoticComponent<
|
||||
ListProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
Item: typeof ListItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list component
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/list)
|
||||
* @see 🏷️ {@link ListProps | Props}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <List>
|
||||
* <List.Item>Coffee</List.Item>
|
||||
* <List.Item>Tea</List.Item>
|
||||
* <List.Item>Milk</List.Item>
|
||||
* </List>
|
||||
* ```
|
||||
*/
|
||||
export const List = forwardRef<HTMLDivElement, ListProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -47,29 +71,10 @@ const ListRoot = forwardRef<HTMLDivElement, ListProps>(
|
||||
</ListContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
) as ListComponent;
|
||||
|
||||
/**
|
||||
* A list component
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/list)
|
||||
* @see 🏷️ {@link ListProps | Props}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <List>
|
||||
* <List.Item>Coffee</List.Item>
|
||||
* <List.Item>Tea</List.Item>
|
||||
* <List.Item>Milk</List.Item>
|
||||
* </List>
|
||||
* ```
|
||||
*/
|
||||
const List = Object.assign(ListRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
Item: ListItem,
|
||||
});
|
||||
List.Item = ListItem;
|
||||
|
||||
export { List, ListItem };
|
||||
export default List;
|
||||
export { ListItem };
|
||||
export type { ListProps, ListItemProps };
|
||||
@@ -1,4 +1,4 @@
|
||||
interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* HTML list element to render.
|
||||
@@ -16,7 +16,7 @@ interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
size?: "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
/**
|
||||
* List item content.
|
||||
*/
|
||||
@@ -30,5 +30,3 @@ interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type { ListProps, ListItemProps };
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client";
|
||||
export { List, ListItem } from "./root/ListRoot";
|
||||
export type { ListProps, ListItemProps } from "./root/ListRoot";
|
||||
export { default as List, ListItem } from "./List";
|
||||
export type { ListProps, ListItemProps } from "./List";
|
||||
@@ -3,3 +3,4 @@ export { DataGrid } from "./data-grid";
|
||||
export { LookupDeprecated as Lookup } from "./lookup/Lookup";
|
||||
export { BadgeDeprecated as Badge } from "./badge/Badge";
|
||||
export { RadioCard, RadioCardGroup } from "./form/radio-card";
|
||||
export { CheckboxCard, CheckboxCardGroup } from "./form/checkbox-card";
|
||||
+3
-5
@@ -1,5 +1,5 @@
|
||||
import { createStrictContext } from "../../utils/helpers";
|
||||
import type { TabsProps } from "../Tabs.types";
|
||||
import { createStrictContext } from "../utils/helpers";
|
||||
import type { TabsProps } from "./Tabs.types";
|
||||
import type { useTabs } from "./useTabs";
|
||||
|
||||
type TabsProviderProps = ReturnType<typeof useTabs> &
|
||||
@@ -9,11 +9,9 @@ type TabsProviderProps = ReturnType<typeof useTabs> &
|
||||
>;
|
||||
|
||||
/* State context */
|
||||
const { Provider: TabsProvider, useContext: useTabsContext } =
|
||||
export const { Provider: TabsProvider, useContext: useTabsContext } =
|
||||
createStrictContext<TabsProviderProps>({
|
||||
name: "TabsContext",
|
||||
errorMessage:
|
||||
"Tabs.List, Tabs.Tag and Tabs.Panel needs to be wrapped within <Tabs>",
|
||||
});
|
||||
|
||||
export { TabsProvider, useTabsContext };
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { Tabs } from "./TabsRoot";
|
||||
import { Tabs } from "./Tabs";
|
||||
|
||||
const TestTabs = ({
|
||||
value,
|
||||
+63
-51
@@ -1,13 +1,57 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import type { TabsProps } from "../Tabs.types";
|
||||
import { TabsList, type TabsListProps } from "../list/TabsList";
|
||||
import { TabsPanel, type TabsPanelProps } from "../panel/TabsPanel";
|
||||
import { TabsTab, type TabsTabProps } from "../tab/TabsTab";
|
||||
import { TabsProvider } from "./TabsRoot.context";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { TabsProvider } from "./Tabs.context";
|
||||
import type { TabsProps } from "./Tabs.types";
|
||||
import Tab, { type TabProps } from "./parts/tab/Tab";
|
||||
import TabList, { type TabListProps } from "./parts/tablist/TabList";
|
||||
import TabPanel, { type TabPanelProps } from "./parts/tabpanel/TabPanel";
|
||||
import { useTabs } from "./useTabs";
|
||||
|
||||
const TabsRoot = forwardRef<HTMLDivElement, TabsProps>(
|
||||
interface TabsComponent extends React.ForwardRefExoticComponent<
|
||||
TabsProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link TabProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Tab: typeof Tab;
|
||||
/**
|
||||
* @see 🏷️ {@link TabListProps}
|
||||
*/
|
||||
List: typeof TabList;
|
||||
/**
|
||||
* @see 🏷️ {@link TabPanelProps}
|
||||
*/
|
||||
Panel: typeof TabPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that displays a set of tabs that can be used to switch between different views.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/tabs)
|
||||
* @see 🏷️ {@link TabsProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Tabs defaultValue="logg">
|
||||
* <Tabs.List>
|
||||
* <Tabs.Tab value="logg" label="Logg" />
|
||||
* <Tabs.Tab value="inbox" label="Inbox" />
|
||||
* <Tabs.Tab value="sendt" label="Sendt" />
|
||||
* </Tabs.List>
|
||||
* <Tabs.Panel value="logg">
|
||||
* Logg-tab
|
||||
* </Tabs.Panel>
|
||||
* <Tabs.Panel value="inbox">
|
||||
* Inbox-tab
|
||||
* </Tabs.Panel>
|
||||
* <Tabs.Panel value="sendt">
|
||||
* Sendt-tab
|
||||
* </Tabs.Panel>
|
||||
* </Tabs>
|
||||
* ```
|
||||
*/
|
||||
export const Tabs = forwardRef<HTMLDivElement, TabsProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
@@ -52,49 +96,17 @@ const TabsRoot = forwardRef<HTMLDivElement, TabsProps>(
|
||||
</TabsProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
) as TabsComponent;
|
||||
|
||||
/**
|
||||
* A component that displays a set of tabs that can be used to switch between different views.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/tabs)
|
||||
* @see 🏷️ {@link TabsProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Tabs defaultValue="logg">
|
||||
* <Tabs.List>
|
||||
* <Tabs.Tab value="logg" label="Logg" />
|
||||
* <Tabs.Tab value="inbox" label="Inbox" />
|
||||
* <Tabs.Tab value="sendt" label="Sendt" />
|
||||
* </Tabs.List>
|
||||
* <Tabs.Panel value="logg">
|
||||
* Logg-tab
|
||||
* </Tabs.Panel>
|
||||
* <Tabs.Panel value="inbox">
|
||||
* Inbox-tab
|
||||
* </Tabs.Panel>
|
||||
* <Tabs.Panel value="sendt">
|
||||
* Sendt-tab
|
||||
* </Tabs.Panel>
|
||||
* </Tabs>
|
||||
* ```
|
||||
*/
|
||||
const Tabs = Object.assign(TabsRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link TabsTabProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Tab: TabsTab,
|
||||
/**
|
||||
* @see 🏷️ {@link TabsListProps}
|
||||
*/
|
||||
List: TabsList,
|
||||
/**
|
||||
* @see 🏷️ {@link TabsPanelProps}
|
||||
*/
|
||||
Panel: TabsPanel,
|
||||
});
|
||||
Tabs.Tab = Tab;
|
||||
Tabs.List = TabList;
|
||||
Tabs.Panel = TabPanel;
|
||||
|
||||
export { Tabs, TabsTab, TabsList, TabsPanel };
|
||||
export type { TabsProps, TabsTabProps, TabsListProps, TabsPanelProps };
|
||||
export default Tabs;
|
||||
export { Tab as TabsTab, TabList as TabsList, TabPanel as TabsPanel };
|
||||
export type {
|
||||
TabsProps,
|
||||
TabProps as TabsTabProps,
|
||||
TabListProps as TabsListProps,
|
||||
TabPanelProps as TabsPanelProps,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HTMLAttributes } from "react";
|
||||
|
||||
interface TabsProps extends Omit<
|
||||
export interface TabsProps extends Omit<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
"onChange" | "dir"
|
||||
> {
|
||||
@@ -43,5 +43,3 @@ interface TabsProps extends Omit<
|
||||
*/
|
||||
fill?: boolean;
|
||||
}
|
||||
|
||||
export type { TabsProps };
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
export { Tabs, TabsTab, TabsList, TabsPanel } from "./root/TabsRoot";
|
||||
export { default as Tabs, TabsTab, TabsList, TabsPanel } from "./Tabs";
|
||||
export type {
|
||||
TabsProps,
|
||||
TabsTabProps,
|
||||
TabsListProps,
|
||||
TabsPanelProps,
|
||||
} from "./root/TabsRoot";
|
||||
} from "./Tabs";
|
||||
+7
-8
@@ -1,11 +1,11 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { BodyShort } from "../../typography";
|
||||
import type { OverridableComponent } from "../../utils-external";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useTabsContext } from "../root/TabsRoot.context";
|
||||
import { BodyShort } from "../../../typography";
|
||||
import type { OverridableComponent } from "../../../utils-external";
|
||||
import { cl } from "../../../utils/helpers";
|
||||
import { useTabsContext } from "../../Tabs.context";
|
||||
import { useTab } from "./useTab";
|
||||
|
||||
interface TabsTabProps extends Omit<
|
||||
export interface TabProps extends Omit<
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
"children"
|
||||
> {
|
||||
@@ -30,7 +30,7 @@ interface TabsTabProps extends Omit<
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const TabsTab: OverridableComponent<TabsTabProps, HTMLButtonElement> =
|
||||
export const Tab: OverridableComponent<TabProps, HTMLButtonElement> =
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
@@ -94,5 +94,4 @@ const TabsTab: OverridableComponent<TabsTabProps, HTMLButtonElement> =
|
||||
},
|
||||
);
|
||||
|
||||
export { TabsTab };
|
||||
export type { TabsTabProps };
|
||||
export default Tab;
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { composeEventHandlers } from "../../utils/helpers";
|
||||
import { useTabsContext } from "../root/TabsRoot.context";
|
||||
import { composeEventHandlers } from "../../../utils/helpers";
|
||||
import { useTabsContext } from "../../Tabs.context";
|
||||
|
||||
interface UseTabProps {
|
||||
export interface UseTabProps {
|
||||
id?: string;
|
||||
/**
|
||||
* If `true`, the `Tab` won't be toggleable
|
||||
@@ -56,4 +56,3 @@ function useTab({
|
||||
}
|
||||
|
||||
export { useTab };
|
||||
export type { UseTabProps };
|
||||
+4
-9
@@ -1,18 +1,14 @@
|
||||
import React from "react";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@navikt/aksel-icons";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { cl } from "../../../utils/helpers";
|
||||
|
||||
interface TabsScrollButtonInternalProps {
|
||||
interface ScrollButtonProps {
|
||||
hidden: boolean;
|
||||
onClick: () => void;
|
||||
dir: "left" | "right";
|
||||
}
|
||||
|
||||
function TabsScrollButtonInternal({
|
||||
hidden,
|
||||
onClick,
|
||||
dir,
|
||||
}: TabsScrollButtonInternalProps) {
|
||||
function ScrollButton({ hidden, onClick, dir }: ScrollButtonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cl("aksel-tabs__scroll-button", {
|
||||
@@ -26,5 +22,4 @@ function TabsScrollButtonInternal({
|
||||
);
|
||||
}
|
||||
|
||||
export { TabsScrollButtonInternal };
|
||||
export type { TabsScrollButtonInternalProps };
|
||||
export default ScrollButton;
|
||||
+8
-9
@@ -1,18 +1,18 @@
|
||||
import React, { forwardRef, useRef } from "react";
|
||||
import { cl, composeEventHandlers } from "../../utils/helpers";
|
||||
import { useMergeRefs } from "../../utils/hooks";
|
||||
import { TabsScrollButtonInternal } from "../scroll-button/TabsScrollButtonInternal";
|
||||
import { cl, composeEventHandlers } from "../../../utils/helpers";
|
||||
import { useMergeRefs } from "../../../utils/hooks";
|
||||
import ScrollButton from "./ScrollButtons";
|
||||
import { useScrollButtons } from "./useScrollButtons";
|
||||
import { useTabList } from "./useTabList";
|
||||
|
||||
interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface TabListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* <Tabs.Tab /> elements.
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
|
||||
export const TabList = forwardRef<HTMLDivElement, TabListProps>(
|
||||
({ className, onKeyDown, ...rest }, ref) => {
|
||||
const { onKeyDown: _onKeyDown } = useTabList();
|
||||
|
||||
@@ -24,7 +24,7 @@ const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
|
||||
return (
|
||||
<div className="aksel-tabs__tablist-wrapper">
|
||||
{scrollCtx.show && (
|
||||
<TabsScrollButtonInternal
|
||||
<ScrollButton
|
||||
dir="left"
|
||||
hidden={!scrollCtx.start}
|
||||
onClick={scrollCtx.scrollLeft}
|
||||
@@ -40,7 +40,7 @@ const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
|
||||
onKeyDown={composeEventHandlers(onKeyDown, _onKeyDown)}
|
||||
/>
|
||||
{scrollCtx.show && (
|
||||
<TabsScrollButtonInternal
|
||||
<ScrollButton
|
||||
dir="right"
|
||||
hidden={!scrollCtx.end}
|
||||
onClick={scrollCtx.scrollRight}
|
||||
@@ -51,5 +51,4 @@ const TabsList = forwardRef<HTMLDivElement, TabsListProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export { TabsList };
|
||||
export type { TabsListProps };
|
||||
export default TabList;
|
||||
+5
-5
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { debounce } from "../../utils-external";
|
||||
import { ownerDocument } from "../../utils/helpers";
|
||||
import { debounce } from "../../../utils-external";
|
||||
import { ownerDocument } from "../../../utils/helpers";
|
||||
|
||||
function useScrollButtons(listRef: React.RefObject<HTMLDivElement | null>) {
|
||||
export function useScrollButtons(
|
||||
listRef: React.RefObject<HTMLDivElement | null>,
|
||||
) {
|
||||
const [displayScroll, setDisplayScroll] = useState({
|
||||
start: false,
|
||||
end: false,
|
||||
@@ -68,5 +70,3 @@ function useScrollButtons(listRef: React.RefObject<HTMLDivElement | null>) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { useScrollButtons };
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { rovingFocus } from "../../utils/helpers/roving-focus";
|
||||
import { useTabsContext } from "../root/TabsRoot.context";
|
||||
import { rovingFocus } from "../../../utils/helpers/roving-focus";
|
||||
import { useTabsContext } from "../../Tabs.context";
|
||||
|
||||
const TAB_SELECTOR = "[data-aksel-tab]:not([data-disabled])";
|
||||
|
||||
@@ -8,7 +8,7 @@ const TAB_SELECTOR = "[data-aksel-tab]:not([data-disabled])";
|
||||
* TabList hook to manage multiple tab buttons,
|
||||
* and ensures only one tab is selected at a time.
|
||||
*/
|
||||
function useTabList() {
|
||||
export function useTabList() {
|
||||
const { loop, selectedValue, setFocusedValue } = useTabsContext();
|
||||
|
||||
/**
|
||||
@@ -52,5 +52,3 @@ function useTabList() {
|
||||
|
||||
return { onKeyDown };
|
||||
}
|
||||
|
||||
export { useTabList };
|
||||
+4
-5
@@ -1,8 +1,8 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { cl } from "../../../utils/helpers";
|
||||
import { useTabPanel } from "./useTabPanel";
|
||||
|
||||
interface TabsPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface TabPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Tab panel content.
|
||||
*/
|
||||
@@ -25,7 +25,7 @@ interface TabsPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const TabsPanel = forwardRef<HTMLDivElement, TabsPanelProps>(
|
||||
const TabPanel = forwardRef<HTMLDivElement, TabPanelProps>(
|
||||
({ className, value, children, lazy = true, id, ...rest }, ref) => {
|
||||
const ctx = useTabPanel({ value });
|
||||
|
||||
@@ -48,5 +48,4 @@ const TabsPanel = forwardRef<HTMLDivElement, TabsPanelProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export { TabsPanel };
|
||||
export type { TabsPanelProps };
|
||||
export default TabPanel;
|
||||
+3
-5
@@ -1,13 +1,13 @@
|
||||
import { useTabsContext } from "../root/TabsRoot.context";
|
||||
import { useTabsContext } from "../../Tabs.context";
|
||||
|
||||
type UseTabPanelProps = {
|
||||
type TabPanelProps = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabs hook for managing the visible/hidden state of Tabs.Panel
|
||||
*/
|
||||
function useTabPanel({ value }: UseTabPanelProps) {
|
||||
export function useTabPanel({ value }: TabPanelProps) {
|
||||
const { id, selectedValue, makeTabId, makeTabPanelId } = useTabsContext();
|
||||
|
||||
return {
|
||||
@@ -16,5 +16,3 @@ function useTabPanel({ value }: UseTabPanelProps) {
|
||||
id: makeTabPanelId(id, value),
|
||||
};
|
||||
}
|
||||
|
||||
export { useTabPanel };
|
||||
+4
-6
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useId } from "../../utils-external";
|
||||
import { useControllableState } from "../../utils/hooks";
|
||||
import type { TabsProps } from "../Tabs.types";
|
||||
import { useId } from "../utils-external";
|
||||
import { useControllableState } from "../utils/hooks";
|
||||
import type { TabsProps } from "./Tabs.types";
|
||||
|
||||
function useTabs({
|
||||
export function useTabs({
|
||||
onChange,
|
||||
value,
|
||||
defaultValue = "",
|
||||
@@ -45,5 +45,3 @@ function makeTabId(id: string, value: string) {
|
||||
function makeTabPanelId(id: string, value: string) {
|
||||
return `${id}--tabpanel-${value}`;
|
||||
}
|
||||
|
||||
export { useTabs };
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createContext as ReactCreateContext } from "react";
|
||||
import { createStrictContext } from "../utils/helpers";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
import type { useToggleGroup } from "./useToggleGroup";
|
||||
|
||||
interface ToggleContextProps {
|
||||
size: "medium" | "small" | "xsmall";
|
||||
}
|
||||
|
||||
export const ToggleGroupContext = ReactCreateContext<ToggleContextProps | null>(
|
||||
null,
|
||||
);
|
||||
type ToggleGroupProviderProps = ReturnType<typeof useToggleGroup> &
|
||||
Pick<ToggleGroupProps, "size">;
|
||||
|
||||
/* State context */
|
||||
export const {
|
||||
Provider: ToggleGroupProvider,
|
||||
useContext: useToggleGroupContext,
|
||||
} = createStrictContext<ToggleGroupProviderProps>({
|
||||
name: "ToggleGroupContext",
|
||||
errorMessage: "<ToggleGroup.Item> needs to be wrapped within <ToggleGroup>",
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import type { Meta, StoryFn, StoryObj } from "@storybook/react-vite";
|
||||
import React, { useState } from "react";
|
||||
import { expect, userEvent, within } from "storybook/test";
|
||||
import {
|
||||
EnvelopeClosedIcon,
|
||||
EnvelopeOpenIcon,
|
||||
@@ -8,8 +9,8 @@ import {
|
||||
} from "@navikt/aksel-icons";
|
||||
import { VStack } from "../primitives/stack";
|
||||
import { renderStoriesForChromatic } from "../utils/renderStoriesForChromatic";
|
||||
import ToggleGroup from "./ToggleGroup";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
import { ToggleGroup } from "./index";
|
||||
|
||||
const meta: Meta<typeof ToggleGroup> = {
|
||||
title: "ds-react/ToggleGroup",
|
||||
@@ -21,6 +22,8 @@ const meta: Meta<typeof ToggleGroup> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof ToggleGroup>;
|
||||
|
||||
const Items = (icon?: boolean, both?: boolean) => {
|
||||
const hasLabel = both || !icon;
|
||||
|
||||
@@ -170,6 +173,60 @@ export const XSmall = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const PlayControlled: Story = {
|
||||
render: () => {
|
||||
const [activeValue, setActiveValue] = useState("ny");
|
||||
|
||||
return (
|
||||
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const initialToggle = canvas.getByText("Ny").closest("button");
|
||||
|
||||
expect(initialToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(initialToggle).toHaveAttribute("tabindex", "0");
|
||||
initialToggle?.focus();
|
||||
expect(initialToggle).toHaveFocus();
|
||||
|
||||
await userEvent.keyboard("{ArrowRight}");
|
||||
const nextToggle = canvas.getByText("Leste").closest("button");
|
||||
expect(nextToggle).toHaveFocus();
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(nextToggle).toHaveAttribute("tabindex", "0");
|
||||
|
||||
expect(initialToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(initialToggle).toHaveAttribute("tabindex", "-1");
|
||||
|
||||
await userEvent.keyboard("{Enter}");
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "true");
|
||||
|
||||
await userEvent.keyboard("{ArrowLeft}");
|
||||
await userEvent.keyboard("{ArrowLeft}");
|
||||
const firstToggle = canvas.getByText("Uleste").closest("button");
|
||||
expect(firstToggle).toHaveFocus();
|
||||
expect(firstToggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(firstToggle).toHaveAttribute("tabindex", "0");
|
||||
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(nextToggle).toHaveAttribute("tabindex", "-1");
|
||||
},
|
||||
};
|
||||
|
||||
export const PlayUnControlled: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<ToggleGroup defaultValue="ny" onChange={() => null}>
|
||||
{Items()}
|
||||
</ToggleGroup>
|
||||
);
|
||||
},
|
||||
play: PlayControlled.play,
|
||||
};
|
||||
|
||||
export const Chromatic = renderStoriesForChromatic({
|
||||
Label,
|
||||
Compositions,
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { ToggleGroup } from "./ToggleGroupRoot";
|
||||
import { ToggleGroup } from "./ToggleGroup";
|
||||
|
||||
const TestToggleGroup = ({ value, onChange, defaultValue }: any) => (
|
||||
<ToggleGroup value={value} onChange={onChange} defaultValue={defaultValue}>
|
||||
@@ -1,77 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import React, { useState } from "react";
|
||||
import { expect, userEvent, within } from "storybook/test";
|
||||
import { ToggleGroup } from "./index";
|
||||
|
||||
const meta: Meta<typeof ToggleGroup> = {
|
||||
title: "ds-react/ToggleGroup/Tests",
|
||||
component: ToggleGroup,
|
||||
parameters: {
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof ToggleGroup>;
|
||||
|
||||
const Items = () => (
|
||||
<>
|
||||
<ToggleGroup.Item value="ulest" label="Uleste" />
|
||||
<ToggleGroup.Item value="ny" label="Ny" />
|
||||
<ToggleGroup.Item value="lest" label="Leste" />
|
||||
<ToggleGroup.Item value="sendt" label="Sendt" />
|
||||
</>
|
||||
);
|
||||
|
||||
export const PlayControlled: Story = {
|
||||
render: () => {
|
||||
const [activeValue, setActiveValue] = useState("ny");
|
||||
|
||||
return (
|
||||
<ToggleGroup value={activeValue} onChange={setActiveValue}>
|
||||
<Items />
|
||||
</ToggleGroup>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const initialToggle = canvas.getByRole("radio", { name: "Ny" });
|
||||
|
||||
expect(initialToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(initialToggle).toHaveAttribute("tabindex", "0");
|
||||
initialToggle.focus();
|
||||
expect(initialToggle).toHaveFocus();
|
||||
|
||||
await userEvent.keyboard("{ArrowRight}");
|
||||
const nextToggle = canvas.getByRole("radio", { name: "Leste" });
|
||||
expect(nextToggle).toHaveFocus();
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(nextToggle).toHaveAttribute("tabindex", "0");
|
||||
|
||||
expect(initialToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(initialToggle).toHaveAttribute("tabindex", "-1");
|
||||
|
||||
await userEvent.keyboard("{Enter}");
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "true");
|
||||
|
||||
await userEvent.keyboard("{ArrowLeft}");
|
||||
await userEvent.keyboard("{ArrowLeft}");
|
||||
const firstToggle = canvas.getByRole("radio", { name: "Uleste" });
|
||||
expect(firstToggle).toHaveFocus();
|
||||
expect(firstToggle).toHaveAttribute("aria-checked", "false");
|
||||
expect(firstToggle).toHaveAttribute("tabindex", "0");
|
||||
|
||||
expect(nextToggle).toHaveAttribute("aria-checked", "true");
|
||||
expect(nextToggle).toHaveAttribute("tabindex", "-1");
|
||||
},
|
||||
};
|
||||
|
||||
export const PlayUnControlled: Story = {
|
||||
render: () => (
|
||||
<ToggleGroup defaultValue="ny" onChange={() => null}>
|
||||
<Items />
|
||||
</ToggleGroup>
|
||||
),
|
||||
play: PlayControlled.play,
|
||||
};
|
||||
+36
-34
@@ -1,17 +1,38 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import type { AkselColor } from "../../types";
|
||||
import { Label } from "../../typography";
|
||||
import { useId } from "../../utils-external";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import type { ToggleGroupProps } from "../ToggleGroup.types";
|
||||
import {
|
||||
ToggleGroupItem,
|
||||
type ToggleGroupItemProps,
|
||||
} from "../item/ToggleGroupItem";
|
||||
import { ToggleGroupProvider } from "./ToggleGroupRoot.context";
|
||||
import type { AkselColor } from "../types";
|
||||
import { Label } from "../typography";
|
||||
import { useId } from "../utils-external";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ToggleGroupProvider } from "./ToggleGroup.context";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
import ToggleItem, { type ToggleGroupItemProps } from "./parts/ToggleItem";
|
||||
import { useToggleGroup } from "./useToggleGroup";
|
||||
|
||||
const ToggleGroupRoot = forwardRef<HTMLDivElement, ToggleGroupProps>(
|
||||
interface ToggleGroupComponent extends React.ForwardRefExoticComponent<
|
||||
ToggleGroupProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link ToggleGroupItemProps}
|
||||
*/
|
||||
Item: typeof ToggleItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that displays a group of toggle buttons.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/toggle-group)
|
||||
* @see 🏷️ {@link ToggleGroupProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ToggleGroup defaultValue="lest" onChange={console.log} size="small">
|
||||
* <ToggleGroup.Item value="ulest" label="Ulest" />
|
||||
* <ToggleGroup.Item value="lest" label="Leste" />
|
||||
* <ToggleGroup.Item value="sendt" label="Sendt" />
|
||||
* </ToggleGroup>
|
||||
* ```
|
||||
*/
|
||||
export const ToggleGroup = forwardRef<HTMLDivElement, ToggleGroupProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
@@ -86,7 +107,7 @@ const ToggleGroupRoot = forwardRef<HTMLDivElement, ToggleGroupProps>(
|
||||
</ToggleGroupProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
) as ToggleGroupComponent;
|
||||
|
||||
function variantToColor(
|
||||
variant?: ToggleGroupProps["variant"],
|
||||
@@ -101,27 +122,8 @@ function variantToColor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that displays a group of toggle buttons.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/toggle-group)
|
||||
* @see 🏷️ {@link ToggleGroupProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ToggleGroup defaultValue="lest" onChange={console.log} size="small">
|
||||
* <ToggleGroup.Item value="ulest" label="Ulest" />
|
||||
* <ToggleGroup.Item value="lest" label="Leste" />
|
||||
* <ToggleGroup.Item value="sendt" label="Sendt" />
|
||||
* </ToggleGroup>
|
||||
* ```
|
||||
*/
|
||||
const ToggleGroup = Object.assign(ToggleGroupRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link ToggleGroupItemProps}
|
||||
*/
|
||||
Item: ToggleGroupItem,
|
||||
});
|
||||
ToggleGroup.Item = ToggleItem;
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
export default ToggleGroup;
|
||||
export { ToggleItem as ToggleGroupItem };
|
||||
export type { ToggleGroupProps, ToggleGroupItemProps };
|
||||
@@ -2,7 +2,7 @@ import type { HTMLAttributes } from "react";
|
||||
import type { AkselStatusColorRole } from "@navikt/ds-tokens/types";
|
||||
import type { AkselColor } from "../types";
|
||||
|
||||
interface ToggleGroupProps extends Omit<
|
||||
export interface ToggleGroupProps extends Omit<
|
||||
HTMLAttributes<HTMLDivElement>,
|
||||
"onChange" | "dir"
|
||||
> {
|
||||
@@ -49,5 +49,3 @@ interface ToggleGroupProps extends Omit<
|
||||
*/
|
||||
fill?: boolean;
|
||||
}
|
||||
|
||||
export type { ToggleGroupProps };
|
||||
@@ -1,6 +1,3 @@
|
||||
"use client";
|
||||
export { ToggleGroup, ToggleGroupItem } from "./root/ToggleGroupRoot";
|
||||
export type {
|
||||
ToggleGroupProps,
|
||||
ToggleGroupItemProps,
|
||||
} from "./root/ToggleGroupRoot";
|
||||
export { default as ToggleGroup, ToggleGroupItem } from "./ToggleGroup";
|
||||
export type { ToggleGroupProps, ToggleGroupItemProps } from "./ToggleGroup";
|
||||
+4
-5
@@ -1,7 +1,7 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { BodyShort } from "../../typography/BodyShort";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useToggleGroupContext } from "../root/ToggleGroupRoot.context";
|
||||
import { useToggleGroupContext } from "../ToggleGroup.context";
|
||||
import { useToggleItem } from "./useToggleItem";
|
||||
|
||||
type BaseProps = Omit<
|
||||
@@ -47,10 +47,10 @@ type IconProps = {
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
type ToggleGroupItemProps = BaseProps &
|
||||
export type ToggleGroupItemProps = BaseProps &
|
||||
(ChildrenProps | LabelProps | IconProps);
|
||||
|
||||
const ToggleGroupItem = forwardRef<HTMLButtonElement, ToggleGroupItemProps>(
|
||||
const ToggleItem = forwardRef<HTMLButtonElement, ToggleGroupItemProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
@@ -101,5 +101,4 @@ const ToggleGroupItem = forwardRef<HTMLButtonElement, ToggleGroupItemProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export { ToggleGroupItem };
|
||||
export type { ToggleGroupItemProps };
|
||||
export default ToggleItem;
|
||||
+3
-6
@@ -1,11 +1,11 @@
|
||||
import { useCallback } from "react";
|
||||
import { composeEventHandlers } from "../../utils/helpers";
|
||||
import { rovingFocus } from "../../utils/helpers/roving-focus";
|
||||
import { useToggleGroupContext } from "../root/ToggleGroupRoot.context";
|
||||
import { useToggleGroupContext } from "../ToggleGroup.context";
|
||||
|
||||
const TOGGLE_ITEM_SELECTOR = "[data-aksel-toggle-item]:not([data-disabled])";
|
||||
|
||||
interface UseToggleItemProps {
|
||||
export interface UseToggleItemProps {
|
||||
/**
|
||||
* If `true`, the `ToggleItem` won't be toggleable
|
||||
* @default false
|
||||
@@ -17,7 +17,7 @@ interface UseToggleItemProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
function useToggleItem<P extends UseToggleItemProps>({
|
||||
export function useToggleItem<P extends UseToggleItemProps>({
|
||||
value,
|
||||
disabled = false,
|
||||
onFocus: _onFocus,
|
||||
@@ -92,6 +92,3 @@ function useToggleItem<P extends UseToggleItemProps>({
|
||||
"data-disabled": disabled ? "" : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export { useToggleItem };
|
||||
export type { UseToggleItemProps };
|
||||
@@ -1,21 +0,0 @@
|
||||
import { createContext as ReactCreateContext } from "react";
|
||||
import { createStrictContext } from "../../utils/helpers";
|
||||
import type { ToggleGroupProps } from "../ToggleGroup.types";
|
||||
import type { useToggleGroup } from "./useToggleGroup";
|
||||
|
||||
interface ToggleContextProps {
|
||||
size: "medium" | "small" | "xsmall";
|
||||
}
|
||||
|
||||
const ToggleGroupContext = ReactCreateContext<ToggleContextProps | null>(null);
|
||||
type ToggleGroupProviderProps = ReturnType<typeof useToggleGroup> &
|
||||
Pick<ToggleGroupProps, "size">;
|
||||
|
||||
/* State context */
|
||||
const { Provider: ToggleGroupProvider, useContext: useToggleGroupContext } =
|
||||
createStrictContext<ToggleGroupProviderProps>({
|
||||
name: "ToggleGroupContext",
|
||||
errorMessage: "<ToggleGroup.Item> needs to be wrapped within <ToggleGroup>",
|
||||
});
|
||||
|
||||
export { ToggleGroupContext, ToggleGroupProvider, useToggleGroupContext };
|
||||
+3
-5
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useControllableState } from "../../utils/hooks";
|
||||
import type { ToggleGroupProps } from "../ToggleGroup.types";
|
||||
import { useControllableState } from "../utils/hooks";
|
||||
import type { ToggleGroupProps } from "./ToggleGroup.types";
|
||||
|
||||
function useToggleGroup({
|
||||
export function useToggleGroup({
|
||||
onChange,
|
||||
value,
|
||||
defaultValue = "",
|
||||
@@ -28,5 +28,3 @@ function useToggleGroup({
|
||||
setFocusedValue,
|
||||
};
|
||||
}
|
||||
|
||||
export { useToggleGroup };
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { PartialTranslations } from "./i18n.types";
|
||||
import type { Component, ComponentTranslation } from "./i18n.types";
|
||||
|
||||
// Based on https://github.com/Shopify/polaris/blob/main/polaris-react/src/utilities/get.ts
|
||||
|
||||
export function get(
|
||||
export function get<T extends Component>(
|
||||
keypath: string,
|
||||
objs: (PartialTranslations | undefined)[],
|
||||
objs: (ComponentTranslation<T> | undefined)[],
|
||||
) {
|
||||
const keys = keypath.split(".");
|
||||
|
||||
@@ -13,7 +13,7 @@ export function get(
|
||||
continue;
|
||||
}
|
||||
|
||||
let acc: string | PartialTranslations = obj;
|
||||
let acc: string | ComponentTranslation<T> = obj;
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const val = acc[keys[i]];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { bench, describe } from "vitest";
|
||||
import { Provider } from "../../provider";
|
||||
import { get } from "./get";
|
||||
import { useI18n } from "./i18n.hooks";
|
||||
import type { PartialTranslations } from "./i18n.types";
|
||||
import type { ComponentTranslation } from "./i18n.types";
|
||||
import { nb } from "./locales";
|
||||
|
||||
describe("translate function throughput", () => {
|
||||
@@ -54,11 +54,11 @@ describe("translate with multiple context objects", () => {
|
||||
});
|
||||
|
||||
describe("get() function throughput", () => {
|
||||
const objects: (PartialTranslations | undefined)[] = [
|
||||
const objects: (ComponentTranslation<"FileUpload"> | undefined)[] = [
|
||||
undefined,
|
||||
{ FileUpload: { item: { uploading: "First" } } },
|
||||
{ FileUpload: { item: { downloading: "Second" } } },
|
||||
{ FileUpload: nb.FileUpload },
|
||||
{ item: { uploading: "First" } },
|
||||
{ item: { downloading: "Second" } },
|
||||
nb.FileUpload,
|
||||
];
|
||||
|
||||
bench("shallow key hit in first object", () => {
|
||||
@@ -74,6 +74,6 @@ describe("get() function throughput", () => {
|
||||
});
|
||||
|
||||
bench("long key dot notation parsing", () => {
|
||||
get("dropzone.dragAndDropMultiple", [{ FileUpload: nb.FileUpload }]);
|
||||
get("dropzone.dragAndDropMultiple", [nb.FileUpload]);
|
||||
});
|
||||
});
|
||||
@@ -110,6 +110,44 @@ describe("useI18n", () => {
|
||||
const translate = result.current;
|
||||
expect(() => translate("item.uploading", { other: "John" })).toThrowError();
|
||||
});
|
||||
|
||||
test("should use internal defaults without entries in the public locales", () => {
|
||||
const { result } = renderHook(() => useI18n("TEST_DATA"), {
|
||||
wrapper: ({ children }) => <Provider locale={en}>{children}</Provider>,
|
||||
});
|
||||
|
||||
expect(result.current("label")).toBe("Internal grid");
|
||||
expect(result.current("selected", { count: 3 })).toBe("3 rows selected");
|
||||
/* @ts-expect-error Testing nonexistent key */
|
||||
expect(() => result.current("nonexistentKey")).toThrowError();
|
||||
});
|
||||
|
||||
test("should prioritize local internal translations and fall back to internal defaults", () => {
|
||||
const { result } = renderHook(() =>
|
||||
useI18n(
|
||||
"TEST_DATA",
|
||||
undefined,
|
||||
{ label: "First override" },
|
||||
{ label: "Second override" },
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current("label")).toBe("First override");
|
||||
expect(result.current("selected", { count: 2 })).toBe("2 rows selected");
|
||||
});
|
||||
|
||||
test("should update internal overrides without changing the translate function", () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ label }) => useI18n("TEST_DATA", { label }),
|
||||
{ initialProps: { label: "Initial label" } },
|
||||
);
|
||||
const translate = result.current;
|
||||
|
||||
rerender({ label: "Updated label" });
|
||||
|
||||
expect(result.current).toBe(translate);
|
||||
expect(translate("label")).toBe("Updated label");
|
||||
});
|
||||
});
|
||||
|
||||
describe("useDateLocale", () => {
|
||||
|
||||
@@ -2,12 +2,8 @@ import type { Locale } from "date-fns";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useProvider } from "../../provider/Provider";
|
||||
import { get } from "./get";
|
||||
import type {
|
||||
Component,
|
||||
ComponentTranslation,
|
||||
PartialTranslations,
|
||||
TFunction,
|
||||
} from "./i18n.types";
|
||||
import { internalTranslations } from "./i18n.internal";
|
||||
import type { Component, ComponentTranslation, TFunction } from "./i18n.types";
|
||||
|
||||
/* https://regex101.com/r/LYKWi3/1 */
|
||||
const REPLACE_REGEX = /{[^}]*}/g;
|
||||
@@ -17,13 +13,9 @@ function useI18n<T extends Component>(
|
||||
...localTranslations: (ComponentTranslation<T> | undefined)[]
|
||||
) {
|
||||
const context = useProvider();
|
||||
const contextTranslations = context.translations || [];
|
||||
const i18nObjects: (PartialTranslations | undefined)[] = [
|
||||
const i18nObjects: (ComponentTranslation<T> | undefined)[] = [
|
||||
...localTranslations,
|
||||
...(Array.isArray(contextTranslations)
|
||||
? contextTranslations.map((t) => t[componentName])
|
||||
: [contextTranslations[componentName]]),
|
||||
context.locale[componentName],
|
||||
...geti18nObjects(componentName, context),
|
||||
];
|
||||
|
||||
const i18nRef = useRef(i18nObjects);
|
||||
@@ -57,6 +49,31 @@ function useI18n<T extends Component>(
|
||||
return translate;
|
||||
}
|
||||
|
||||
function geti18nObjects(
|
||||
componentName: Component,
|
||||
context: ReturnType<typeof useProvider>,
|
||||
) {
|
||||
if (isInternalComponent(componentName)) {
|
||||
return [internalTranslations[componentName]];
|
||||
}
|
||||
|
||||
const contextTranslations = context.translations || [];
|
||||
const i18nObjects: (ComponentTranslation<Component> | undefined)[] = [
|
||||
...(Array.isArray(contextTranslations)
|
||||
? contextTranslations.map((t) => t[componentName])
|
||||
: [contextTranslations[componentName]]),
|
||||
context.locale[componentName],
|
||||
];
|
||||
|
||||
return i18nObjects;
|
||||
}
|
||||
|
||||
function isInternalComponent(
|
||||
componentName: Component,
|
||||
): componentName is keyof typeof internalTranslations {
|
||||
return componentName in internalTranslations;
|
||||
}
|
||||
|
||||
function useDateLocale() {
|
||||
const context = useProvider();
|
||||
const contextTranslations = context.translations || [];
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/** Defaults for internal components, independent of the public locales. Cant be overridden by users. */
|
||||
const internalTranslations = {
|
||||
TEST_DATA: {
|
||||
label: "Internal grid",
|
||||
selected: "{count} rows selected",
|
||||
},
|
||||
} satisfies Record<string, Record<string, string | Record<string, string>>>;
|
||||
|
||||
export { internalTranslations };
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { internalTranslations } from "./i18n.internal";
|
||||
import type nb from "./locales/nb";
|
||||
|
||||
/* https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3 */
|
||||
@@ -7,8 +8,10 @@ type NestedKeyOf<ObjectType extends object> = {
|
||||
: `${Key}`;
|
||||
}[keyof ObjectType & (string | number)];
|
||||
|
||||
type InternalTranslations = typeof internalTranslations;
|
||||
|
||||
export type TFunction<T extends Component> = (
|
||||
keypath: NestedKeyOf<Translations[T]>,
|
||||
keypath: NestedKeyOf<(Translations & InternalTranslations)[T]>,
|
||||
replacements?: Record<string, string | number>,
|
||||
) => string;
|
||||
|
||||
@@ -21,8 +24,8 @@ export type Translations = typeof nb;
|
||||
|
||||
export type PartialTranslations = RecursivePartial<Translations>;
|
||||
|
||||
export type Component = keyof Translations;
|
||||
export type Component = keyof Translations | keyof InternalTranslations;
|
||||
|
||||
export type ComponentTranslation<T extends Component> = RecursivePartial<
|
||||
Translations[T]
|
||||
(Translations & InternalTranslations)[T]
|
||||
>;
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/tailwind/",
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^8.19.2",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@types/node": "^24.10.9",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "8.19.2",
|
||||
"version": "8.20.0",
|
||||
"description": "Design-tokens for Nav designsystem",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Checkbox: Add `CheckboxCard` and `CheckboxCardGroup` components ([#5364](https://github.com/navikt/aksel/pull/5364))
|
||||
|
||||
* ToggleGroup: Add xsmall as size ([#5411](https://github.com/navikt/aksel/pull/5411))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Checkbox: Add `CheckboxCard` and `CheckboxCardGroup` components ([#5364](https://github.com/navikt/aksel/pull/5364))
|
||||
|
||||
* Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW ([#5401](https://github.com/navikt/aksel/pull/5401))
|
||||
|
||||
* ToggleGroup: Add xsmall as size ([#5411](https://github.com/navikt/aksel/pull/5411))
|
||||
|
||||
## 8.19.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
"sanity:update": "yarn workspace website update:sanity"
|
||||
},
|
||||
"dependencies": {
|
||||
"@navikt/aksel": "^8.19.2",
|
||||
"@navikt/aksel-icons": "^8.19.2",
|
||||
"@navikt/ds-css": "^8.19.2",
|
||||
"@navikt/ds-react": "^8.19.2",
|
||||
"@navikt/ds-tailwind": "^8.19.2",
|
||||
"@navikt/ds-tokens": "^8.19.2"
|
||||
"@navikt/aksel": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-react": "^8.20.0",
|
||||
"@navikt/ds-tailwind": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0"
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ export const KodeEksempler = defineType({
|
||||
"Mindre padding i forhåndsvisningen, og koden er skjult som standard.",
|
||||
name: "compact",
|
||||
type: "boolean",
|
||||
initialValue: false,
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
|
||||
@@ -20,7 +20,7 @@ type RouteConfig<T extends RouteItemBase> = {
|
||||
heading: string;
|
||||
description: string;
|
||||
buildAttributes: (item: T) => Record<string, string>;
|
||||
getIntro?: (item: T) => any[] | undefined;
|
||||
getIntro?: (item: T) => string;
|
||||
};
|
||||
|
||||
function createRoute<T extends RouteItemBase>(config: RouteConfig<T>) {
|
||||
@@ -96,7 +96,7 @@ function buildItemMarkdown<T extends RouteItemBase>(
|
||||
return buildMarkdown(
|
||||
open,
|
||||
{ heading: item.heading ?? "" },
|
||||
portableMarkdown(config.getIntro?.(item)),
|
||||
config.getIntro?.(item) ?? null,
|
||||
portableMarkdown(item.content ?? undefined),
|
||||
MetadataSeksjonMarkdown(item.component_metadata),
|
||||
close,
|
||||
|
||||
@@ -4,7 +4,9 @@ import {
|
||||
KOMPONENT_BY_SLUG_MARKDOWN_QUERY,
|
||||
} from "@/app/_sanity/queries";
|
||||
import type { ALL_KOMPONENTS_MARKDOWN_QUERY_RESULT } from "@/app/_sanity/query-types";
|
||||
import { buildMarkdown } from "../helpers/build-markdown";
|
||||
import { createRoute } from "../helpers/create-route";
|
||||
import { portableMarkdown } from "../helpers/portable-markdown";
|
||||
|
||||
type KomponentItem = ALL_KOMPONENTS_MARKDOWN_QUERY_RESULT[number];
|
||||
|
||||
@@ -44,5 +46,27 @@ export default createRoute<KomponentItem>({
|
||||
packages: (item.kodepakker ?? []).join(", ") || "Ukjent",
|
||||
version: PackageJson?.version,
|
||||
}),
|
||||
getIntro: (item) => item.intro?.body,
|
||||
getIntro: (item) => {
|
||||
const useFor = [
|
||||
...(item.status?.internal ? ["Bruk på interne flater"] : []),
|
||||
...(item.intro?.brukes_til ?? []),
|
||||
];
|
||||
const avoidUseFor = item.intro?.brukes_ikke_til ?? [];
|
||||
|
||||
return buildMarkdown(
|
||||
portableMarkdown(item.intro?.body),
|
||||
useFor.length > 0
|
||||
? buildMarkdown(
|
||||
{ heading: "Egnet til:", level: 2 },
|
||||
useFor.map((text) => `- ${text}`).join("\n"),
|
||||
)
|
||||
: null,
|
||||
avoidUseFor.length > 0
|
||||
? buildMarkdown(
|
||||
{ heading: "Uegnet til:", level: 2 },
|
||||
avoidUseFor.map((text) => `- ${text}`).join("\n"),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
});
|
||||
+12
-11
@@ -19,7 +19,7 @@ type CaseT = {
|
||||
keywords: string[];
|
||||
priority: 0 | 1 | 2 | 3;
|
||||
sender: string;
|
||||
age: Date;
|
||||
created: Date;
|
||||
status:
|
||||
| "Mottatt"
|
||||
| "Under behandling"
|
||||
@@ -29,9 +29,8 @@ type CaseT = {
|
||||
deadline: Date;
|
||||
};
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
function createDemoRows(count: number): CaseT[] {
|
||||
const now = Date.now();
|
||||
/* prettier-ignore */
|
||||
const names = ["Emma","Olivia","Nora","Sofie","Leah","Ella","Frida","Sofia","Ellinor","Astrid","Noah","Jakob","Lucas","Emil","Oskar","William","Elias","Isak","Oliver","Ludvig"];
|
||||
/* prettier-ignore */
|
||||
@@ -59,15 +58,16 @@ function createDemoRows(count: number): CaseT[] {
|
||||
keywords: Array.from(keywords),
|
||||
priority: ([0, 1, 2, 3] as const)[rng(4)],
|
||||
sender: names[rng(names.length)],
|
||||
age: new Date(now - index * daysInMs(1)),
|
||||
created: new Date(now - index * daysInMs(1)),
|
||||
status: statuses[rng(statuses.length)],
|
||||
deadline: new Date(now - daysInMs(8) + (index % 50) * daysInMs(1)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/* prettier-ignore */
|
||||
function createDemoColumns({ withSorting = false }: { withSorting?: boolean; } = {}): DataGrid.Columns<CaseT> {
|
||||
function createDemoColumns({
|
||||
withSorting = false,
|
||||
}: { withSorting?: boolean } = {}): DataGrid.Columns<CaseT> {
|
||||
return [
|
||||
{
|
||||
id: "caseId",
|
||||
@@ -116,10 +116,10 @@ function createDemoColumns({ withSorting = false }: { withSorting?: boolean; } =
|
||||
isSortable: withSorting,
|
||||
},
|
||||
{
|
||||
id: "age",
|
||||
id: "created",
|
||||
header: "Saksalder",
|
||||
bodyCell: ({ age }) =>
|
||||
`${Math.floor((Date.now() - age.getTime()) / daysInMs(1))}d`,
|
||||
bodyCell: ({ created }) =>
|
||||
`${Math.floor((Date.now() - created.getTime()) / daysInMs(1))}d`,
|
||||
align: "right",
|
||||
width: { defaultValue: 110 },
|
||||
isSortable: withSorting,
|
||||
@@ -161,7 +161,8 @@ function createDemoColumns({ withSorting = false }: { withSorting?: boolean; } =
|
||||
align: "center",
|
||||
width: { autoResizeOnce: true },
|
||||
},
|
||||
];
|
||||
] satisfies (DataGrid.Columns<CaseT>[number] &
|
||||
({ isSortable?: never } | { isSortable: boolean; id: keyof CaseT }))[];
|
||||
}
|
||||
|
||||
const PriorityNames = {
|
||||
@@ -188,7 +189,7 @@ function PriorityTag({ priority }: { priority: CaseT["priority"] }) {
|
||||
}
|
||||
|
||||
function DeadlineCell({ deadline }: { deadline: CaseT["deadline"] }) {
|
||||
const isOverdue = deadline.getTime() < now;
|
||||
const isOverdue = deadline.getTime() < Date.now(); // eslint-disable-line react-hooks/purity
|
||||
|
||||
return (
|
||||
<HGrid
|
||||
|
||||
Loaded 100 of 113 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user