mirror of
https://github.com/navikt/aksel.git
synced 2026-10-26 23:52:02 +00:00
Compare commits
27
Commits
v8.20.0
...
experimental
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e6f7a014 | ||
|
|
d140ee84cc | ||
|
|
dfd3ddbe51 | ||
|
|
68c66f345a | ||
|
|
64726b8874 | ||
|
|
774e25dd27 | ||
|
|
1b639f4ede | ||
|
|
c234d98d7c | ||
|
|
a0c7f1fe54 | ||
|
|
13bd3cbfd0 | ||
|
|
d2325fb33b | ||
|
|
d8abc9bfeb | ||
|
|
769c477392 | ||
|
|
6fb7eda476 | ||
|
|
a013d5312f | ||
|
|
dabfb56def | ||
|
|
c571a7d4ba | ||
|
|
848783bb9f | ||
|
|
a7876ed078 | ||
|
|
6713cd6d30 | ||
|
|
136676ffa0 | ||
|
|
234c19f16d | ||
|
|
a2c0f09980 | ||
|
|
c3669539c6 | ||
|
|
0a0be96cd9 | ||
|
|
f2cff91d4f | ||
|
|
2994628c79 |
No files matched your search
@@ -22,7 +22,7 @@
|
||||
## Repo summary
|
||||
|
||||
- Nav's design system. Yarn 4 workspaces monorepo: published packages under `@navikt/` + docs site `aksel.nav.no/` + `apps/` + `tooling/`.
|
||||
- Stack: TypeScript 6, React 19 (dev), Next.js 16, Storybook 10, Vitest 4, ESLint 9, Stylelint 17, Biome 2 (lint only), Prettier 3 (formatting). Node 22+, Yarn 4.18.0.
|
||||
- Stack: TypeScript 6, React 19 (dev), Next.js 16, Storybook 10, Vitest 4, ESLint 9, Stylelint 17, Oxlint (JS/TS lint), Prettier 3 (formatting). Node 22+, Yarn 4.18.0.
|
||||
- Published `@navikt/*` packages must stay React 17 compatible (peer `react ^17 || ^18 || ^19`).
|
||||
|
||||
## Start and validate
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
## Commands
|
||||
|
||||
- `corepack yarn eslint --max-warnings=0 --no-warn-ignored <files...>` / `corepack yarn stylelint <files...>` / `corepack yarn biome lint --no-errors-on-unmatched <files...>`
|
||||
- `corepack yarn eslint --max-warnings=0 --no-warn-ignored <files...>` / `corepack yarn stylelint <files...>` / `corepack yarn oxlint --deny-warnings <files...>`
|
||||
- `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)
|
||||
@@ -64,7 +64,7 @@ Path-specific rules load automatically from `.github/instructions/` (see each `a
|
||||
- `@navikt/aksel/src/` - CLI and codemods
|
||||
- `aksel.nav.no/website/` - Next.js docs site · `aksel.nav.no/sanity-studio/` - CMS schema
|
||||
- `apps/playroom/`, `apps/mcp-server/`, `apps/figma-icon-plugin/` - apps · `tooling/` - ESLint plugin, scripts, changelog, analyzer
|
||||
- `.changeset/` - release notes · Root config: `eslint.config.js`, `stylelint.config.mjs`, `biome.json`, `vitest.config.ts`, `.storybook/`
|
||||
- `.changeset/` - release notes · Root config: `eslint.config.js`, `stylelint.config.mjs`, `.oxlintrc.json`, `vitest.config.ts`, `.storybook/`
|
||||
|
||||
## Skills and agents
|
||||
|
||||
@@ -90,3 +90,4 @@ Path-specific rules load automatically from `.github/instructions/` (see each `a
|
||||
- Preserve JSDoc on public props/components when changing public APIs.
|
||||
- Reuse helpers: `cl`, `composeEventHandlers`, `useId`, `omit`, `useClientLayoutEffect`, `useEventListener`.
|
||||
- No tests for trivial logic. Only tests for complex logic that has a higher risk of breaking.
|
||||
- Suppress Oxlint rules with `oxlint-disable-next-line <rule>` / `oxlint-disable <rule>`, never `eslint-disable` (breaks ESLint).
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
description: "JS/TS conventions not enforced by ESLint/Biome: loops, export style and placement."
|
||||
description: "JS/TS conventions not enforced by ESLint/Oxlint: loops, export style and placement."
|
||||
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.mts,**/*.mjs"
|
||||
---
|
||||
|
||||
# JS/TS conventions
|
||||
|
||||
Formatting, import order and `import type` are handled by Prettier and Biome. Don't hand-tune them.
|
||||
Formatting and import order are handled by Prettier; linting by ESLint and Oxlint. Don't hand-tune formatting or import order.
|
||||
|
||||
Suppress Oxlint rules with `// oxlint-disable-next-line <rule>` (file-level: `/* oxlint-disable <rule> */`). Never use `eslint-disable` for Oxlint rules; ESLint reports unknown rules and unused directives.
|
||||
|
||||
- Use `for...of` instead of `Array.forEach`. It supports `break`/`continue`/`await` and reads top-down.
|
||||
- Use named exports only. Keep type exports separate from value exports (`export { X }` / `export type { XProps }`).
|
||||
|
||||
@@ -59,7 +59,7 @@ Group only matching files:
|
||||
```sh
|
||||
corepack yarn eslint --max-warnings=0 --no-warn-ignored <changed .ts/.tsx/.js/.jsx files>
|
||||
corepack yarn stylelint <changed .css files>
|
||||
corepack yarn biome lint --no-errors-on-unmatched <changed supported source/config files>
|
||||
corepack yarn oxlint --deny-warnings <changed .ts/.tsx/.js/.jsx files>
|
||||
```
|
||||
|
||||
Do not pass unsupported file types to make a command appear comprehensive. Documentation-only changes need no code validation unless a documentation check exists.
|
||||
|
||||
@@ -26,7 +26,7 @@ argument-hint: "PR number, branch or files to review"
|
||||
- 🟡 **Suggestion**: should fix. Missing test, docs or changeset, maintainability risk, over-editing.
|
||||
- 💭 **Nit**: optional. Leave it out when in doubt.
|
||||
|
||||
Skip anything ESLint, Stylelint, Biome, Prettier or TypeScript already catches. No praise-only or speculative comments. Every finding says **why** it matters and what to do.
|
||||
Skip anything ESLint, Stylelint, Oxlint, Prettier or TypeScript already catches. No praise-only or speculative comments. Every finding says **why** it matters and what to do.
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ Move stories to component root dir. If stories import from internal paths, updat
|
||||
|
||||
### 5. Update `index.ts`
|
||||
|
||||
All exports are **named** — no `default` exports anywhere. The component `index.ts` re-exports the compound component, its sub-components, and their types from the root file:
|
||||
All exports are **named** — no `default` exports anywhere. For compound components, the root file is the single export source: it assembles the compound component, imports every publicly exported sub-component and its prop types, and exports all public values and types together. The component `index.ts` only re-exports these names from `./root/<Component>Root` — never directly from sub-component files or shared type files.
|
||||
|
||||
```ts
|
||||
"use client";
|
||||
@@ -111,13 +111,18 @@ export type {
|
||||
} from "./root/<Component>Root";
|
||||
```
|
||||
|
||||
Group values first, then types. If types live in `<Component>.types.ts`, re-export them from there.
|
||||
Group values first, then types. If types live in `<Component>.types.ts`, import them into the root file and include them in its type export block. The `index.ts` still re-exports them from the root file.
|
||||
|
||||
#### Compound root file export pattern
|
||||
|
||||
In `<Component>Root.tsx`, assemble the compound component with `Object.assign` — **do not** use an `as <Component>Component` cast or a dedicated `<Component>Component` interface:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
<Component>Trigger,
|
||||
type <Component>TriggerProps,
|
||||
} from "../trigger/<Component>Trigger";
|
||||
|
||||
const <Component>Root = forwardRef<HTMLDivElement, <Component>Props>((props, ref) => {
|
||||
// ...
|
||||
});
|
||||
@@ -138,6 +143,8 @@ export { <Component>, <Component>Trigger };
|
||||
export type { <Component>Props, <Component>TriggerProps };
|
||||
```
|
||||
|
||||
Follow `accordion/root/AccordionRoot.tsx`: export the imported sub-component values and prop types alongside the compound component at the bottom of the root file. Do not add internal-only sub-components to the public export chain. Sub-components must not import from the root file or component `index.ts`; use shared type/context/helper files to avoid circular dependencies.
|
||||
|
||||
Why `Object.assign` instead of `as`: the cast types the const as an interface that has no runtime `valueDeclaration`, so `react-docgen-typescript` (used by `yarn docgen:meta`) fails to extract props and can't document the component when it is exported via a grouped `export { }` statement. `Object.assign` lets TypeScript infer the intersection type from the value, so metadata generation works with named, bottom-of-file exports.
|
||||
|
||||
Sub-component files follow the same rule — named value export plus named type export at the bottom, no inline `export interface` and no default:
|
||||
@@ -173,6 +180,8 @@ Concretely: grep for `export` lines in both versions and diff them. All componen
|
||||
|
||||
**Package root.** Verify `@navikt/core/react/src/index.ts` still re-exports the component. No changes needed there unless the component's `index.ts` path changed (it shouldn't).
|
||||
|
||||
**Root-only re-exports.** For compound components, verify every value and type export in the component `index.ts` comes from `./root/<Component>Root`, and every forwarded name is exported by that root file.
|
||||
|
||||
**No case-duplicate tracked paths.** This is the most common restructure bug. On case-insensitive filesystems (macOS, Windows) git can end up tracking **two** entries that differ only in casing (e.g. `accordion.stories.tsx` **and** `Accordion.stories.tsx`) while the working tree shows only one file. The stale entry then breaks tests and checkout on other machines. `ls` / file search will NOT reveal it — you must ask git. Run:
|
||||
|
||||
```sh
|
||||
@@ -281,6 +290,7 @@ dialog/
|
||||
|
||||
- **No breaking changes.** Public export names and prop shapes must be identical after restructuring.
|
||||
- **Named exports only.** No `default` exports; group named exports at the bottom of each file (values first, then types).
|
||||
- **Compound exports flow through the root.** Component `index.ts` re-exports public values and types only from `root/<Component>Root.tsx`, not directly from sub-component or shared type files.
|
||||
- **Compound roots use `Object.assign`.** No `as <Component>Component` cast or `<Component>Component` interface — it breaks `yarn docgen:meta` prop extraction.
|
||||
- Component rules (React 17 compatibility, `"use client"`, `forwardRef`/`className`/`...rest`/`as`, tokens) live in `.github/instructions/ds-react-component.instructions.md`. They still apply.
|
||||
- Keep JSDoc on public props and components. The component-level JSDoc block sits directly above the `Object.assign` call; sub-component `@see` tags go on the keys inside the `Object.assign` object literal.
|
||||
@@ -289,6 +299,6 @@ dialog/
|
||||
|
||||
- Canonical example: `@navikt/core/react/src/dialog/`
|
||||
- Package public API: `@navikt/core/react/src/index.ts`
|
||||
- Component `index.ts` pattern: `@navikt/core/react/src/dialog/index.ts`
|
||||
- Component export pattern: `@navikt/core/react/src/accordion/root/AccordionRoot.tsx` and `@navikt/core/react/src/accordion/index.ts`
|
||||
- Context pattern: `@navikt/core/react/src/dialog/root/DialogRoot.context.ts`
|
||||
- Meta: `@navikt/core/react/src/accordion/Accordion.meta.ts`
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": [
|
||||
"eslint",
|
||||
"typescript",
|
||||
"unicorn",
|
||||
"oxc",
|
||||
"react",
|
||||
"jsx-a11y",
|
||||
"import",
|
||||
"vitest"
|
||||
],
|
||||
"categories": {
|
||||
"correctness": "error"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/lib",
|
||||
"**/public",
|
||||
"**/esm",
|
||||
"**/cjs",
|
||||
"**/dist",
|
||||
"**/.next",
|
||||
"**/.build",
|
||||
"**/coverage",
|
||||
"**/storybook-static",
|
||||
"**/test-results",
|
||||
"**/playwright-report",
|
||||
"**/.sanity/runtime",
|
||||
"**/next-env.d.ts",
|
||||
"**/codemod/**/*.js",
|
||||
"**/transforms/**/*.input.*",
|
||||
"**/transforms/**/*.output.*",
|
||||
"@navikt/aksel/**/*.input.*",
|
||||
"@navikt/aksel/**/*.output.*",
|
||||
"**/modal/helpers/dialog-polyfill.ts",
|
||||
"**/query-types.ts",
|
||||
"**/component-names.generated.ts",
|
||||
"**/tokens/**/plugin.js",
|
||||
"@navikt/aksel-icons/src",
|
||||
"@navikt/aksel/src/version.ts",
|
||||
"@navikt/core/tailwind/src/tokens.ts",
|
||||
"@navikt/core/tailwind/tailwind.config.js",
|
||||
"@navikt/core/tailwind/tailwind.darkside.config.js",
|
||||
"@navikt/core/tokens/token_docs.js",
|
||||
"apps/playroom/playroom.config.js",
|
||||
"apps/playroom/src/auto-generated-imports.ts",
|
||||
"apps/figma-icon-plugin/src/ui/vite-env.d.ts",
|
||||
"tooling/analyzer/temp",
|
||||
".yarn"
|
||||
],
|
||||
"rules": {
|
||||
"jsx-a11y/alt-text": "error",
|
||||
"jsx-a11y/anchor-ambiguous-text": "error",
|
||||
"jsx-a11y/anchor-has-content": "error",
|
||||
"jsx-a11y/anchor-is-valid": "error",
|
||||
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
|
||||
"jsx-a11y/aria-props": "error",
|
||||
"jsx-a11y/aria-proptypes": "error",
|
||||
"jsx-a11y/aria-role": "error",
|
||||
"jsx-a11y/aria-unsupported-elements": "error",
|
||||
"jsx-a11y/autocomplete-valid": "error",
|
||||
"jsx-a11y/click-events-have-key-events": "error",
|
||||
"jsx-a11y/heading-has-content": "error",
|
||||
"jsx-a11y/html-has-lang": "error",
|
||||
"jsx-a11y/iframe-has-title": "error",
|
||||
"jsx-a11y/img-redundant-alt": "error",
|
||||
"jsx-a11y/interactive-supports-focus": "error",
|
||||
"jsx-a11y/label-has-associated-control": "error",
|
||||
"jsx-a11y/lang": "error",
|
||||
"jsx-a11y/media-has-caption": "error",
|
||||
"jsx-a11y/mouse-events-have-key-events": "error",
|
||||
"jsx-a11y/no-access-key": "error",
|
||||
"jsx-a11y/no-aria-hidden-on-focusable": "error",
|
||||
"jsx-a11y/no-autofocus": "error",
|
||||
"jsx-a11y/no-distracting-elements": "error",
|
||||
"jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
|
||||
"jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
|
||||
"jsx-a11y/no-noninteractive-tabindex": "error",
|
||||
"jsx-a11y/no-redundant-roles": "error",
|
||||
"jsx-a11y/no-static-element-interactions": "error",
|
||||
"jsx-a11y/prefer-tag-over-role": "off",
|
||||
"jsx-a11y/role-has-required-aria-props": "error",
|
||||
"jsx-a11y/role-supports-aria-props": "error",
|
||||
"jsx-a11y/scope": "error",
|
||||
"jsx-a11y/tabindex-no-positive": "error",
|
||||
"react/button-has-type": "error",
|
||||
"eslint/no-extra-label": "warn",
|
||||
"eslint/no-lone-blocks": "warn",
|
||||
"eslint/no-sequences": "warn",
|
||||
"eslint/no-unneeded-ternary": "warn",
|
||||
"eslint/no-useless-computed-key": "warn",
|
||||
"eslint/no-useless-constructor": "warn",
|
||||
"eslint/no-useless-rename": "warn",
|
||||
"eslint/prefer-numeric-literals": "warn",
|
||||
"eslint/prefer-regex-literals": "warn",
|
||||
"eslint/prefer-rest-params": "warn",
|
||||
"react/jsx-no-useless-fragment": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"typescript/no-useless-empty-export": "warn",
|
||||
"unicorn/no-static-only-class": "warn",
|
||||
"unicorn/no-useless-switch-case": "warn",
|
||||
"unicorn/prefer-array-flat": "warn",
|
||||
"unicorn/prefer-array-flat-map": "warn",
|
||||
"unicorn/prefer-array-index-of": "warn",
|
||||
"unicorn/prefer-date-now": "warn",
|
||||
"eslint/no-constructor-return": "error",
|
||||
"eslint/no-inner-declarations": [
|
||||
"error",
|
||||
"both",
|
||||
{
|
||||
"blockScopedFunctions": "allow"
|
||||
}
|
||||
],
|
||||
"eslint/no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false,
|
||||
"variables": false,
|
||||
"enums": false,
|
||||
"typedefs": false,
|
||||
"ignoreTypeReferences": true,
|
||||
"allowNamedExports": true
|
||||
}
|
||||
],
|
||||
"eslint/radix": "warn",
|
||||
"oxc/bad-min-max-func": "error",
|
||||
"react/void-dom-elements-no-children": "error",
|
||||
"import/namespace": "warn",
|
||||
"oxc/no-accumulating-spread": "warn",
|
||||
"eslint/no-eval": "error",
|
||||
"eslint/no-script-url": "error",
|
||||
"react/jsx-no-script-url": "error",
|
||||
"react/no-danger": "error",
|
||||
"eslint/prefer-const": "warn",
|
||||
"eslint/prefer-exponentiation-operator": "warn",
|
||||
"eslint/prefer-template": "warn",
|
||||
"typescript/consistent-type-imports": [
|
||||
"warn",
|
||||
{
|
||||
"disallowTypeAnnotations": false
|
||||
}
|
||||
],
|
||||
"typescript/no-non-null-assertion": "warn",
|
||||
"typescript/prefer-function-type": "warn",
|
||||
"typescript/prefer-literal-enum-member": "warn",
|
||||
"unicorn/prefer-node-protocol": "warn",
|
||||
"eslint/array-callback-return": [
|
||||
"error",
|
||||
{
|
||||
"checkForEach": true,
|
||||
"allowVoid": true
|
||||
}
|
||||
],
|
||||
"eslint/default-case-last": "warn",
|
||||
"eslint/eqeqeq": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"null": "ignore"
|
||||
}
|
||||
],
|
||||
"eslint/no-label-var": "error",
|
||||
"eslint/no-labels": [
|
||||
"warn",
|
||||
{
|
||||
"allowLoop": true
|
||||
}
|
||||
],
|
||||
"eslint/no-proto": "warn",
|
||||
"eslint/no-self-compare": "error",
|
||||
"eslint/no-template-curly-in-string": "warn",
|
||||
"oxc/approx-constant": "warn",
|
||||
"oxc/misrefactored-assign-op": "error",
|
||||
"oxc/no-const-enum": "warn",
|
||||
"react/no-array-index-key": "error",
|
||||
"typescript/adjacent-overload-signatures": "warn",
|
||||
"typescript/no-invalid-void-type": "warn",
|
||||
"unicorn/no-document-cookie": "warn",
|
||||
"unicorn/no-instanceof-array": "warn",
|
||||
"unicorn/no-thenable": "error",
|
||||
"unicorn/prefer-number-properties": "off",
|
||||
"eslint/no-console": [
|
||||
"warn",
|
||||
{
|
||||
"allow": [
|
||||
"info",
|
||||
"warn",
|
||||
"error",
|
||||
"group",
|
||||
"groupEnd",
|
||||
"table",
|
||||
"assert",
|
||||
"countReset",
|
||||
"count",
|
||||
"dir",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"timeStamp"
|
||||
]
|
||||
}
|
||||
],
|
||||
"eslint/no-else-return": [
|
||||
"error",
|
||||
{
|
||||
"allowElseIf": false
|
||||
}
|
||||
],
|
||||
"eslint/no-shadow": [
|
||||
"error",
|
||||
{
|
||||
"ignoreTypeValueShadow": true,
|
||||
"ignoreFunctionTypeParameterNameValueShadow": true
|
||||
}
|
||||
],
|
||||
"eslint/object-shorthand": ["error", "always"],
|
||||
"typescript/array-type": "off",
|
||||
"vitest/require-mock-type-parameters": "off",
|
||||
"vitest/require-to-throw-message": "off",
|
||||
"typescript/no-explicit-any": "off",
|
||||
"unicorn/no-array-for-each": "off",
|
||||
"import/no-cycle": "off",
|
||||
"react/refs": "off",
|
||||
"eslint/constructor-super": "off",
|
||||
"eslint/for-direction": "off",
|
||||
"eslint/getter-return": "off",
|
||||
"eslint/no-async-promise-executor": "off",
|
||||
"eslint/no-class-assign": "off",
|
||||
"eslint/no-compare-neg-zero": "off",
|
||||
"eslint/no-cond-assign": "off",
|
||||
"eslint/no-const-assign": "off",
|
||||
"eslint/no-constant-binary-expression": "off",
|
||||
"eslint/no-constant-condition": "off",
|
||||
"eslint/no-control-regex": "off",
|
||||
"eslint/no-debugger": "off",
|
||||
"eslint/no-delete-var": "off",
|
||||
"eslint/no-dupe-class-members": "off",
|
||||
"eslint/no-dupe-else-if": "off",
|
||||
"eslint/no-dupe-keys": "off",
|
||||
"eslint/no-duplicate-case": "off",
|
||||
"eslint/no-empty-character-class": "off",
|
||||
"eslint/no-empty-pattern": "off",
|
||||
"eslint/no-empty-static-block": "off",
|
||||
"eslint/no-ex-assign": "off",
|
||||
"eslint/no-extra-boolean-cast": "off",
|
||||
"eslint/no-func-assign": "off",
|
||||
"eslint/no-global-assign": "off",
|
||||
"eslint/no-import-assign": "off",
|
||||
"eslint/no-invalid-regexp": "off",
|
||||
"eslint/no-irregular-whitespace": "off",
|
||||
"eslint/no-loss-of-precision": "off",
|
||||
"eslint/no-misleading-character-class": "off",
|
||||
"eslint/no-new-native-nonconstructor": "off",
|
||||
"eslint/no-nonoctal-decimal-escape": "off",
|
||||
"eslint/no-obj-calls": "off",
|
||||
"eslint/no-self-assign": "off",
|
||||
"eslint/no-setter-return": "off",
|
||||
"eslint/no-shadow-restricted-names": "off",
|
||||
"eslint/no-sparse-arrays": "off",
|
||||
"eslint/no-this-before-super": "off",
|
||||
"eslint/no-unassigned-vars": "off",
|
||||
"eslint/no-unreachable": "off",
|
||||
"eslint/no-unsafe-finally": "off",
|
||||
"eslint/no-unsafe-negation": "off",
|
||||
"eslint/no-unsafe-optional-chaining": "off",
|
||||
"eslint/no-unused-expressions": "off",
|
||||
"eslint/no-unused-labels": "off",
|
||||
"eslint/no-unused-private-class-members": "off",
|
||||
"eslint/no-unused-vars": "off",
|
||||
"eslint/no-useless-backreference": "off",
|
||||
"eslint/no-useless-catch": "off",
|
||||
"eslint/no-useless-escape": "off",
|
||||
"eslint/no-with": "off",
|
||||
"eslint/require-yield": "off",
|
||||
"eslint/use-isnan": "off",
|
||||
"eslint/valid-typeof": "off",
|
||||
"import/default": "off",
|
||||
"react/error-boundaries": "off",
|
||||
"react/exhaustive-deps": "off",
|
||||
"react/globals": "off",
|
||||
"react/immutability": "off",
|
||||
"react/incompatible-library": "off",
|
||||
"react/jsx-key": "off",
|
||||
"react/jsx-no-duplicate-props": "off",
|
||||
"react/jsx-no-undef": "off",
|
||||
"react/no-children-prop": "off",
|
||||
"react/no-danger-with-children": "off",
|
||||
"react/no-direct-mutation-state": "off",
|
||||
"react/no-find-dom-node": "off",
|
||||
"react/no-is-mounted": "off",
|
||||
"react/no-render-return-value": "off",
|
||||
"react/no-string-refs": "off",
|
||||
"react/preserve-manual-memoization": "off",
|
||||
"react/purity": "off",
|
||||
"react/set-state-in-effect": "off",
|
||||
"react/set-state-in-render": "off",
|
||||
"react/static-components": "off",
|
||||
"react/use-memo": "off",
|
||||
"react/void-use-memo": "off",
|
||||
"typescript/no-duplicate-enum-values": "off",
|
||||
"typescript/no-extra-non-null-assertion": "off",
|
||||
"typescript/no-misused-new": "off",
|
||||
"typescript/no-non-null-asserted-optional-chain": "off",
|
||||
"typescript/no-this-alias": "off",
|
||||
"typescript/no-unsafe-declaration-merging": "off",
|
||||
"typescript/no-wrapper-object-types": "off",
|
||||
"typescript/prefer-as-const": "off",
|
||||
"typescript/prefer-namespace-keyword": "off",
|
||||
"typescript/triple-slash-reference": "off",
|
||||
"vitest/no-duplicate-hooks": "error",
|
||||
"vitest/expect-expect": "off",
|
||||
"vitest/no-conditional-expect": "off",
|
||||
"vitest/no-disabled-tests": "off",
|
||||
"vitest/no-focused-tests": "off",
|
||||
"vitest/no-standalone-expect": "off",
|
||||
"vitest/require-local-test-context-for-concurrent-snapshots": "off",
|
||||
"vitest/valid-describe-callback": "off",
|
||||
"vitest/valid-expect": "off",
|
||||
"vitest/valid-expect-in-promise": "off",
|
||||
"vitest/valid-title": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.stories.ts", "**/*.stories.tsx", "scripts/*.ts"],
|
||||
"rules": {
|
||||
"eslint/no-console": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+1
-1
@@ -2,7 +2,7 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylelint.vscode-stylelint",
|
||||
"biomejs.biome",
|
||||
"oxc.oxc-vscode",
|
||||
"esbenp.prettier-vscode",
|
||||
"denoland.vscode-deno",
|
||||
"github.vscode-github-actions",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -13,8 +13,7 @@ files.forEach((file) => {
|
||||
|
||||
data = data.map((x) => {
|
||||
return x.includes("React.ForwardRefExoticComponent")
|
||||
? x.split(":")[0] +
|
||||
`: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement> & SVGRProps & React.RefAttributes<SVGSVGElement>>;`
|
||||
? `${x.split(":")[0]}: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement> & SVGRProps & React.RefAttributes<SVGSVGElement>>;`
|
||||
: x;
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ ymlList.forEach((file) => {
|
||||
const ymlData = load(fs.readFileSync(`${basePath}/${file}`), {
|
||||
schema: JSON_SCHEMA,
|
||||
});
|
||||
// biome-ignore lint/complexity/useOptionalChain: optional chain not supported in enviroment. Consider revriting to Typescript
|
||||
if (ymlData.keywords && ymlData.keywords.includes("[ignore-docs]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"author": "Aksel | Nav",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
@@ -85,7 +85,7 @@
|
||||
"js-yaml": "^5.5.0",
|
||||
"lodash": "4.18.1",
|
||||
"prettier": "3.9.6",
|
||||
"react": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"rehype-parse": "9.0.1",
|
||||
"tsc-alias": "1.9.7",
|
||||
"tsx": "^4.23.15",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* biome-ignore-all lint/correctness/useHookAtTopLevel: `useId` is invariant at runtime. */
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
// https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/useId.ts
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
@@ -43,8 +43,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/ds-css": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"concurrently": "10.0.6",
|
||||
"postcss-selector-parser": "^7.1.7",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
## 8.19.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
|
||||
"author": "Aksel | Nav designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -40,7 +40,7 @@
|
||||
"url": "https://github.com/navikt/aksel/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "5.6.2",
|
||||
"chalk": "6.0.1",
|
||||
"cli-progress": "3.12.0",
|
||||
"clipboardy": "5.3.2",
|
||||
"commander": "15.0.0",
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("getJSXLiteralValue", () => {
|
||||
});
|
||||
|
||||
test("returns null for TemplateLiteral with expressions", () => {
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing only
|
||||
// oxlint-disable-next-line eslint/no-template-curly-in-string -- Testing only
|
||||
const node = getAttrValue("<Component prop={`hello ${name}`} />");
|
||||
expect(getJSXLiteralValue(node)).toBe(null);
|
||||
});
|
||||
|
||||
@@ -209,7 +209,7 @@ describe("getTokenRegex JS", () => {
|
||||
|
||||
expect("ATextRed").toMatch(regex);
|
||||
expect(" ATextRed ").toMatch(regex);
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing token match inside template string
|
||||
// oxlint-disable-next-line eslint/no-template-curly-in-string -- Testing token match inside template string
|
||||
expect(" background: `${ATextRed}` ").toMatch(regex);
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("v8-tokens-js keeps boundaries intact", () => {
|
||||
["const a = ASurfaceDefault;", "const a = BgDefault;"],
|
||||
["const a = [ASurfaceDefault];", "const a = [BgDefault];"],
|
||||
["const a = {...ASurfaceDefault};", "const a = {...BgDefault};"],
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing token inside template literal
|
||||
// oxlint-disable-next-line eslint/no-template-curly-in-string -- Testing token inside template literal
|
||||
["const a = `${ASurfaceDefault}`;", "const a = `${BgDefault}`;"],
|
||||
])("%s", (input, output) => {
|
||||
expect(runJs(`${importLine}\n${input}`)).toBe(
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 8.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Badge: Increase inline padding by 1px for better optical balance ([#5443](https://github.com/navikt/aksel/pull/5443))
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -139,14 +139,14 @@ async function bundle() {
|
||||
|
||||
/* ------------------------------ /global build ----------------------------- */
|
||||
for (const style of BASELINE_CSS_LIST) {
|
||||
function parser(input: string) {
|
||||
const parser = (input: string) => {
|
||||
const parsed = input
|
||||
.split("\n")
|
||||
.filter((line) => line.startsWith("@import"))
|
||||
.filter((line) => line.includes(style))
|
||||
.join("\n");
|
||||
return `${layerDefinition}\n${parsed}`;
|
||||
}
|
||||
};
|
||||
|
||||
await bundleCSS(parser).then((file) => {
|
||||
writeFile({
|
||||
@@ -208,13 +208,13 @@ async function bundle() {
|
||||
}
|
||||
|
||||
for (const componentLine of componentFiles()) {
|
||||
function parser(input: string) {
|
||||
const parser = (input: string) => {
|
||||
const parsed = input
|
||||
.split("\n")
|
||||
.filter((line) => line === componentLine)
|
||||
.join("\n");
|
||||
return `${layerDefinition}\n${parsed}`;
|
||||
}
|
||||
};
|
||||
|
||||
await bundleCSS(parser).then((file) => {
|
||||
const componentName = componentLine
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"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.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"browserslist": "^4.28.7",
|
||||
"esbuild": "^0.28.2",
|
||||
"fast-glob": "3.3.3",
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
justify-content: center;
|
||||
min-width: var(--__axc-badge-size);
|
||||
height: var(--__axc-badge-size);
|
||||
padding: var(--ax-space-4);
|
||||
|
||||
/* 1px optical adjustment inline */
|
||||
padding: var(--ax-space-4) calc(var(--ax-space-4) + var(--ax-space-1));
|
||||
border-radius: var(--ax-radius-16);
|
||||
background: var(--ax-bg-strong);
|
||||
color: var(--ax-text-contrast);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
/* biome-ignore lint/suspicious/noDuplicateProperties: vh is fallback for browsers that do not support lvh */
|
||||
min-height: 100lvh;
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@
|
||||
|
||||
.aksel-page__content--fullheight {
|
||||
min-height: 100vh;
|
||||
/* biome-ignore lint/suspicious/noDuplicateProperties: vh is fallback for browsers that do not support lvh */
|
||||
min-height: 100lvh;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 8.20.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@8.20.1
|
||||
- @navikt/aksel-icons@8.20.1
|
||||
|
||||
## 8.20.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "8.20.0",
|
||||
"version": "8.20.1",
|
||||
"description": "React components from the Norwegian Labour and Welfare Administration.",
|
||||
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
|
||||
"license": "MIT",
|
||||
@@ -788,16 +788,16 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.27.20",
|
||||
"@floating-ui/react-dom": "^2.1.9",
|
||||
"@navikt/aksel-icons": "^8.20.0",
|
||||
"@navikt/ds-tokens": "^8.20.0",
|
||||
"@navikt/aksel-icons": "^8.20.1",
|
||||
"@navikt/ds-tokens": "^8.20.1",
|
||||
"date-fns": "^4.0.0",
|
||||
"react-day-picker": "10.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "10.4.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/dom": "10.4.2",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@testing-library/user-event": "^14.6.7",
|
||||
"@testing-library/user-event": "^14.6.8",
|
||||
"@types/jscodeshift": "17.3.0",
|
||||
"@types/node": "^24.10.9",
|
||||
"@types/react": "19.2.14",
|
||||
@@ -807,8 +807,8 @@
|
||||
"cross-env": "^10.1.0",
|
||||
"jscodeshift": "17.4.0",
|
||||
"jsdom": "29.1.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"react": "19.3.1",
|
||||
"react-dom": "19.3.1",
|
||||
"rimraf": "6.1.3",
|
||||
"swr": "^2.4.2",
|
||||
"tsc-alias": "1.9.7",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useRef } from "react";
|
||||
import { useModalContext } from "../../modal/Modal.context";
|
||||
import { useModalContext } from "../../modal/root/ModalRoot.context";
|
||||
import { useId } from "../../utils-external";
|
||||
import {
|
||||
Menu,
|
||||
|
||||
@@ -16,10 +16,8 @@ const ActionMenuShortcut = ({ children }: ShortcutProps) => {
|
||||
<ActionMenuMarker placement="right">
|
||||
{parsed.map((char, index) => (
|
||||
<span
|
||||
key={`${children}-${
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: This is a static list of characters, so using the index as a key is acceptable in this case
|
||||
index
|
||||
}-${char}`}
|
||||
// oxlint-disable-next-line react/no-array-index-key -- This is a static list of characters, so using the index as a key is acceptable in this case
|
||||
key={`${children}-${index}-${char}`}
|
||||
className="aksel-action-menu__shortcut"
|
||||
>
|
||||
{char}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { PersonIcon } from "@navikt/aksel-icons";
|
||||
import { VStack } from "../primitives/stack";
|
||||
import { renderStoriesForChromatic } from "../utils/renderStoriesForChromatic";
|
||||
import Chat, { POSITIONS, SIZES, VARIANTS } from "./Chat";
|
||||
import { Chat, POSITIONS, SIZES, VARIANTS } from "./root/ChatRoot";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Chat",
|
||||
|
||||
+6
-5
@@ -1,8 +1,8 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { Detail, Heading, type HeadingProps } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { Detail, Heading, type HeadingProps } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Bubble text.
|
||||
*/
|
||||
@@ -26,7 +26,7 @@ export interface ChatBubbleProps extends HTMLAttributes<HTMLDivElement> {
|
||||
toptextHeadingLevel?: Exclude<HeadingProps["level"], "1">;
|
||||
}
|
||||
|
||||
const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
const ChatBubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -65,4 +65,5 @@ const Bubble = forwardRef<HTMLDivElement, ChatBubbleProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export default Bubble;
|
||||
export { ChatBubble };
|
||||
export type { ChatBubbleProps };
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client";
|
||||
export { default as Chat, ChatBubble } from "./Chat";
|
||||
export type { ChatProps, ChatBubbleProps } from "./Chat";
|
||||
export { Chat, ChatBubble } from "./root/ChatRoot";
|
||||
export type { ChatProps, ChatBubbleProps } from "./root/ChatRoot";
|
||||
+33
-38
@@ -1,13 +1,13 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import type { AkselStatusColorRole } from "@navikt/ds-tokens/types";
|
||||
import type { AkselColor } from "../types";
|
||||
import { BodyLong, type HeadingProps } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import Bubble, { type ChatBubbleProps } from "./Bubble";
|
||||
import type { AkselColor } from "../../types";
|
||||
import { BodyLong, type HeadingProps } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { ChatBubble, type ChatBubbleProps } from "../bubble/ChatBubble";
|
||||
|
||||
export const VARIANTS = ["subtle", "info", "neutral"] as const;
|
||||
export const POSITIONS = ["left", "right"] as const;
|
||||
export const SIZES = ["medium", "small"] as const;
|
||||
const VARIANTS = ["subtle", "info", "neutral"] as const;
|
||||
const POSITIONS = ["left", "right"] as const;
|
||||
const SIZES = ["medium", "small"] as const;
|
||||
|
||||
interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
@@ -62,33 +62,7 @@ interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
||||
"data-color"?: Exclude<AkselColor, AkselStatusColorRole>;
|
||||
}
|
||||
|
||||
interface ChatComponent extends React.ForwardRefExoticComponent<
|
||||
ChatProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link ChatBubbleProps}
|
||||
*/
|
||||
Bubble: typeof Bubble;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component for communicating a dialog between two or more parties.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/chat)
|
||||
* @see 🏷️ {@link ChatProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Chat avatar="A" name="Alice" timestamp="01.01.21 14:00">
|
||||
* <Chat.Bubble>Hello!</Chat.Bubble>
|
||||
* <Chat.Bubble>How can I help you?</Chat.Bubble>
|
||||
* </Chat>
|
||||
* <Chat avatar="B" name="Bob" timestamp="01.01.21 14:01" position="right">
|
||||
* <Chat.Bubble>Hi there!</Chat.Bubble>
|
||||
* </Chat>
|
||||
* ```
|
||||
*/
|
||||
export const Chat = forwardRef<HTMLDivElement, ChatProps>(
|
||||
const ChatRoot = forwardRef<HTMLDivElement, ChatProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -143,7 +117,7 @@ export const Chat = forwardRef<HTMLDivElement, ChatProps>(
|
||||
</div>
|
||||
);
|
||||
},
|
||||
) as ChatComponent;
|
||||
);
|
||||
|
||||
function variantToColor(variant: ChatProps["variant"]): AkselColor {
|
||||
switch (variant) {
|
||||
@@ -158,8 +132,29 @@ function variantToColor(variant: ChatProps["variant"]): AkselColor {
|
||||
}
|
||||
}
|
||||
|
||||
Chat.Bubble = Bubble;
|
||||
/**
|
||||
* A component for communicating a dialog between two or more parties.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/chat)
|
||||
* @see 🏷️ {@link ChatProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Chat avatar="A" name="Alice" timestamp="01.01.21 14:00">
|
||||
* <Chat.Bubble>Hello!</Chat.Bubble>
|
||||
* <Chat.Bubble>How can I help you?</Chat.Bubble>
|
||||
* </Chat>
|
||||
* <Chat avatar="B" name="Bob" timestamp="01.01.21 14:01" position="right">
|
||||
* <Chat.Bubble>Hi there!</Chat.Bubble>
|
||||
* </Chat>
|
||||
* ```
|
||||
*/
|
||||
const Chat = Object.assign(ChatRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link ChatBubbleProps}
|
||||
*/
|
||||
Bubble: ChatBubble,
|
||||
});
|
||||
|
||||
export default Chat;
|
||||
export { Bubble as ChatBubble };
|
||||
export { Chat, ChatBubble, VARIANTS, POSITIONS, SIZES };
|
||||
export type { ChatProps, ChatBubbleProps };
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
export { default as Chips, type ChipsProps } from "./root/ChipsRoot";
|
||||
export {
|
||||
ChipsRemovable,
|
||||
type ChipsRemovableProps,
|
||||
} from "./removable/ChipsRemovable";
|
||||
export { ChipsToggle, type ChipsToggleProps } from "./toggle/ChipsToggle";
|
||||
export { Chips, ChipsRemovable, ChipsToggle } from "./root/ChipsRoot";
|
||||
export type {
|
||||
ChipsProps,
|
||||
ChipsRemovableProps,
|
||||
ChipsToggleProps,
|
||||
} from "./root/ChipsRoot";
|
||||
@@ -4,7 +4,7 @@ import type { AkselColor } from "../../types";
|
||||
import { cl, composeEventHandlers } from "../../utils/helpers";
|
||||
import { useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
|
||||
export interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* @deprecated Use `data-color` prop instead.
|
||||
@@ -22,10 +22,7 @@ export interface ChipsRemovableProps extends React.ButtonHTMLAttributes<HTMLButt
|
||||
"data-color"?: AkselColor;
|
||||
}
|
||||
|
||||
export const ChipsRemovable = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ChipsRemovableProps
|
||||
>(
|
||||
const ChipsRemovable = forwardRef<HTMLButtonElement, ChipsRemovableProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -46,6 +43,7 @@ export const ChipsRemovable = forwardRef<
|
||||
data-color={color ?? variantToColor(variant)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
// oxlint-disable-next-line react/button-has-type -- React's button type union is validated by TypeScript and defaults to "button".
|
||||
type={type}
|
||||
className={cl(
|
||||
"aksel-chips__chip aksel-chips__removable aksel-chips--icon-right",
|
||||
@@ -76,4 +74,5 @@ function variantToColor(
|
||||
}
|
||||
}
|
||||
|
||||
export default ChipsRemovable;
|
||||
export { ChipsRemovable };
|
||||
export type { ChipsRemovableProps };
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { type HTMLAttributes, forwardRef } from "react";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { ChipsRemovable } from "../removable/ChipsRemovable";
|
||||
import { ChipsToggle } from "../toggle/ChipsToggle";
|
||||
import {
|
||||
ChipsRemovable,
|
||||
type ChipsRemovableProps,
|
||||
} from "../removable/ChipsRemovable";
|
||||
import { ChipsToggle, type ChipsToggleProps } from "../toggle/ChipsToggle";
|
||||
|
||||
interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
|
||||
children: React.ReactNode;
|
||||
@@ -12,22 +15,24 @@ interface ChipsProps extends HTMLAttributes<HTMLUListElement> {
|
||||
size?: "medium" | "small";
|
||||
}
|
||||
|
||||
interface ChipsComponent extends React.ForwardRefExoticComponent<
|
||||
ChipsProps & React.RefAttributes<HTMLUListElement>
|
||||
> {
|
||||
/**
|
||||
* Toggle between selected-states.
|
||||
* @see 🏷️ {@link ToggleChipsProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Toggle: typeof ChipsToggle;
|
||||
/**
|
||||
* Remove filter or the likes on click.
|
||||
* @see 🏷️ {@link RemovableChipsProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Removable: typeof ChipsRemovable;
|
||||
}
|
||||
const ChipsRoot = forwardRef<HTMLUListElement, ChipsProps>(
|
||||
({ className, size = "medium", children, ...rest }, ref) => {
|
||||
return (
|
||||
<ul
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("aksel-chips", className, `aksel-chips--${size}`, {
|
||||
"aksel-body-short aksel-body-short--small": size === "medium",
|
||||
"aksel-detail aksel-detail--small": size === "small",
|
||||
})}
|
||||
>
|
||||
{React.Children.map(children, (chip, index) => {
|
||||
return <li key={chip?.toString() || index}>{chip}</li>;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* A component that displays a list of items as chips.
|
||||
@@ -56,27 +61,20 @@ interface ChipsComponent extends React.ForwardRefExoticComponent<
|
||||
* </Chips>
|
||||
* ```
|
||||
*/
|
||||
export const Chips = forwardRef<HTMLUListElement, ChipsProps>(
|
||||
({ className, size = "medium", children, ...rest }, ref) => {
|
||||
return (
|
||||
<ul
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("aksel-chips", className, `aksel-chips--${size}`, {
|
||||
"aksel-body-short aksel-body-short--small": size === "medium",
|
||||
"aksel-detail aksel-detail--small": size === "small",
|
||||
})}
|
||||
>
|
||||
{React.Children.map(children, (chip, index) => {
|
||||
return <li key={chip?.toString() || index}>{chip}</li>;
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
) as ChipsComponent;
|
||||
const Chips = Object.assign(ChipsRoot, {
|
||||
/**
|
||||
* Toggle between selected-states.
|
||||
* @see 🏷️ {@link ChipsToggleProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Toggle: ChipsToggle,
|
||||
/**
|
||||
* Remove filter or the likes on click.
|
||||
* @see 🏷️ {@link ChipsRemovableProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Removable: ChipsRemovable,
|
||||
});
|
||||
|
||||
Chips.Toggle = ChipsToggle;
|
||||
Chips.Removable = ChipsRemovable;
|
||||
|
||||
export default Chips;
|
||||
export type { ChipsProps };
|
||||
export { Chips, ChipsRemovable, ChipsToggle };
|
||||
export type { ChipsProps, ChipsRemovableProps, ChipsToggleProps };
|
||||
@@ -3,7 +3,7 @@ import type { AkselColor } from "../../types";
|
||||
import type { OverridableComponent } from "../../utils-external";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
export interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* Toggles aria-pressed and visual changes
|
||||
@@ -26,68 +26,66 @@ export interface ChipsToggleProps extends React.ButtonHTMLAttributes<HTMLButtonE
|
||||
checkmark?: boolean;
|
||||
}
|
||||
|
||||
export const ChipsToggle: OverridableComponent<
|
||||
ChipsToggleProps,
|
||||
HTMLButtonElement
|
||||
> = forwardRef(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
selected,
|
||||
variant,
|
||||
checkmark = true,
|
||||
as: Component = "button",
|
||||
"data-color": color,
|
||||
...rest
|
||||
const ChipsToggle: OverridableComponent<ChipsToggleProps, HTMLButtonElement> =
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
className,
|
||||
children,
|
||||
selected,
|
||||
variant,
|
||||
checkmark = true,
|
||||
as: Component = "button",
|
||||
"data-color": color,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<Component
|
||||
data-color={color ?? variantToColor(variant)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("aksel-chips__chip aksel-chips__toggle", className, {
|
||||
"aksel-chips__toggle--with-checkmark": checkmark,
|
||||
})}
|
||||
aria-pressed={selected}
|
||||
data-pressed={selected}
|
||||
>
|
||||
{checkmark && (
|
||||
<svg
|
||||
aria-hidden
|
||||
className="aksel-chips__toggle-icon"
|
||||
width="1.25em"
|
||||
height="1.25em"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable={false}
|
||||
>
|
||||
{selected ? (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 18.125C14.4873 18.125 18.125 14.4873 18.125 10C18.125 5.51269 14.4873 1.875 10 1.875C5.51269 1.875 1.875 5.51269 1.875 10C1.875 14.4873 5.51269 18.125 10 18.125ZM14.128 7.72904C14.3695 7.44357 14.3339 7.01635 14.0485 6.7748C13.763 6.53326 13.3358 6.56886 13.0942 6.85432L8.60428 12.1606L6.41627 9.97263C6.15185 9.70822 5.72315 9.70822 5.45873 9.97263C5.19431 10.2371 5.19431 10.6658 5.45873 10.9302L8.16706 13.6385C8.30095 13.7724 8.48479 13.8441 8.67397 13.8362C8.86316 13.8284 9.0404 13.7416 9.16271 13.5971L14.128 7.72904Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 3.125C6.20304 3.125 3.125 6.20304 3.125 10C3.125 13.797 6.20304 16.875 10 16.875C13.797 16.875 16.875 13.797 16.875 10C16.875 6.20304 13.797 3.125 10 3.125ZM1.875 10C1.875 5.51269 5.51269 1.875 10 1.875C14.4873 1.875 18.125 5.51269 18.125 10C18.125 14.4873 14.4873 18.125 10 18.125C5.51269 18.125 1.875 14.4873 1.875 10Z"
|
||||
/* After removing old fallbacks, change to currentColor */
|
||||
fill="var(--ax-text-default)"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
<span className="aksel-chips__chip-text">{children}</span>
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<Component
|
||||
data-color={color ?? variantToColor(variant)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("aksel-chips__chip aksel-chips__toggle", className, {
|
||||
"aksel-chips__toggle--with-checkmark": checkmark,
|
||||
})}
|
||||
aria-pressed={selected}
|
||||
data-pressed={selected}
|
||||
>
|
||||
{checkmark && (
|
||||
<svg
|
||||
aria-hidden
|
||||
className="aksel-chips__toggle-icon"
|
||||
width="1.25em"
|
||||
height="1.25em"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable={false}
|
||||
>
|
||||
{selected ? (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 18.125C14.4873 18.125 18.125 14.4873 18.125 10C18.125 5.51269 14.4873 1.875 10 1.875C5.51269 1.875 1.875 5.51269 1.875 10C1.875 14.4873 5.51269 18.125 10 18.125ZM14.128 7.72904C14.3695 7.44357 14.3339 7.01635 14.0485 6.7748C13.763 6.53326 13.3358 6.56886 13.0942 6.85432L8.60428 12.1606L6.41627 9.97263C6.15185 9.70822 5.72315 9.70822 5.45873 9.97263C5.19431 10.2371 5.19431 10.6658 5.45873 10.9302L8.16706 13.6385C8.30095 13.7724 8.48479 13.8441 8.67397 13.8362C8.86316 13.8284 9.0404 13.7416 9.16271 13.5971L14.128 7.72904Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 3.125C6.20304 3.125 3.125 6.20304 3.125 10C3.125 13.797 6.20304 16.875 10 16.875C13.797 16.875 16.875 13.797 16.875 10C16.875 6.20304 13.797 3.125 10 3.125ZM1.875 10C1.875 5.51269 5.51269 1.875 10 1.875C14.4873 1.875 18.125 5.51269 18.125 10C18.125 14.4873 14.4873 18.125 10 18.125C5.51269 18.125 1.875 14.4873 1.875 10Z"
|
||||
/* After removing old fallbacks, change to currentColor */
|
||||
fill="var(--ax-text-default)"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
<span className="aksel-chips__chip-text">{children}</span>
|
||||
</Component>
|
||||
);
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
function variantToColor(
|
||||
variant?: ChipsToggleProps["variant"],
|
||||
@@ -102,4 +100,5 @@ function variantToColor(
|
||||
}
|
||||
}
|
||||
|
||||
export default ChipsToggle;
|
||||
export { ChipsToggle };
|
||||
export type { ChipsToggleProps };
|
||||
@@ -149,7 +149,7 @@ export namespace DataGrid {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace Table {
|
||||
// biome-ignore lint/suspicious/noShadow: intentional namespace re-export
|
||||
// oxlint-disable-next-line eslint/no-shadow -- intentional namespace re-export
|
||||
export type Props<T = unknown> = DataGridTable.Props<T>;
|
||||
export type Sorting = DataGridTable.Sorting;
|
||||
export type SortEntry = DataGridTable.SortEntry;
|
||||
@@ -161,7 +161,7 @@ export namespace DataGrid {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace Preferences {
|
||||
// biome-ignore lint/suspicious/noShadow: intentional namespace re-export
|
||||
// oxlint-disable-next-line eslint/no-shadow -- intentional namespace re-export
|
||||
export type Props = DataGridPreferences.Props;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/** biome-ignore-all lint/correctness/useHookAtTopLevel: False positive because of the way forwardRef() is added */
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
|
||||
@@ -139,7 +139,6 @@ function useTableColumnResize({
|
||||
[resizeMin, resizeMax, setWidth],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We only want to run this on mount and when autoResizeOnce changes
|
||||
useEffect(
|
||||
function autoResizeColumn() {
|
||||
if (!autoResizeOnce) {
|
||||
|
||||
@@ -220,7 +220,6 @@ export const TokenFilter = forwardRef<HTMLDivElement, TokenFilterProps>(
|
||||
);
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/useSemanticElements: search-tag is too new (baseline 2023)
|
||||
<div
|
||||
ref={ref}
|
||||
className={cl("aksel-property-filter", className)}
|
||||
|
||||
@@ -61,7 +61,7 @@ function createGroups<T>(
|
||||
}
|
||||
|
||||
/* Custom groups first, then default group if it has items */
|
||||
const groups: OptionGroup<T>[] = [...Object.values(customGroups)];
|
||||
const groups: OptionGroup<T>[] = Object.values(customGroups);
|
||||
|
||||
if (defaultGroup.options.length > 0) {
|
||||
groups.push(defaultGroup);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isSameDay } from "date-fns";
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "../button";
|
||||
import { Dialog } from "../dialog";
|
||||
import Modal from "../modal/Modal";
|
||||
import { Modal } from "../modal";
|
||||
import { Box } from "../primitives/box";
|
||||
import { HGrid } from "../primitives/grid";
|
||||
import { HStack, Spacer, VStack } from "../primitives/stack";
|
||||
@@ -49,16 +49,15 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
toDate: new Date("Aug 23 2029"),
|
||||
});
|
||||
|
||||
const newProps = {
|
||||
...(!props.inputfield || props.mode === "multiple"
|
||||
const newProps =
|
||||
!props.inputfield || props.mode === "multiple"
|
||||
? {
|
||||
open,
|
||||
onClose: () => setOpen(false),
|
||||
fromDate: new Date("Aug 23 2020"),
|
||||
toDate: new Date("Aug 23 2029"),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
: {};
|
||||
|
||||
const Comp = !props.standalone ? DatePicker : DatePicker.Standalone;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ const DatePickerDayButton = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
{...rest}
|
||||
ref={ref}
|
||||
aria-hidden={day.outside}
|
||||
|
||||
@@ -104,6 +104,7 @@ const ReactDayPicker = ({
|
||||
selected={selected}
|
||||
classNames={LegacyClassNames}
|
||||
components={{
|
||||
// oxlint-disable-next-line react/jsx-no-useless-fragment -- react-day-picker requires an Element return type, so null is not allowed.
|
||||
MonthCaption: () => <></>,
|
||||
DayButton: useCallback(
|
||||
(props) => <DatePickerDayButton {...props} locale={locale} />,
|
||||
|
||||
@@ -23,7 +23,7 @@ const DatePickerWeekRow = ({
|
||||
|
||||
return (
|
||||
<Show below="sm" asChild>
|
||||
{/** biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: role="grid" improves a11y reading */}
|
||||
{/* oxlint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role -- role="grid" improves a11y reading */}
|
||||
<table className="rdp-table" role="grid">
|
||||
<tbody className="rdp-tbody">
|
||||
<tr className="rdp-row aksel-date__week-row">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { useModalContext } from "../../modal/Modal.context";
|
||||
import { useModalContext } from "../../modal/root/ModalRoot.context";
|
||||
import { Portal, type PortalProps } from "../../portal";
|
||||
import { DialogBackdropInternal } from "../backdrop/DialogBackdropInternal";
|
||||
import { useDialogContext } from "../root/DialogRoot.context";
|
||||
|
||||
+7
-6
@@ -1,10 +1,10 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { useThemeInternal } from "../theme/Theme";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
import { useThemeInternal } from "../../theme/Theme";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
|
||||
|
||||
export interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -44,4 +44,5 @@ const ExpansionCardContent = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export default ExpansionCardContent;
|
||||
export { ExpansionCardContent };
|
||||
export type { ExpansionCardContentProps };
|
||||
@@ -1,13 +0,0 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
export type ExpansionCardContextProps = {
|
||||
open: boolean;
|
||||
toggleOpen: () => void;
|
||||
size: "medium" | "small";
|
||||
};
|
||||
|
||||
export const ExpansionCardContext = createContext<ExpansionCardContextProps>({
|
||||
open: false,
|
||||
toggleOpen: () => {},
|
||||
size: "medium",
|
||||
});
|
||||
+7
-6
@@ -1,13 +1,13 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../typography";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
|
||||
|
||||
export interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ExpansionCardDescription = forwardRef<
|
||||
const ExpansionCardDescription = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
ExpansionCardDescriptionProps
|
||||
>(({ className, ...rest }, ref) => {
|
||||
@@ -31,4 +31,5 @@ export const ExpansionCardDescription = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export default ExpansionCardDescription;
|
||||
export { ExpansionCardDescription };
|
||||
export type { ExpansionCardDescriptionProps };
|
||||
+6
-5
@@ -1,10 +1,10 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { useI18n } from "../utils/i18n/i18n.hooks";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useI18n } from "../../utils/i18n/i18n.hooks";
|
||||
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
|
||||
|
||||
export interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -46,4 +46,5 @@ const ExpansionCardHeader = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export default ExpansionCardHeader;
|
||||
export { ExpansionCardHeader };
|
||||
export type { ExpansionCardHeaderProps };
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
export {
|
||||
type ExpansionCardProps,
|
||||
default as ExpansionCard,
|
||||
} from "./ExpansionCard";
|
||||
export { default as ExpansionCardContent } from "./ExpansionCardContent";
|
||||
export { default as ExpansionCardHeader } from "./ExpansionCardHeader";
|
||||
export { default as ExpansionCardTitle } from "./ExpansionCardTitle";
|
||||
export { default as ExpansionCardDescription } from "./ExpansionCardDescription";
|
||||
ExpansionCard,
|
||||
ExpansionCardContent,
|
||||
ExpansionCardHeader,
|
||||
ExpansionCardTitle,
|
||||
ExpansionCardDescription,
|
||||
} from "./root/ExpansionCardRoot";
|
||||
export type { ExpansionCardProps } from "./root/ExpansionCardRoot";
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
type ExpansionCardContextProps = {
|
||||
open: boolean;
|
||||
toggleOpen: () => void;
|
||||
size: "medium" | "small";
|
||||
};
|
||||
|
||||
const ExpansionCardContext = createContext<ExpansionCardContextProps>({
|
||||
open: false,
|
||||
toggleOpen: () => {},
|
||||
size: "medium",
|
||||
});
|
||||
|
||||
export { ExpansionCardContext };
|
||||
export type { ExpansionCardContextProps };
|
||||
+66
-65
@@ -1,51 +1,24 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import type { AkselColor } from "../types";
|
||||
import type { OverridableComponent } from "../utils-external";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { useControllableState } from "../utils/hooks";
|
||||
import ExpansionCardContent, {
|
||||
import type { AkselColor } from "../../types";
|
||||
import { cl } from "../../utils/helpers";
|
||||
import { useControllableState } from "../../utils/hooks";
|
||||
import {
|
||||
ExpansionCardContent,
|
||||
type ExpansionCardContentProps,
|
||||
} from "./ExpansionCardContent";
|
||||
} from "../content/ExpansionCardContent";
|
||||
import {
|
||||
ExpansionCardDescription,
|
||||
type ExpansionCardDescriptionProps,
|
||||
} from "./ExpansionCardDescription";
|
||||
import ExpansionCardHeader, {
|
||||
} from "../description/ExpansionCardDescription";
|
||||
import {
|
||||
ExpansionCardHeader,
|
||||
type ExpansionCardHeaderProps,
|
||||
} from "./ExpansionCardHeader";
|
||||
} from "../header/ExpansionCardHeader";
|
||||
import {
|
||||
ExpansionCardTitle,
|
||||
type ExpansionCardTitleProps,
|
||||
} from "./ExpansionCardTitle";
|
||||
import { ExpansionCardContext } from "./context";
|
||||
|
||||
interface ExpansionCardComponent extends React.ForwardRefExoticComponent<
|
||||
ExpansionCardProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardHeaderProps}
|
||||
*/
|
||||
Header: React.ForwardRefExoticComponent<
|
||||
ExpansionCardHeaderProps & React.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardTitleProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Title: OverridableComponent<ExpansionCardTitleProps, HTMLHeadingElement>;
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardDescriptionProps}
|
||||
*/
|
||||
Description: React.ForwardRefExoticComponent<
|
||||
ExpansionCardDescriptionProps & React.RefAttributes<HTMLParagraphElement>
|
||||
>;
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardContentProps}
|
||||
*/
|
||||
Content: React.ForwardRefExoticComponent<
|
||||
ExpansionCardContentProps & React.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
}
|
||||
} from "../title/ExpansionCardTitle";
|
||||
import { ExpansionCardContext } from "./ExpansionCardRoot.context";
|
||||
|
||||
interface ExpansionCardCommonProps extends Omit<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
@@ -93,28 +66,10 @@ type ExpansionCardConditionalProps =
|
||||
"aria-labelledby": string;
|
||||
};
|
||||
|
||||
export type ExpansionCardProps = ExpansionCardCommonProps &
|
||||
type ExpansionCardProps = ExpansionCardCommonProps &
|
||||
ExpansionCardConditionalProps;
|
||||
|
||||
/**
|
||||
* A component that displays an expandable card.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
|
||||
* @see 🏷️ {@link ExpansionCardProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ExpansionCard aria-label="Utbetaling av sykepenger">
|
||||
* <ExpansionCard.Header>
|
||||
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
|
||||
* </ExpansionCard.Header>
|
||||
* <ExpansionCard.Content>
|
||||
* <Innhold />
|
||||
* </ExpansionCard.Content>
|
||||
* </ExpansionCard>
|
||||
* ```
|
||||
*/
|
||||
export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
|
||||
const ExpansionCardRoot = forwardRef<HTMLDivElement, ExpansionCardProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
@@ -154,11 +109,57 @@ export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
|
||||
</ExpansionCardContext.Provider>
|
||||
);
|
||||
},
|
||||
) as ExpansionCardComponent;
|
||||
);
|
||||
|
||||
ExpansionCard.Header = ExpansionCardHeader;
|
||||
ExpansionCard.Content = ExpansionCardContent;
|
||||
ExpansionCard.Title = ExpansionCardTitle;
|
||||
ExpansionCard.Description = ExpansionCardDescription;
|
||||
/**
|
||||
* A component that displays an expandable card.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
|
||||
* @see 🏷️ {@link ExpansionCardProps}
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ExpansionCard aria-label="Utbetaling av sykepenger">
|
||||
* <ExpansionCard.Header>
|
||||
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
|
||||
* </ExpansionCard.Header>
|
||||
* <ExpansionCard.Content>
|
||||
* <Innhold />
|
||||
* </ExpansionCard.Content>
|
||||
* </ExpansionCard>
|
||||
* ```
|
||||
*/
|
||||
const ExpansionCard = Object.assign(ExpansionCardRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardHeaderProps}
|
||||
*/
|
||||
Header: ExpansionCardHeader,
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardTitleProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*/
|
||||
Title: ExpansionCardTitle,
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardDescriptionProps}
|
||||
*/
|
||||
Description: ExpansionCardDescription,
|
||||
/**
|
||||
* @see 🏷️ {@link ExpansionCardContentProps}
|
||||
*/
|
||||
Content: ExpansionCardContent,
|
||||
});
|
||||
|
||||
export default ExpansionCard;
|
||||
export {
|
||||
ExpansionCard,
|
||||
ExpansionCardHeader,
|
||||
ExpansionCardTitle,
|
||||
ExpansionCardDescription,
|
||||
ExpansionCardContent,
|
||||
};
|
||||
export type {
|
||||
ExpansionCardProps,
|
||||
ExpansionCardHeaderProps,
|
||||
ExpansionCardTitleProps,
|
||||
ExpansionCardDescriptionProps,
|
||||
ExpansionCardContentProps,
|
||||
};
|
||||
+6
-5
@@ -1,8 +1,8 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import type { OverridableComponent } from "../utils-external";
|
||||
import { cl } from "../utils/helpers";
|
||||
import type { OverridableComponent } from "../../utils-external";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
||||
interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Changes text-sizing
|
||||
@@ -11,7 +11,7 @@ export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadin
|
||||
size?: "large" | "medium" | "small";
|
||||
}
|
||||
|
||||
export const ExpansionCardTitle: OverridableComponent<
|
||||
const ExpansionCardTitle: OverridableComponent<
|
||||
ExpansionCardTitleProps,
|
||||
HTMLHeadingElement
|
||||
> = forwardRef(
|
||||
@@ -32,4 +32,5 @@ export const ExpansionCardTitle: OverridableComponent<
|
||||
},
|
||||
);
|
||||
|
||||
export default ExpansionCardTitle;
|
||||
export { ExpansionCardTitle };
|
||||
export type { ExpansionCardTitleProps };
|
||||
@@ -26,8 +26,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
props;
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: clickable div for checkbox card
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: clickable div for checkbox card
|
||||
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- clickable div for checkbox card
|
||||
<div
|
||||
className={cl(className, "aksel-checkbox", `aksel-checkbox--${size}`, {
|
||||
"aksel-checkbox--error": hasError,
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface CheckboxProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Visually hides label and description, and makes it viewable for screen-readers only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,6 @@ const ComboboxWrapper = ({
|
||||
}
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: Wrapper for combobox input and chips.
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className={cl(
|
||||
|
||||
@@ -52,7 +52,7 @@ const AddNewOption = () => {
|
||||
activeDescendantId === filteredOptionsUtil.getAddNewOptionId(id),
|
||||
},
|
||||
)}
|
||||
// biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: List item acts as an option in the combobox.
|
||||
// oxlint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role -- List item acts as an option in the combobox.
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
>
|
||||
|
||||
@@ -113,7 +113,7 @@ const FilteredOptions = () => {
|
||||
{shouldRenderFilteredOptionsList && (
|
||||
<ul
|
||||
ref={setFilteredOptionsRef}
|
||||
// biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: The <ul> element acts as a listbox in the combobox.
|
||||
// oxlint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role -- The <ul> element acts as a listbox in the combobox.
|
||||
role="listbox"
|
||||
className="aksel-combobox__list-options"
|
||||
>
|
||||
|
||||
@@ -76,7 +76,7 @@ const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
}}
|
||||
// biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: List item acts as an option in the combobox.
|
||||
// oxlint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role -- List item acts as an option in the combobox.
|
||||
role="option"
|
||||
aria-selected={isInList(option.value, selectedOptions)}
|
||||
aria-disabled={isDisabled(option) || undefined}
|
||||
|
||||
@@ -54,8 +54,7 @@ export const InputController = forwardRef<
|
||||
|
||||
return (
|
||||
<Floating.Anchor asChild ref={setAnchorRef}>
|
||||
{/** biome-ignore lint/a11y/noStaticElementInteractions: Acts as wrapper list-toggle and input */}
|
||||
{/** biome-ignore lint/a11y/useKeyWithClickEvents: key-events are handled by input itself */}
|
||||
{/* oxlint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- Acts as wrapper list-toggle and input; key-events are handled by input itself */}
|
||||
<div
|
||||
className={cl("aksel-combobox__wrapper-inner aksel-text-field__input", {
|
||||
"aksel-combobox__wrapper-inner--virtually-unfocused":
|
||||
|
||||
@@ -112,7 +112,6 @@ const SelectedOptionsProvider = ({
|
||||
const newHideCaret =
|
||||
isLimitReached || (!isMultiSelect && selectedOptions.length > 0);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We explicitly want to run this effect when selectedOptions changes to match the view with the selected options.
|
||||
useEffect(() => {
|
||||
setHideCaret(newHideCaret);
|
||||
}, [newHideCaret, selectedOptions, setHideCaret]);
|
||||
|
||||
@@ -60,8 +60,7 @@ export interface ComboboxProps
|
||||
*/
|
||||
filteredOptions?: string[] | ComboboxOption[];
|
||||
/**
|
||||
* Optionally hide the label visually.
|
||||
* Not recommended, but can be considered for e.g. search fields in the top menu.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,6 @@ const highlightSubstring = (text: string, substring: string) => {
|
||||
const end = text.substring(indexOfHighlightedText + substring.length);
|
||||
return (
|
||||
// Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text
|
||||
// biome-ignore lint/a11y/useAriaPropsSupportedByRole: Doesn't matter if aria-label doesn't work
|
||||
<span aria-label={text}>
|
||||
{start}
|
||||
{highlight && <mark>{highlight}</mark>}
|
||||
|
||||
@@ -28,6 +28,7 @@ const ComboboxTrigger = ({
|
||||
return (
|
||||
<Floating.Anchor asChild ref={rootContext.triggerRef}>
|
||||
<Slot
|
||||
// oxlint-disable-next-line jsx-a11y/role-has-required-aria-props -- aria-controls can be supplied through rest or the slotted trigger.
|
||||
role="combobox"
|
||||
aria-expanded={readOnly ? undefined : rootContext.open}
|
||||
onClick={() => !readOnly && rootContext.setOpen(!rootContext.open)}
|
||||
|
||||
@@ -85,7 +85,6 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
const mergedRef = useMergeRefs(ref, wrapperRef);
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: Allows focus-calls on div to move focus to heading.
|
||||
<div
|
||||
ref={mergedRef}
|
||||
{...rest}
|
||||
|
||||
@@ -90,8 +90,7 @@ const FileUploadDropzone = forwardRef<
|
||||
{description}
|
||||
</BodyShort>
|
||||
)}
|
||||
{/** biome-ignore lint/a11y/noStaticElementInteractions: Assistive technologies will use the input within. */}
|
||||
{/** biome-ignore lint/a11y/useKeyWithClickEvents: Keyboard will access input/button within */}
|
||||
{/* oxlint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- Assistive technologies and keyboard users access the input/button within. */}
|
||||
<div
|
||||
className="aksel-dropzone__area"
|
||||
onDragEnter={dropzoneCtx.onDragEnter}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { isAcceptedSize } from "./is-accepted-size";
|
||||
|
||||
describe("isAcceptedSize", () => {
|
||||
it("should return true if maxSize is less than or equal to 0", () => {
|
||||
const file = new File([new Array(5000).join("a")], "filename.txt", {
|
||||
const file = new File(["a".repeat(4999)], "filename.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
expect(isAcceptedSize(file, -1)).toBe(true);
|
||||
@@ -10,7 +10,7 @@ describe("isAcceptedSize", () => {
|
||||
});
|
||||
|
||||
it("should return true if file size is less than or equal to maxSize", () => {
|
||||
const file = new File([new Array(5000).join("a")], "filename.txt", {
|
||||
const file = new File(["a".repeat(4999)], "filename.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
expect(isAcceptedSize(file, 5000)).toBe(true);
|
||||
@@ -18,7 +18,7 @@ describe("isAcceptedSize", () => {
|
||||
});
|
||||
|
||||
it("should return false if file size is greater than maxSize", () => {
|
||||
const file = new File([new Array(5000).join("a")], "filename.txt", {
|
||||
const file = new File(["a".repeat(4999)], "filename.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
expect(isAcceptedSize(file, 4000)).toBe(false);
|
||||
|
||||
@@ -22,8 +22,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
|
||||
const { className, description, children } = props;
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: clickable div for radio card
|
||||
// biome-ignore lint/a11y/useKeyWithClickEvents: clickable div for radio card
|
||||
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events -- clickable div for radio card
|
||||
<div
|
||||
className={cl(className, "aksel-radio", `aksel-radio--${size}`, {
|
||||
"aksel-radio--error": hasError,
|
||||
|
||||
@@ -39,7 +39,7 @@ export interface SearchProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Shows label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
* @default true
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
@@ -158,7 +158,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
||||
clearButton && !inputProps.disabled && (value ?? internalValue);
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: Escape key handler for clearing input
|
||||
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- Escape key handler for clearing input
|
||||
<div
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Escape") {
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface SelectProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* Shows label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface SwitchProps
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface TextareaProps
|
||||
*/
|
||||
label: React.ReactNode;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only.
|
||||
* Hides the label and description visually while keeping them available to screen readers.
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface TextFieldProps
|
||||
*/
|
||||
htmlSize?: number;
|
||||
/**
|
||||
* If enabled shows the label and description for screenreaders only
|
||||
* Hides the label and description visually while keeping them available to screen readers
|
||||
*/
|
||||
hideLabel?: boolean;
|
||||
/**
|
||||
|
||||
@@ -80,7 +80,7 @@ export const useFormField = (
|
||||
const showErrorMsg =
|
||||
!disabled && !readOnly && !!error && typeof error !== "boolean";
|
||||
|
||||
const ariaInvalid = { ...(hasError ? { "aria-invalid": true } : {}) };
|
||||
const ariaInvalid = hasError ? { "aria-invalid": true } : {};
|
||||
|
||||
if ((props as any)?.required) {
|
||||
consoleWarning(
|
||||
|
||||
@@ -52,15 +52,15 @@ export const Variants: StoryFn = () => {
|
||||
<VStack gap="space-16">
|
||||
{variants.map((variant, i) => (
|
||||
<Alert key={variant} variant={variant}>
|
||||
{new Array(i + 1).fill(
|
||||
"Id elit esse enim reprehenderit enim nisi veniam nostrud. ",
|
||||
{"Id elit esse enim reprehenderit enim nisi veniam nostrud. ".repeat(
|
||||
i + 1,
|
||||
)}
|
||||
</Alert>
|
||||
))}
|
||||
{variants.map((variant, i) => (
|
||||
<Alert key={variant} variant={variant} size="small">
|
||||
{new Array(i + 1).fill(
|
||||
"Id elit esse enim reprehenderit enim nisi veniam nostrud. ",
|
||||
{"Id elit esse enim reprehenderit enim nisi veniam nostrud. ".repeat(
|
||||
i + 1,
|
||||
)}
|
||||
</Alert>
|
||||
))}
|
||||
|
||||
@@ -102,7 +102,6 @@ export const Stepper: StepperComponent = forwardRef<
|
||||
className="aksel-stepper__item"
|
||||
data-interactive={isInteractive}
|
||||
data-color={isInteractive ? undefined : "neutral"}
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: Unsure how to fix gracefully
|
||||
key={index + (children?.toString?.() ?? "")}
|
||||
>
|
||||
<span className="aksel-stepper__line aksel-stepper__line--1" />
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
LinkCardIcon,
|
||||
LinkCardImage,
|
||||
LinkCardTitle,
|
||||
} from "./LinkCard";
|
||||
} from "./index";
|
||||
|
||||
type Story = StoryObj<typeof LinkCard>;
|
||||
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
LinkAnchor,
|
||||
type LinkAnchorProps,
|
||||
} from "../../utils/components/link-anchor";
|
||||
|
||||
type LinkCardAnchorProps = LinkAnchorProps;
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link LinkCardAnchorProps}
|
||||
*/
|
||||
const LinkCardAnchor = LinkAnchor;
|
||||
|
||||
export { LinkCardAnchor };
|
||||
export type { LinkCardAnchorProps };
|
||||
@@ -0,0 +1,32 @@
|
||||
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 };
|
||||
@@ -0,0 +1,29 @@
|
||||
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 };
|
||||
@@ -0,0 +1,27 @@
|
||||
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 };
|
||||
@@ -0,0 +1,46 @@
|
||||
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 "./LinkCard";
|
||||
} from "./root/LinkCardRoot";
|
||||
|
||||
export type {
|
||||
LinkCardProps,
|
||||
@@ -17,4 +17,4 @@ export type {
|
||||
LinkCardAnchorProps,
|
||||
LinkCardIconProps,
|
||||
LinkCardImageProps,
|
||||
} from "./LinkCard";
|
||||
} from "./root/LinkCardRoot";
|
||||
@@ -0,0 +1,14 @@
|
||||
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 };
|
||||
@@ -0,0 +1,177 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
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 };
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { Box } from "../primitives/box";
|
||||
import { VStack } from "../primitives/stack";
|
||||
import { BodyLong } from "../typography";
|
||||
import List from "./List";
|
||||
import { List } from "./index";
|
||||
|
||||
export default {
|
||||
title: "ds-react/List",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* HTML list element to render.
|
||||
@@ -16,7 +16,7 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
size?: "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
/**
|
||||
* List item content.
|
||||
*/
|
||||
@@ -30,3 +30,5 @@ export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type { ListProps, ListItemProps };
|
||||
@@ -1,3 +1,3 @@
|
||||
"use client";
|
||||
export { default as List, ListItem } from "./List";
|
||||
export type { ListProps, ListItemProps } from "./List";
|
||||
export { List, ListItem } from "./root/ListRoot";
|
||||
export type { ListProps, ListItemProps } from "./root/ListRoot";
|
||||
+9
-7
@@ -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 { ListContext } from "./List.context";
|
||||
import type { ListItemProps } from "./List.types";
|
||||
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";
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
({ className, children, title, icon, ...rest }, ref) => {
|
||||
const { listType, size } = useContext(ListContext);
|
||||
|
||||
@@ -57,4 +57,6 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
);
|
||||
|
||||
ListItem.displayName = "List.Item";
|
||||
export default ListItem;
|
||||
|
||||
export { ListItem };
|
||||
export type { ListItemProps };
|
||||
+4
-2
@@ -1,12 +1,14 @@
|
||||
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>;
|
||||
}
|
||||
|
||||
export const ListContext = createContext<ListContextProps>({
|
||||
const ListContext = createContext<ListContextProps>({
|
||||
listType: "ul",
|
||||
size: "medium",
|
||||
});
|
||||
|
||||
export { ListContext };
|
||||
+29
-34
@@ -1,35 +1,11 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
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";
|
||||
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";
|
||||
|
||||
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>(
|
||||
const ListRoot = forwardRef<HTMLDivElement, ListProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
@@ -71,10 +47,29 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
|
||||
</ListContext.Provider>
|
||||
);
|
||||
},
|
||||
) as ListComponent;
|
||||
);
|
||||
|
||||
List.Item = 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>
|
||||
* ```
|
||||
*/
|
||||
const List = Object.assign(ListRoot, {
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
Item: ListItem,
|
||||
});
|
||||
|
||||
export default List;
|
||||
export { ListItem };
|
||||
export { List, ListItem };
|
||||
export type { ListProps, ListItemProps };
|
||||
@@ -89,7 +89,6 @@ export const Lookup = forwardRef<HTMLSpanElement, LookupProps>(
|
||||
|
||||
return (
|
||||
<>
|
||||
{/** biome-ignore lint/a11y/useSemanticElements: We use a span since the built-in button styling does not allow for proper line-break. Lookup-text needs to be able to "flow" across multiple lines. */}
|
||||
<span
|
||||
{...rest}
|
||||
role="button"
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Checkbox, CheckboxGroup } from "../form/checkbox";
|
||||
import { VStack } from "../primitives/stack";
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { BodyLong, Heading } from "../typography";
|
||||
import Modal from "./Modal";
|
||||
import { Modal } from "./index";
|
||||
|
||||
const meta: Meta<typeof Modal> = {
|
||||
title: "ds-react/Modal",
|
||||
@@ -149,26 +149,24 @@ export const EmptyHeader: StoryFn = () => (
|
||||
);
|
||||
|
||||
export const SizeSmall: StoryFn = () => (
|
||||
<>
|
||||
<Modal
|
||||
open
|
||||
onClose={() => null}
|
||||
header={{
|
||||
heading: "Simple header",
|
||||
size: "small",
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong>Lorem ipsum dolor sit amet.</BodyLong>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button size="small" variant="secondary">
|
||||
Dummy button
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
<Modal
|
||||
open
|
||||
onClose={() => null}
|
||||
header={{
|
||||
heading: "Simple header",
|
||||
size: "small",
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong>Lorem ipsum dolor sit amet.</BodyLong>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button size="small" variant="secondary">
|
||||
Dummy button
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
SizeSmall.storyName = "Size = Small";
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { expectTypeOf } from "vitest";
|
||||
import type { ModalProps } from "./types";
|
||||
import type { ModalProps } from "./Modal.types";
|
||||
|
||||
test("ModalProps works as intended", () => {
|
||||
expectTypeOf({
|
||||
@@ -93,7 +93,7 @@ interface ModalPropsBase extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
||||
}
|
||||
|
||||
// Require onClose if you use open & Require either header, aria-labelledby or aria-label
|
||||
export type ModalProps = ModalPropsBase &
|
||||
type ModalProps = ModalPropsBase &
|
||||
(
|
||||
| { onClose: ModalPropsBase["onClose"]; open: boolean | undefined }
|
||||
| { onClose?: ModalPropsBase["onClose"]; open?: never }
|
||||
@@ -117,3 +117,5 @@ export type ModalProps = ModalPropsBase &
|
||||
| { "aria-labelledby": string; "aria-label"?: never }
|
||||
| { "aria-labelledby"?: never; "aria-label": string }
|
||||
);
|
||||
|
||||
export type { ModalProps };
|
||||
@@ -1,57 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import type { ModalProps } from "./types";
|
||||
|
||||
export interface MouseCoordinates {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}
|
||||
|
||||
export const coordsAreInside = (
|
||||
{ clientX, clientY }: MouseCoordinates,
|
||||
{ left, top, right, bottom }: DOMRect,
|
||||
) => {
|
||||
if (clientX < left || clientY < top) return false;
|
||||
if (clientX > right || clientY > bottom) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
export function getCloseHandler(
|
||||
modalRef: React.RefObject<HTMLDialogElement | null>,
|
||||
header: ModalProps["header"],
|
||||
onBeforeClose: ModalProps["onBeforeClose"],
|
||||
) {
|
||||
if (header && header.closeButton === false) return undefined;
|
||||
if (onBeforeClose) {
|
||||
return () => onBeforeClose() !== false && modalRef.current?.close();
|
||||
}
|
||||
return () => modalRef.current?.close();
|
||||
}
|
||||
|
||||
function useIsModalOpen(modalRef: HTMLDialogElement | null) {
|
||||
const [isOpen, setIsOpen] = React.useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!modalRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpen(modalRef.open); // eslint-disable-line react-hooks/set-state-in-effect
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
setIsOpen(modalRef.open);
|
||||
});
|
||||
|
||||
observer.observe(modalRef, {
|
||||
attributes: true,
|
||||
attributeFilter: ["open"],
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [modalRef]);
|
||||
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
export { useIsModalOpen };
|
||||
+4
-3
@@ -1,7 +1,7 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { cl } from "../utils/helpers";
|
||||
import { cl } from "../../utils/helpers";
|
||||
|
||||
export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ const ModalBody = forwardRef<HTMLDivElement, ModalBodyProps>(
|
||||
},
|
||||
);
|
||||
|
||||
export default ModalBody;
|
||||
export { ModalBody };
|
||||
export type { ModalBodyProps };
|
||||
Loaded 100 of 277 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user