From c969191175b29047dfaf56c3c85c74544f2296e8 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Fri, 16 Aug 2024 10:12:02 +0200 Subject: [PATCH 01/20] feat: initial setup popover component with related files --- packages/popover-react/README.md | 22 ++++ .../popover-react/documentation/DevClient.tsx | 5 + .../popover-react/documentation/DevServer.tsx | 5 + .../popover-react/documentation/Example.tsx | 8 ++ .../popover-react/documentation/Popover.mdx | 35 ++++++ .../documentation/PopoverExample.tsx | 15 +++ packages/popover-react/esbuild.dev.mjs | 13 ++ packages/popover-react/esbuild.mjs | 13 ++ packages/popover-react/esbuild.prod.mjs | 7 ++ .../popover-react/integration/popover.spec.js | 22 ++++ packages/popover-react/package.json | 57 +++++++++ packages/popover-react/src/Popover.test.tsx | 28 +++++ packages/popover-react/src/Popover.tsx | 112 ++++++++++++++++++ packages/popover-react/src/PopoverContent.tsx | 15 +++ packages/popover-react/src/index.ts | 1 + .../tsconfig-for-declarations.json | 8 ++ packages/popover/README.md | 20 ++++ packages/popover/_index.scss | 1 + packages/popover/gulpfile.js | 3 + packages/popover/package.json | 35 ++++++ packages/popover/popover.scss | 6 + packages/react-hooks/src/index.ts | 1 + .../react-hooks/src/useClientLayoutEffect.ts | 5 + pnpm-lock.yaml | 19 +++ 24 files changed, 456 insertions(+) create mode 100644 packages/popover-react/README.md create mode 100644 packages/popover-react/documentation/DevClient.tsx create mode 100644 packages/popover-react/documentation/DevServer.tsx create mode 100644 packages/popover-react/documentation/Example.tsx create mode 100644 packages/popover-react/documentation/Popover.mdx create mode 100644 packages/popover-react/documentation/PopoverExample.tsx create mode 100644 packages/popover-react/esbuild.dev.mjs create mode 100644 packages/popover-react/esbuild.mjs create mode 100644 packages/popover-react/esbuild.prod.mjs create mode 100644 packages/popover-react/integration/popover.spec.js create mode 100644 packages/popover-react/package.json create mode 100644 packages/popover-react/src/Popover.test.tsx create mode 100644 packages/popover-react/src/Popover.tsx create mode 100644 packages/popover-react/src/PopoverContent.tsx create mode 100644 packages/popover-react/src/index.ts create mode 100644 packages/popover-react/tsconfig-for-declarations.json create mode 100644 packages/popover/README.md create mode 100644 packages/popover/_index.scss create mode 100644 packages/popover/gulpfile.js create mode 100644 packages/popover/package.json create mode 100644 packages/popover/popover.scss create mode 100644 packages/react-hooks/src/useClientLayoutEffect.ts diff --git a/packages/popover-react/README.md b/packages/popover-react/README.md new file mode 100644 index 00000000000..b16910d7f34 --- /dev/null +++ b/packages/popover-react/README.md @@ -0,0 +1,22 @@ +# [`@fremtind/jkl-popover-react`](https://jokul.fremtind.no/komponenter/popover) + +Se portalen for [bruk og prinsipper](https://jokul.fremtind.no/komponenter/popover). + +## Installasjon + +**Tips:** [stilpakken](../popover/) blir automatisk installert som en avhengighet. + +1. `npm i @fremtind/jkl-popover-react`. +2. Importér _både_ React-komponent og stilark i prosjektet ditt. + +```js +import { Popover } from "@fremtind/jkl-popover-react"; + +// Importer stilark via JavaScript med CSS-loader. +import "@fremtind/jkl-popover/popover.min.css"; +``` + +```scss +// Eller importer stilark via SCSS. +@use "@fremtind/jkl-popover/popover"; +``` diff --git a/packages/popover-react/documentation/DevClient.tsx b/packages/popover-react/documentation/DevClient.tsx new file mode 100644 index 00000000000..8ca99c9b6fa --- /dev/null +++ b/packages/popover-react/documentation/DevClient.tsx @@ -0,0 +1,5 @@ +import React from "react"; +import { hydrateClient } from "../../../doc-utils/DevClient"; +import Example from "./Example"; + +hydrateClient(); diff --git a/packages/popover-react/documentation/DevServer.tsx b/packages/popover-react/documentation/DevServer.tsx new file mode 100644 index 00000000000..4dd450a3387 --- /dev/null +++ b/packages/popover-react/documentation/DevServer.tsx @@ -0,0 +1,5 @@ +import React from "react"; +import { createServer } from "../../../doc-utils/DevServer"; +import Example from "./Example"; + +createServer(); diff --git a/packages/popover-react/documentation/Example.tsx b/packages/popover-react/documentation/Example.tsx new file mode 100644 index 00000000000..75f0fda51a2 --- /dev/null +++ b/packages/popover-react/documentation/Example.tsx @@ -0,0 +1,8 @@ +import React from "react"; +import { DevExample } from "../../../doc-utils"; +import { PopoverExample, popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; +import "../../popover/popover.scss"; + +export default function Example() { + return ; +} diff --git a/packages/popover-react/documentation/Popover.mdx b/packages/popover-react/documentation/Popover.mdx new file mode 100644 index 00000000000..8178ee6f317 --- /dev/null +++ b/packages/popover-react/documentation/Popover.mdx @@ -0,0 +1,35 @@ +--- +title: Popover +react: popover-react +scss: popover +group: edit me! +--- + +import PopoverExample, { popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; + +Ingress med kort beskrivelse av komponenten + + + +Første eksempel må være synlig uten å scrolle og skal ha et kodeeksempel som oppdateres til å matche valgte parametere. + +Fyll på med mer utfyllende beskrivelse av komponenten her under eksempelet, for eksempel føringer for innhold. List opp de ulike variantene dersom komponenten har dem, sammen med beskrivelse av når den ene varianten bør brukes i stedet for en annen. + +Dokumentér riktig og feil bruk av varianter visuelt med `DoDontExample`. Se for eksempel [Tag](/komponenter/tag) for inspirasjon. + +## Tilgjengelighet + +Dokumenter spesielle hensyn for universell utforming. Eksempler kan være spesielle hensyn som må tas for brukere av skjermlesere, for fargeblinde, eller liknende. + +## Når bruker vi Popover? + +Før du bruker Popover er det greit å ha tatt stilling til noen spørsmål: + +- Liste med kontrollspørsmål +- Se [Message box](/komponenter/messagebox#når-bruker-vi-en-melding-) for eksempler + +Dokumentér riktig og feil bruk av komponenten visuelt med `DoDontExample`. Se for eksempel [Alert message](/komponenter/alertmessage#når-bruker-vi-en-varselmelding-) for inspirasjon. + +Om det er relevant, lenk direkte til andre komponenter som er riktig å bruke dersom denne komponenten ikke bør brukes i en gitt situasjon. + +Kontroller at Gatsby klarer å generere React API-dokumentasjon riktig i bunnen. Om komponenten din ikke dukker opp riktig, sørg for at den har et `displayName`. Om du får flere komponenter enn du ønsker i tabellen kan du bruke [`displayTypes` i frontmatter](https://github.com/fremtind/jokul/blob/60edb292a922eea69e539875359524e2c13eda3e/packages/core/documentation/Link.mdx?plain=1#L6-L8). diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx new file mode 100644 index 00000000000..f3507ab5b70 --- /dev/null +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -0,0 +1,15 @@ +import React, { FC } from "react"; +import { ExampleComponentProps, ExampleKnobsProps, CodeExample } from "../../../doc-utils"; +import { Popover } from "../src"; + +export const popoverExampleKnobs: ExampleKnobsProps = {}; + +export const PopoverExample: FC = () => { + return Edit me!; +}; + +export default PopoverExample; + +export const popoverExampleCode: CodeExample = () => ` +Edit me! +`; diff --git a/packages/popover-react/esbuild.dev.mjs b/packages/popover-react/esbuild.dev.mjs new file mode 100644 index 00000000000..8b8e4a3a605 --- /dev/null +++ b/packages/popover-react/esbuild.dev.mjs @@ -0,0 +1,13 @@ +import { build } from "../../esbuild.dev.mjs"; + +await build([ + { + entryPoints: ["documentation/DevServer.tsx"], + outfile: "dist/server.js", + platform: "node", + }, + { + entryPoints: ["documentation/DevClient.tsx"], + outfile: "dist/client.js", + }, +]); diff --git a/packages/popover-react/esbuild.mjs b/packages/popover-react/esbuild.mjs new file mode 100644 index 00000000000..cde4b5a0d65 --- /dev/null +++ b/packages/popover-react/esbuild.mjs @@ -0,0 +1,13 @@ +import { build } from "../../esbuild.mjs"; + +await build([ + { + entryPoints: ["documentation/DevServer.tsx"], + outfile: "dist/server.js", + platform: "node", + }, + { + entryPoints: ["documentation/DevClient.tsx"], + outfile: "dist/client.js", + }, +]); diff --git a/packages/popover-react/esbuild.prod.mjs b/packages/popover-react/esbuild.prod.mjs new file mode 100644 index 00000000000..33d49a982dd --- /dev/null +++ b/packages/popover-react/esbuild.prod.mjs @@ -0,0 +1,7 @@ +import glob from "tiny-glob"; +import { build } from "../../esbuild.prod.mjs"; + +await build({ + entryPoints: await glob("src/**/!(*.test).@(ts|tsx)"), + outdir: "build", +}); diff --git a/packages/popover-react/integration/popover.spec.js b/packages/popover-react/integration/popover.spec.js new file mode 100644 index 00000000000..a619e36327f --- /dev/null +++ b/packages/popover-react/integration/popover.spec.js @@ -0,0 +1,22 @@ +/// +/// + +describe("Popover", () => { + beforeEach(() => { + cy.testComponent("popover"); + }); + + it("renders correctly", () => { + cy.takeSnapshots({ + setup: () => { + // Her kan du velge å klikke rundt for å gjøre klart eksempelet for snapshot + }, + teardown: () => { + // Her kan du eventuelt resette ting du gjorde i setup, dersom snapshoten for dark mode blir feil. + // Om du har brukt f. eks. `cy.setMedFeil()` i setup må du + // kalle `cy.resetMedFeil()` her. + }, + // Se for øvrig typedefinisjonen for propertyene `eq` og `variants`. + }); + }); +}); diff --git a/packages/popover-react/package.json b/packages/popover-react/package.json new file mode 100644 index 00000000000..d841eecef90 --- /dev/null +++ b/packages/popover-react/package.json @@ -0,0 +1,57 @@ +{ + "name": "@fremtind/jkl-popover-react", + "version": "1.0.0-alpha.0", + "publishConfig": { + "access": "public" + }, + "description": "Jøkul react popover", + "homepage": "https://github.com/fremtind/jokul/tree/main/packages/popover-react", + "keywords": [ + "jøkul", + "fremtind" + ], + "directories": { + "lib": "build" + }, + "files": [ + "build" + ], + "license": "MIT", + "types": "./build/index.d.ts", + "main": "./build/cjs/index.js", + "module": "./build/esm/index.js", + "browser": "./build/esm/index.js", + "sideEffects": [ + "*.css", + "*.scss" + ], + "scripts": { + "clean": "rimraf node_modules/ build/ node_modules/ ", + "build:types": "tsc -p tsconfig-for-declarations.json", + "build:scripts": "node ./esbuild.prod.mjs", + "build": "run-s build:*", + "test": "jest --testMatch '**/popover-react/**/*.test.+(ts|tsx|js)' --config=../../jest.config.js", + "dev:js": "ESBUILD_WATCH=true node ./esbuild.dev.mjs", + "dev:server": "nodemon ./dist/server.js", + "dev": "run-p dev:*" + }, + "dependencies": { + "@floating-ui/react": "^0.24.2", + "@fremtind/jkl-core": "^12.0.0", + "@fremtind/jkl-popover": "^1.0.0-alpha.0", + "classnames": "^2.5.1" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0 || ^18.0.0", + "@types/react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0", + "react": "^16.8.6 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fremtind/jokul.git" + }, + "bugs": { + "url": "https://github.com/fremtind/jokul/issues" + } +} diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx new file mode 100644 index 00000000000..945acc3d492 --- /dev/null +++ b/packages/popover-react/src/Popover.test.tsx @@ -0,0 +1,28 @@ +import { render, RenderOptions } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { axe } from "jest-axe"; +import React from "react"; +import { Popover } from "."; + +function setup(jsx: JSX.Element, renderOptions?: RenderOptions) { + return { + user: userEvent.setup(), + ...render(jsx, renderOptions), + }; +} + +describe("Popover", () => { + it("should render", () => { + const { getByText } = setup(Edit me!); + + getByText("Edit me!"); + }); + + it("should pass jest-axe tests in default state", async () => { + const { container } = setup(Edit me!); + + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); +}); diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx new file mode 100644 index 00000000000..4d1bd8c1189 --- /dev/null +++ b/packages/popover-react/src/Popover.tsx @@ -0,0 +1,112 @@ +import { autoUpdate, flip, offset as flOffset, shift, useFloating, useMergeRefs } from "@floating-ui/react"; +import classNames from "classnames"; +import { useClientLayoutEffect } from "packages/react-hooks/src"; +import React, { HTMLAttributes } from "react"; +import PopoverContent, { PopoverContentProps } from "./PopoverContent"; + +interface PopoverProps extends HTMLAttributes { + /** + * Popover content + */ + children: React.ReactNode; + /** + * Popover placement + */ + placement?: "top" | "right" | "bottom" | "left"; + /** + * Element popover anchors to + */ + anchorEl: Element | null; + /** + * Open state + */ + open: boolean; + /** + * onClose callback + */ + onClose: () => void; + /** + * Distance from anchor to popover + * @default 4 + */ + offset?: number; + /** + * Changes what CSS position property to use + * You want to use "fixed" if reference element is inside a fixed container, but popover is not + * @default "absolute" + */ + strategy?: "absolute" | "fixed"; + /** + * Changes placement of the floating element in order to keep it in view. + * @default true + */ + flip?: boolean; +} + +interface PopoverComponent extends React.ForwardRefExoticComponent> { + Content: React.ForwardRefExoticComponent>; +} + +const Popover = React.forwardRef(function Popover( + { + className, + children, + strategy, + placement = "top", + offset = 4, + open, + onClose, + anchorEl, + flip: _flip = true, + ...rest + }, + ref, +) { + const { + update, + refs, + placement: flPlacement, + floatingStyles, + } = useFloating({ + strategy, + placement, + open, + middleware: [ + flOffset(offset), + _flip && flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), + shift({ padding: 12 }), + ], + }); + + useClientLayoutEffect(() => { + refs.setReference(anchorEl); + }, [anchorEl]); + + const floatingRef = useMergeRefs([refs.setFloating, ref]); + + useClientLayoutEffect(() => { + if (!refs.reference.current || !refs.floating.current || !open) return; + + const cleanup = autoUpdate(refs.reference.current, refs.floating.current, update); + + return () => cleanup(); + }, [refs.floating, refs.reference, update, open, anchorEl]); + + return ( +
+ {children} +
+ ); +}) as PopoverComponent; + +Popover.Content = PopoverContent; + +export default Popover; diff --git a/packages/popover-react/src/PopoverContent.tsx b/packages/popover-react/src/PopoverContent.tsx new file mode 100644 index 00000000000..f91413ace77 --- /dev/null +++ b/packages/popover-react/src/PopoverContent.tsx @@ -0,0 +1,15 @@ +import classNames from "classnames"; +import React from "react"; + +export interface PopoverContentProps extends React.HTMLAttributes { + children: React.ReactNode; +} + +const PopoverContent = React.forwardRef(function PopoverContent( + { className, ...rest }, + ref, +) { + return
; +}); + +export default PopoverContent; diff --git a/packages/popover-react/src/index.ts b/packages/popover-react/src/index.ts new file mode 100644 index 00000000000..7ce91dd5a79 --- /dev/null +++ b/packages/popover-react/src/index.ts @@ -0,0 +1 @@ +export { Popover } from "./Popover"; diff --git a/packages/popover-react/tsconfig-for-declarations.json b/packages/popover-react/tsconfig-for-declarations.json new file mode 100644 index 00000000000..742e5683436 --- /dev/null +++ b/packages/popover-react/tsconfig-for-declarations.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig-for-declarations.json", + "compilerOptions": { + "outDir": "./build", + "rootDir": "./src" + }, + "include": ["./src"] +} diff --git a/packages/popover/README.md b/packages/popover/README.md new file mode 100644 index 00000000000..3824b0dc5a6 --- /dev/null +++ b/packages/popover/README.md @@ -0,0 +1,20 @@ +# [`@fremtind/jkl-popover`](https://jokul.fremtind.no/komponenter/popover) + +Se portalen for [bruk og prinsipper](https://jokul.fremtind.no/komponenter/popover). + +## Installasjon + +**Tips:** om du bruker [React-pakken](../popover-react/) trenger du ikke installere denne pakken direkte. + +1. `npm i @fremtind/jkl-popover`. +2. Importér stil-pakken i prosjektet ditt. + +```js +// Importer stilark via JavaScript med CSS-loader. +import "@fremtind/jkl-popover/popover.min.css"; +``` + +```scss +// Eller importer stilark via SCSS. +@use "@fremtind/jkl-popover/popover"; +``` diff --git a/packages/popover/_index.scss b/packages/popover/_index.scss new file mode 100644 index 00000000000..071aef87329 --- /dev/null +++ b/packages/popover/_index.scss @@ -0,0 +1 @@ +@forward "popover"; diff --git a/packages/popover/gulpfile.js b/packages/popover/gulpfile.js new file mode 100644 index 00000000000..cc124a12ab9 --- /dev/null +++ b/packages/popover/gulpfile.js @@ -0,0 +1,3 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +require("../../gulpfile")(require("gulp")); +/* eslint-enable @typescript-eslint/no-var-requires */ diff --git a/packages/popover/package.json b/packages/popover/package.json new file mode 100644 index 00000000000..0e6f4fbd828 --- /dev/null +++ b/packages/popover/package.json @@ -0,0 +1,35 @@ +{ + "name": "@fremtind/jkl-popover", + "version": "1.0.0-alpha.0", + "publishConfig": { + "access": "public" + }, + "description": "Jøkul style for popover", + "homepage": "https://github.com/fremtind/jokul/tree/main/packages/popover", + "keywords": [ + "jøkul", + "fremtind" + ], + "files": [ + "*.css", + "*.scss" + ], + "license": "MIT", + "scripts": { + "clean": "rimraf .turbo **/*.css", + "build": "gulp build", + "build:watch": "gulp build:watch", + "test": "echo \"Error: run tests from root\" && exit 1", + "dev": "echo \"Error: run dev from popover-react\" && exit 1" + }, + "dependencies": { + "@fremtind/jkl-core": "^12.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fremtind/jokul.git" + }, + "bugs": { + "url": "https://github.com/fremtind/jokul/issues" + } +} diff --git a/packages/popover/popover.scss b/packages/popover/popover.scss new file mode 100644 index 00000000000..67de59d91dd --- /dev/null +++ b/packages/popover/popover.scss @@ -0,0 +1,6 @@ +@charset "UTF-8"; +@use "@fremtind/jkl-core/jkl"; + +.jkl-popover { + z-index: jkl.$z-index--floating; +} diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 16bb31c2d3d..a3b5e300e22 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -28,3 +28,4 @@ export { useListNavigation } from "./useListNavigation"; export { useSwipeGesture, type SwipeChangeHandler } from "./useSwipeGesture"; export { useAnimatedDetails } from "./useAnimatedDetails"; export { useLocalStorage } from "./useLocalStorage/useLocalStorage"; +export { useClientLayoutEffect } from "./useClientLayoutEffect"; diff --git a/packages/react-hooks/src/useClientLayoutEffect.ts b/packages/react-hooks/src/useClientLayoutEffect.ts new file mode 100644 index 00000000000..a6af9a5a7d4 --- /dev/null +++ b/packages/react-hooks/src/useClientLayoutEffect.ts @@ -0,0 +1,5 @@ +"use client"; + +import { useLayoutEffect } from "react"; + +export const useClientLayoutEffect = globalThis?.document ? useLayoutEffect : () => {}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6c648ceebb..fc3de7fcf41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -845,6 +845,24 @@ importers: prop-types: 15.8.1 react-a11y-dialog: 6.2.0_prop-types@15.8.1 + packages/popover: + specifiers: + '@fremtind/jkl-core': ^12.0.0 + dependencies: + '@fremtind/jkl-core': 12.2.0 + + packages/popover-react: + specifiers: + '@floating-ui/react': ^0.24.2 + '@fremtind/jkl-core': ^12.0.0 + '@fremtind/jkl-popover': ^1.0.0-alpha.0 + classnames: ^2.5.1 + dependencies: + '@floating-ui/react': 0.24.2 + '@fremtind/jkl-core': 12.2.0 + '@fremtind/jkl-popover': link:../popover + classnames: 2.5.1 + packages/progress-bar: specifiers: '@fremtind/jkl-core': ^14.4.0 @@ -9342,6 +9360,7 @@ packages: /classnames/2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + dev: false /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} From 0137a03fb26b5a3224c2d3684bad9f30da9252f2 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Sat, 17 Aug 2024 21:34:39 +0200 Subject: [PATCH 02/20] feat: add docs --- .../documentation/PopoverExample.tsx | 22 +++++++++++-- packages/popover-react/src/Popover.test.tsx | 31 ++++++------------- packages/popover-react/src/Popover.tsx | 14 ++++----- packages/popover-react/src/PopoverContent.tsx | 2 +- packages/popover-react/src/index.ts | 2 +- packages/popover/popover.scss | 9 ++++++ 6 files changed, 47 insertions(+), 33 deletions(-) diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index f3507ab5b70..c4f96ee6785 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -1,3 +1,4 @@ +import { Button } from "packages/button-react/src"; import React, { FC } from "react"; import { ExampleComponentProps, ExampleKnobsProps, CodeExample } from "../../../doc-utils"; import { Popover } from "../src"; @@ -5,11 +6,28 @@ import { Popover } from "../src"; export const popoverExampleKnobs: ExampleKnobsProps = {}; export const PopoverExample: FC = () => { - return Edit me!; + const [openState, setOpenState] = React.useState(false); + const buttonRef = React.useRef(null); + + return ( + <> + + setOpenState(false)} anchorEl={buttonRef.current}> + Some Content + + + ); }; export default PopoverExample; export const popoverExampleCode: CodeExample = () => ` -Edit me! + + setOpenState(false)} anchorEl={buttonRef.current}> + Some Content + `; diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx index 945acc3d492..7da4bb13c6c 100644 --- a/packages/popover-react/src/Popover.test.tsx +++ b/packages/popover-react/src/Popover.test.tsx @@ -1,28 +1,15 @@ -import { render, RenderOptions } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { axe } from "jest-axe"; +import { render, screen } from "@testing-library/react"; import React from "react"; import { Popover } from "."; -function setup(jsx: JSX.Element, renderOptions?: RenderOptions) { - return { - user: userEvent.setup(), - ...render(jsx, renderOptions), - }; -} - describe("Popover", () => { - it("should render", () => { - const { getByText } = setup(Edit me!); - - getByText("Edit me!"); - }); - - it("should pass jest-axe tests in default state", async () => { - const { container } = setup(Edit me!); - - const results = await axe(container); - - expect(results).toHaveNoViolations(); + test("open", () => { + render( + null} data-testid="popover-id"> +
+ , + ); + + expect(screen.getByTestId("popover-id")).toBeVisible(); }); }); diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 4d1bd8c1189..b2cc2288311 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -1,7 +1,6 @@ import { autoUpdate, flip, offset as flOffset, shift, useFloating, useMergeRefs } from "@floating-ui/react"; import classNames from "classnames"; -import { useClientLayoutEffect } from "packages/react-hooks/src"; -import React, { HTMLAttributes } from "react"; +import React, { HTMLAttributes, useLayoutEffect } from "react"; import PopoverContent, { PopoverContentProps } from "./PopoverContent"; interface PopoverProps extends HTMLAttributes { @@ -31,8 +30,8 @@ interface PopoverProps extends HTMLAttributes { */ offset?: number; /** - * Changes what CSS position property to use - * You want to use "fixed" if reference element is inside a fixed container, but popover is not + * Changes what CSS position property to use. + * You want to use "fixed" if reference element is inside a fixed container, but popover is not. * @default "absolute" */ strategy?: "absolute" | "fixed"; @@ -78,13 +77,13 @@ const Popover = React.forwardRef(function Popover( ], }); - useClientLayoutEffect(() => { + useLayoutEffect(() => { refs.setReference(anchorEl); - }, [anchorEl]); + }, [anchorEl, refs]); const floatingRef = useMergeRefs([refs.setFloating, ref]); - useClientLayoutEffect(() => { + useLayoutEffect(() => { if (!refs.reference.current || !refs.floating.current || !open) return; const cleanup = autoUpdate(refs.reference.current, refs.floating.current, update); @@ -100,6 +99,7 @@ const Popover = React.forwardRef(function Popover( "jkl-popover--hidden": !open || !anchorEl, })} style={{ ...rest.style, ...floatingStyles }} + data-placement={flPlacement} aria-hidden={!open || !anchorEl} > {children} diff --git a/packages/popover-react/src/PopoverContent.tsx b/packages/popover-react/src/PopoverContent.tsx index f91413ace77..763ecfb3258 100644 --- a/packages/popover-react/src/PopoverContent.tsx +++ b/packages/popover-react/src/PopoverContent.tsx @@ -9,7 +9,7 @@ const PopoverContent = React.forwardRef(fun { className, ...rest }, ref, ) { - return
; + return
; }); export default PopoverContent; diff --git a/packages/popover-react/src/index.ts b/packages/popover-react/src/index.ts index 7ce91dd5a79..4583827d7c5 100644 --- a/packages/popover-react/src/index.ts +++ b/packages/popover-react/src/index.ts @@ -1 +1 @@ -export { Popover } from "./Popover"; +export { default as Popover } from "./Popover"; diff --git a/packages/popover/popover.scss b/packages/popover/popover.scss index 67de59d91dd..8f2cdc13d53 100644 --- a/packages/popover/popover.scss +++ b/packages/popover/popover.scss @@ -3,4 +3,13 @@ .jkl-popover { z-index: jkl.$z-index--floating; + box-shadow: jkl.$shadow-task-card; + + &--hidden { + display: none; + } + + &__content { + padding: jkl.$spacing-24; + } } From bacaa5b1b4eb276d76e46c0c022480bb96f637d7 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Mon, 19 Aug 2024 10:19:10 +0200 Subject: [PATCH 03/20] fix: update core package version in scaffold --- scripts/scaffold/templates/component-react/package.json | 2 +- scripts/scaffold/templates/component/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/scaffold/templates/component-react/package.json b/scripts/scaffold/templates/component-react/package.json index 70f3c6dfe1b..004ae0cb983 100644 --- a/scripts/scaffold/templates/component-react/package.json +++ b/scripts/scaffold/templates/component-react/package.json @@ -36,7 +36,7 @@ "dev": "run-p dev:*" }, "dependencies": { - "@fremtind/jkl-core": "^12.0.0", + "@fremtind/jkl-core": "^14.0.0", "@fremtind/jkl-scaffold": "^1.0.0-alpha.0" }, "peerDependencies": { diff --git a/scripts/scaffold/templates/component/package.json b/scripts/scaffold/templates/component/package.json index 9a89d9f3266..3b8c678b421 100644 --- a/scripts/scaffold/templates/component/package.json +++ b/scripts/scaffold/templates/component/package.json @@ -23,7 +23,7 @@ "dev": "echo \"Error: run dev from scaffold-react\" && exit 1" }, "dependencies": { - "@fremtind/jkl-core": "^12.0.0" + "@fremtind/jkl-core": "^14.0.0" }, "repository": { "type": "git", From 4b83293d1b68736e8469802c55b84bc631db902b Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Wed, 21 Aug 2024 12:25:43 +0200 Subject: [PATCH 04/20] feat: update popover component styles and dependencies --- .../popover-react/documentation/Example.tsx | 1 + .../documentation/PopoverExample.tsx | 14 ++++++++++---- packages/popover-react/package.json | 2 +- packages/popover-react/src/Popover.tsx | 19 ++++++++++++++----- packages/popover/package.json | 2 +- packages/popover/popover.scss | 3 ++- pnpm-lock.yaml | 10 ++++++---- portal/gatsby-browser.tsx | 1 + portal/package.json | 3 ++- 9 files changed, 38 insertions(+), 17 deletions(-) diff --git a/packages/popover-react/documentation/Example.tsx b/packages/popover-react/documentation/Example.tsx index 75f0fda51a2..bb4bd5ed48e 100644 --- a/packages/popover-react/documentation/Example.tsx +++ b/packages/popover-react/documentation/Example.tsx @@ -2,6 +2,7 @@ import React from "react"; import { DevExample } from "../../../doc-utils"; import { PopoverExample, popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; import "../../popover/popover.scss"; +import "../../button/button.scss"; export default function Example() { return ; diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index c4f96ee6785..47e02b1fec7 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -1,6 +1,6 @@ -import { Button } from "packages/button-react/src"; -import React, { FC } from "react"; +import React, { type FC } from "react"; import { ExampleComponentProps, ExampleKnobsProps, CodeExample } from "../../../doc-utils"; +import { Button } from "../../button-react/src"; import { Popover } from "../src"; export const popoverExampleKnobs: ExampleKnobsProps = {}; @@ -11,10 +11,16 @@ export const PopoverExample: FC = () => { return ( <> - - setOpenState(false)} anchorEl={buttonRef.current}> + setOpenState(false)} anchorEl={buttonRef.current}> Some Content diff --git a/packages/popover-react/package.json b/packages/popover-react/package.json index d841eecef90..038c5405337 100644 --- a/packages/popover-react/package.json +++ b/packages/popover-react/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@floating-ui/react": "^0.24.2", - "@fremtind/jkl-core": "^12.0.0", + "@fremtind/jkl-core": "^14.0.0", "@fremtind/jkl-popover": "^1.0.0-alpha.0", "classnames": "^2.5.1" }, diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index b2cc2288311..00333cfc4b8 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -1,4 +1,13 @@ -import { autoUpdate, flip, offset as flOffset, shift, useFloating, useMergeRefs } from "@floating-ui/react"; +import { + type Placement, + type Strategy, + autoUpdate, + flip, + offset as flOffset, + shift, + useFloating, + useMergeRefs, +} from "@floating-ui/react"; import classNames from "classnames"; import React, { HTMLAttributes, useLayoutEffect } from "react"; import PopoverContent, { PopoverContentProps } from "./PopoverContent"; @@ -11,7 +20,7 @@ interface PopoverProps extends HTMLAttributes { /** * Popover placement */ - placement?: "top" | "right" | "bottom" | "left"; + placement?: Placement; /** * Element popover anchors to */ @@ -34,7 +43,7 @@ interface PopoverProps extends HTMLAttributes { * You want to use "fixed" if reference element is inside a fixed container, but popover is not. * @default "absolute" */ - strategy?: "absolute" | "fixed"; + strategy?: Strategy; /** * Changes placement of the floating element in order to keep it in view. * @default true @@ -50,8 +59,8 @@ const Popover = React.forwardRef(function Popover( { className, children, - strategy, - placement = "top", + strategy = "absolute", + placement = "bottom-start", offset = 4, open, onClose, diff --git a/packages/popover/package.json b/packages/popover/package.json index 0e6f4fbd828..5537528b72d 100644 --- a/packages/popover/package.json +++ b/packages/popover/package.json @@ -23,7 +23,7 @@ "dev": "echo \"Error: run dev from popover-react\" && exit 1" }, "dependencies": { - "@fremtind/jkl-core": "^12.0.0" + "@fremtind/jkl-core": "^14.0.0" }, "repository": { "type": "git", diff --git a/packages/popover/popover.scss b/packages/popover/popover.scss index 8f2cdc13d53..4cf493301bd 100644 --- a/packages/popover/popover.scss +++ b/packages/popover/popover.scss @@ -3,7 +3,8 @@ .jkl-popover { z-index: jkl.$z-index--floating; - box-shadow: jkl.$shadow-task-card; + box-shadow: 0 4px 20px 0 #31303033; + background-color: jkl.$color-background-container-high; &--hidden { display: none; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc3de7fcf41..2919917f59c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -847,19 +847,19 @@ importers: packages/popover: specifiers: - '@fremtind/jkl-core': ^12.0.0 + '@fremtind/jkl-core': ^14.0.0 dependencies: - '@fremtind/jkl-core': 12.2.0 + '@fremtind/jkl-core': link:../core packages/popover-react: specifiers: '@floating-ui/react': ^0.24.2 - '@fremtind/jkl-core': ^12.0.0 + '@fremtind/jkl-core': ^14.0.0 '@fremtind/jkl-popover': ^1.0.0-alpha.0 classnames: ^2.5.1 dependencies: '@floating-ui/react': 0.24.2 - '@fremtind/jkl-core': 12.2.0 + '@fremtind/jkl-core': link:../core '@fremtind/jkl-popover': link:../popover classnames: 2.5.1 @@ -1194,6 +1194,7 @@ importers: '@fremtind/jkl-message-box-react': ^11.1.28 '@fremtind/jkl-message-react': ^1.0.0 '@fremtind/jkl-modal-react': ^2.1.40 + '@fremtind/jkl-popover-react': 1.0.0-alpha.0 '@fremtind/jkl-progress-bar-react': ^6.1.0 '@fremtind/jkl-radio-button-react': ^11.1.75 '@fremtind/jkl-react-hooks': ^12.1.8 @@ -1273,6 +1274,7 @@ importers: '@fremtind/jkl-message-box-react': link:../packages/message-box-react '@fremtind/jkl-message-react': link:../packages/message-react '@fremtind/jkl-modal-react': link:../packages/modal-react + '@fremtind/jkl-popover-react': link:../packages/popover-react '@fremtind/jkl-progress-bar-react': link:../packages/progress-bar-react '@fremtind/jkl-radio-button-react': link:../packages/radio-button-react '@fremtind/jkl-react-hooks': link:../packages/react-hooks diff --git a/portal/gatsby-browser.tsx b/portal/gatsby-browser.tsx index 911f6a2460a..b2aed830ac6 100644 --- a/portal/gatsby-browser.tsx +++ b/portal/gatsby-browser.tsx @@ -47,6 +47,7 @@ import "@fremtind/jkl-contextual-menu/contextual-menu.min.css"; import "@fremtind/jkl-expand-button/expand-button.min.css"; import "@fremtind/jkl-footer/footer.min.css"; import "@fremtind/jkl-progress-bar/progress-bar.min.css"; +import "@fremtind/jkl-popover/popover.min.css"; import "./src/components/Typography/typography.scss"; initTabListener(); diff --git a/portal/package.json b/portal/package.json index 8c3c65fdeca..951566f78a1 100644 --- a/portal/package.json +++ b/portal/package.json @@ -39,6 +39,7 @@ "@fremtind/jkl-message-box-react": "^11.1.28", "@fremtind/jkl-message-react": "^1.0.0", "@fremtind/jkl-modal-react": "^2.1.40", + "@fremtind/jkl-popover-react": "1.0.0-alpha.0", "@fremtind/jkl-progress-bar-react": "^6.1.0", "@fremtind/jkl-radio-button-react": "^11.1.75", "@fremtind/jkl-react-hooks": "^12.1.8", @@ -109,4 +110,4 @@ "bugs": { "url": "https://github.com/fremtind/jokul/issues" } -} +} \ No newline at end of file From 7bfda0b05436939dd594fc03273e5dc5b37d489c Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Thu, 22 Aug 2024 12:53:58 +0200 Subject: [PATCH 05/20] feat: update popover component styles --- packages/popover-react/src/Popover.tsx | 160 +++++++++++++++++-------- packages/popover/popover.scss | 5 +- 2 files changed, 110 insertions(+), 55 deletions(-) diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 00333cfc4b8..8efa04dc1e7 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -1,54 +1,92 @@ import { - type Placement, - type Strategy, autoUpdate, flip, - offset as flOffset, + offset, + Placement, shift, + Strategy, + useClick, + useDismiss, useFloating, + useFocus, + useHover, + useInteractions, useMergeRefs, + useRole, } from "@floating-ui/react"; +import { Props as UseClickProps } from "@floating-ui/react/src/hooks/useClick"; +import { Props as UseFocusProps } from "@floating-ui/react/src/hooks/useFocus"; +import { Props as UseHoverProps } from "@floating-ui/react/src/hooks/useHover"; +import { Props as UseRoleProps } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; -import React, { HTMLAttributes, useLayoutEffect } from "react"; +import React, { CSSProperties, HTMLAttributes, useLayoutEffect } from "react"; import PopoverContent, { PopoverContentProps } from "./PopoverContent"; interface PopoverProps extends HTMLAttributes { /** - * Popover content + * Popover content. */ children: React.ReactNode; /** - * Popover placement + * Padding of the popover element. + * @default "none" */ - placement?: Placement; + padding?: 8 | 16 | 24; /** - * Element popover anchors to + * Whether the popover is open or not. */ - anchorEl: Element | null; + open: boolean; /** - * Open state + * onClose callback. */ - open: boolean; + onClose: React.Dispatch>; + /** + * Strategy for positioning the floating element. + * @default absolute + * @see https://floating-ui.com/docs/useFloating#strategy + */ + strategy?: Strategy; /** - * onClose callback + * Placement of the floating element. + * @default bottom-start + * @see https://floating-ui.com/docs/useFloating#placement */ - onClose: () => void; + placement?: Placement; /** - * Distance from anchor to popover + * Offset of the floating element. + * @see https://floating-ui.com/docs/offset * @default 4 */ offset?: number; + /** Hover options + * @see {@link UseHoverProps} + * @see https://floating-ui.com/docs/useHover + * @default { enabled: false } + */ + useHoverProps?: UseHoverProps; + /** Focus options + * @see {@link UseFocusProps} + * @see https://floating-ui.com/docs/useFocus + * @default { enabled: false } + */ + useFocusProps?: UseFocusProps; + /** Click options + * @see {@link UseClickProps} + * @see https://floating-ui.com/docs/useClick + * @default { enabled: false } + */ + useClickProps?: UseClickProps; /** - * Changes what CSS position property to use. - * You want to use "fixed" if reference element is inside a fixed container, but popover is not. - * @default "absolute" + * Role options + * @see {@link UseRoleProps} + * @see https://floating-ui.com/docs/useRole + * @default { enabled: true, role: "dialog" } */ - strategy?: Strategy; + useRoleProps?: UseRoleProps; /** - * Changes placement of the floating element in order to keep it in view. - * @default true + * Reference to the element the popover should be anchored to. */ - flip?: boolean; + referenceElement: (props: { ref: React.Ref }) => React.ReactNode; } interface PopoverComponent extends React.ForwardRefExoticComponent> { @@ -59,60 +97,80 @@ const Popover = React.forwardRef(function Popover( { className, children, + open: isOpen, + padding = "none", strategy = "absolute", placement = "bottom-start", - offset = 4, - open, + useHoverProps: { enabled: enableHover = false, ...restHoverProps } = {}, + useFocusProps: { enabled: enableFocus = false, ...restFocusProps } = {}, + useClickProps: { enabled: enableClick = false, ...restClickProps } = {}, + useRoleProps: { enabled: enableRole = true, ...restRoleProps } = {}, onClose, - anchorEl, - flip: _flip = true, - ...rest + referenceElement, + ...restProps }, ref, ) { const { + context, update, - refs, placement: flPlacement, + refs, floatingStyles, + elements, } = useFloating({ + open: isOpen, strategy, placement, - open, - middleware: [ - flOffset(offset), - _flip && flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), - shift({ padding: 12 }), - ], + middleware: [offset(4), flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), shift({ padding: 12 })], + onOpenChange: onClose, }); - useLayoutEffect(() => { - refs.setReference(anchorEl); - }, [anchorEl, refs]); + const role = useRole(context, { enabled: enableRole, ...restRoleProps }); + + const click = useClick(context, { enabled: enableClick, ...restClickProps }); + + const hover = useHover(context, { enabled: enableHover, ...restHoverProps }); + + const focus = useFocus(context, { enabled: enableFocus, ...restFocusProps }); + + const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, click, role]); const floatingRef = useMergeRefs([refs.setFloating, ref]); + useDismiss(context); + useLayoutEffect(() => { - if (!refs.reference.current || !refs.floating.current || !open) return; + if (!isOpen || !elements.reference || !elements.floating) return; - const cleanup = autoUpdate(refs.reference.current, refs.floating.current, update); + const cleanup = autoUpdate(elements.reference, elements.floating, update); return () => cleanup(); - }, [refs.floating, refs.reference, update, open, anchorEl]); + }, [elements, update, isOpen]); return ( -
- {children} -
+ <> + {referenceElement({ ref: refs.setReference, ...getReferenceProps() })} +
+ {children} +
+ ); }) as PopoverComponent; diff --git a/packages/popover/popover.scss b/packages/popover/popover.scss index 4cf493301bd..7de1cacb7f5 100644 --- a/packages/popover/popover.scss +++ b/packages/popover/popover.scss @@ -5,12 +5,9 @@ z-index: jkl.$z-index--floating; box-shadow: 0 4px 20px 0 #31303033; background-color: jkl.$color-background-container-high; + padding: var(--popover-padding, 0); &--hidden { display: none; } - - &__content { - padding: jkl.$spacing-24; - } } From 65b0bcef1bb4f0609ea2f290488950ccb6a8a324 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Thu, 22 Aug 2024 12:54:19 +0200 Subject: [PATCH 06/20] feat: add tabs styles to popover example component --- .../popover-react/documentation/Example.tsx | 1 + .../documentation/PopoverExample.tsx | 319 +++++++++++++++++- 2 files changed, 304 insertions(+), 16 deletions(-) diff --git a/packages/popover-react/documentation/Example.tsx b/packages/popover-react/documentation/Example.tsx index bb4bd5ed48e..97c5455fb31 100644 --- a/packages/popover-react/documentation/Example.tsx +++ b/packages/popover-react/documentation/Example.tsx @@ -3,6 +3,7 @@ import { DevExample } from "../../../doc-utils"; import { PopoverExample, popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; import "../../popover/popover.scss"; import "../../button/button.scss"; +import "../../tabs/tabs.scss"; export default function Example() { return ; diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index 47e02b1fec7..1f252e6df56 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -1,27 +1,175 @@ +import { offset } from "@floating-ui/react"; import React, { type FC } from "react"; import { ExampleComponentProps, ExampleKnobsProps, CodeExample } from "../../../doc-utils"; import { Button } from "../../button-react/src"; +import { TabList, Tab, Tabs, TabPanel } from "../../tabs-react/src"; import { Popover } from "../src"; export const popoverExampleKnobs: ExampleKnobsProps = {}; export const PopoverExample: FC = () => { const [openState, setOpenState] = React.useState(false); - const buttonRef = React.useRef(null); + + const ExampleWrapperComponent = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); + }; return ( <> - + ); + }} > - Toggle Popover - - setOpenState(false)} anchorEl={buttonRef.current}> - Some Content + + + + Brev + SMS + Notat + Be om + Saksflyt + + + +
+

Melding om dødsfall

+

Politi

+

Bytte av forsikringstaker og betaler

+

Avbrutt svangerskap

+

Fritekstbrev med purring og mulighet for basis-/utvidede fullmakter

+

Fritekstbrev uten purring (venter på svar)

+

Statsforvalteren

+

Lege

+

Opplysninger fra tingrett/fullmakt fra etterlatte

+

Samboer

+

Testamente

+

Utbetalingsopplysninger utland

+

Varsel om svik

+

Lege-TestAuto

+
+
+

Utbetalingsbrev NSF barn

+

Fritekstbrev (venter ikke på svar)

+

Avslag samboervilkår/avbrutt svangerskap

+

Info til statsforvalteren

+

Innkreving

+

Utbetaling gruppeliv

+

Ingen dekninger

+

Avslag svik

+

Avslag annet

+
+
+
+ + +
+

SMS sendt om betaling

+

Bekreftelse på mottatt dokumentasjon

+

SMS om manglende dokumenter

+

Påminnelse om innsendelse av erklæring

+

Viktig informasjon om forsikringen din

+

Oppfølging av tidligere henvendelse

+

Statusoppdatering på saken din

+
+
+

SMS om saksbehandling

+

Bekreftelse på utbetaling

+

Varsel om forsinkelse

+

SMS om avslag på krav

+

Påminnelse om manglende betaling

+

SMS med saksnummer

+

Oppdatering om klageprosess

+
+
+
+ + +
+

Intern notat om saksbehandling

+

Vurdering av medisinsk rapport

+

Oppdatering om videre fremdrift

+

Gjennomgang av innsendte dokumenter

+

Notat om kontakt med kunde

+

Internt notat om mulig svik

+
+
+

Vurdering av klage

+

Notat om utbetaling

+

Gjennomgang av policydokument

+

Notat om interne møter

+

Sammenfatning av saksopplysninger

+

Intern kommunikasjon om sak

+
+
+
+ + +
+

Forespørsel om mer informasjon

+

Be om ny medisinsk vurdering

+

Etterspørsel etter manglende dokumentasjon

+

Tilleggsinformasjon fra kunde

+

Be om bekreftelse på dekning

+
+
+

Oppfølging av tidligere forespørsel

+

Be om spesifisering av skade

+

Forespørsel om dokumentasjon på utgifter

+

Avklaring av vilkår

+

Etterspørsel etter legeerklæring

+
+
+
+ + +
+

Sak opprettet

+

Sak under behandling

+

Sak avsluttet

+

Sak eskalert

+

Venter på tilbakemelding

+

Saksflyt oppdatert

+
+
+

Sak på vent

+

Sak gjenåpnet

+

Sak avsluttet uten utbetaling

+

Sak overført til juridisk

+

Sak sendt til ekstern vurdering

+

Intern saksvurdering pågår

+
+
+
+
+
); @@ -30,10 +178,149 @@ export const PopoverExample: FC = () => { export default PopoverExample; export const popoverExampleCode: CodeExample = () => ` - - setOpenState(false)} anchorEl={buttonRef.current}> - Some Content + { + return ( + + ); + }} + > + + + + Brev + SMS + Notat + Be om + Saksflyt + + + +
+

Melding om dødsfall

+

Politi

+

Bytte av forsikringstaker og betaler

+

Avbrutt svangerskap

+

Fritekstbrev med purring og mulighet for basis-/utvidede fullmakter

+

Fritekstbrev uten purring (venter på svar)

+

Statsforvalteren

+

Lege

+

Opplysninger fra tingrett/fullmakt fra etterlatte

+

Samboer

+

Testamente

+

Utbetalingsopplysninger utland

+

Varsel om svik

+

Lege-TestAuto

+
+
+

Utbetalingsbrev NSF barn

+

Fritekstbrev (venter ikke på svar)

+

Avslag samboervilkår/avbrutt svangerskap

+

Info til statsforvalteren

+

Innkreving

+

Utbetaling gruppeliv

+

Ingen dekninger

+

Avslag svik

+

Avslag annet

+
+
+
+ + +
+

SMS sendt om betaling

+

Bekreftelse på mottatt dokumentasjon

+

SMS om manglende dokumenter

+

Påminnelse om innsendelse av erklæring

+

Viktig informasjon om forsikringen din

+

Oppfølging av tidligere henvendelse

+

Statusoppdatering på saken din

+
+
+

SMS om saksbehandling

+

Bekreftelse på utbetaling

+

Varsel om forsinkelse

+

SMS om avslag på krav

+

Påminnelse om manglende betaling

+

SMS med saksnummer

+

Oppdatering om klageprosess

+
+
+
+ + +
+

Intern notat om saksbehandling

+

Vurdering av medisinsk rapport

+

Oppdatering om videre fremdrift

+

Gjennomgang av innsendte dokumenter

+

Notat om kontakt med kunde

+

Internt notat om mulig svik

+
+
+

Vurdering av klage

+

Notat om utbetaling

+

Gjennomgang av policydokument

+

Notat om interne møter

+

Sammenfatning av saksopplysninger

+

Intern kommunikasjon om sak

+
+
+
+ + +
+

Forespørsel om mer informasjon

+

Be om ny medisinsk vurdering

+

Etterspørsel etter manglende dokumentasjon

+

Tilleggsinformasjon fra kunde

+

Be om bekreftelse på dekning

+
+
+

Oppfølging av tidligere forespørsel

+

Be om spesifisering av skade

+

Forespørsel om dokumentasjon på utgifter

+

Avklaring av vilkår

+

Etterspørsel etter legeerklæring

+
+
+
+ + +
+

Sak opprettet

+

Sak under behandling

+

Sak avsluttet

+

Sak eskalert

+

Venter på tilbakemelding

+

Saksflyt oppdatert

+
+
+

Sak på vent

+

Sak gjenåpnet

+

Sak avsluttet uten utbetaling

+

Sak overført til juridisk

+

Sak sendt til ekstern vurdering

+

Intern saksvurdering pågår

+
+
+
+
+
`; From 08004c56453e86f3be8f60b78e5f48fa24cf3055 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Mon, 26 Aug 2024 08:56:36 +0200 Subject: [PATCH 07/20] feat: update popover --- .../popover-react/documentation/Example.tsx | 25 +- .../documentation/PopoverExample.tsx | 498 +++++++----------- packages/popover-react/src/Popover.test.tsx | 2 +- packages/popover-react/src/Popover.tsx | 290 ++++++---- packages/popover-react/src/PopoverContent.tsx | 15 - 5 files changed, 405 insertions(+), 425 deletions(-) delete mode 100644 packages/popover-react/src/PopoverContent.tsx diff --git a/packages/popover-react/documentation/Example.tsx b/packages/popover-react/documentation/Example.tsx index 97c5455fb31..85967189ed3 100644 --- a/packages/popover-react/documentation/Example.tsx +++ b/packages/popover-react/documentation/Example.tsx @@ -1,10 +1,31 @@ import React from "react"; import { DevExample } from "../../../doc-utils"; -import { PopoverExample, popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; +import { + PopoverControlledExample, + popoverControlledExampleCode, + popoverExampleKnobs, + PopoverUnControlledExample, + popoverUnControlledExampleCode, +} from "./PopoverExample"; import "../../popover/popover.scss"; import "../../button/button.scss"; import "../../tabs/tabs.scss"; export default function Example() { - return ; + return ( + <> + + + + ); } diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index 1f252e6df56..5d8d352a191 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -1,326 +1,214 @@ -import { offset } from "@floating-ui/react"; import React, { type FC } from "react"; import { ExampleComponentProps, ExampleKnobsProps, CodeExample } from "../../../doc-utils"; import { Button } from "../../button-react/src"; import { TabList, Tab, Tabs, TabPanel } from "../../tabs-react/src"; -import { Popover } from "../src"; +import Popover from "../src/Popover"; export const popoverExampleKnobs: ExampleKnobsProps = {}; -export const PopoverExample: FC = () => { - const [openState, setOpenState] = React.useState(false); +const ExampleWrapperComponent = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +}; + +const ExampleTabPanel = () => { + return ( + + + Brev + SMS + Notat + Be om + Saksflyt + + + +
+

Melding om dødsfall

+

Politi

+

Bytte av forsikringstaker og betaler

+

Avbrutt svangerskap

+

Fritekstbrev med purring og mulighet for basis-/utvidede fullmakter

+

Fritekstbrev uten purring (venter på svar)

+

Statsforvalteren

+

Lege

+

Opplysninger fra tingrett/fullmakt fra etterlatte

+

Samboer

+

Testamente

+

Utbetalingsopplysninger utland

+

Varsel om svik

+

Lege-TestAuto

+
+
+

Utbetalingsbrev NSF barn

+

Fritekstbrev (venter ikke på svar)

+

Avslag samboervilkår/avbrutt svangerskap

+

Info til statsforvalteren

+

Innkreving

+

Utbetaling gruppeliv

+

Ingen dekninger

+

Avslag svik

+

Avslag annet

+
+
+
+ + +
+

SMS sendt om betaling

+

Bekreftelse på mottatt dokumentasjon

+

SMS om manglende dokumenter

+

Påminnelse om innsendelse av erklæring

+

Viktig informasjon om forsikringen din

+

Oppfølging av tidligere henvendelse

+

Statusoppdatering på saken din

+
+
+

SMS om saksbehandling

+

Bekreftelse på utbetaling

+

Varsel om forsinkelse

+

SMS om avslag på krav

+

Påminnelse om manglende betaling

+

SMS med saksnummer

+

Oppdatering om klageprosess

+
+
+
+ + +
+

Intern notat om saksbehandling

+

Vurdering av medisinsk rapport

+

Oppdatering om videre fremdrift

+

Gjennomgang av innsendte dokumenter

+

Notat om kontakt med kunde

+

Internt notat om mulig svik

+
+
+

Vurdering av klage

+

Notat om utbetaling

+

Gjennomgang av policydokument

+

Notat om interne møter

+

Sammenfatning av saksopplysninger

+

Intern kommunikasjon om sak

+
+
+
+ + +
+

Forespørsel om mer informasjon

+

Be om ny medisinsk vurdering

+

Etterspørsel etter manglende dokumentasjon

+

Tilleggsinformasjon fra kunde

+

Be om bekreftelse på dekning

+
+
+

Oppfølging av tidligere forespørsel

+

Be om spesifisering av skade

+

Forespørsel om dokumentasjon på utgifter

+

Avklaring av vilkår

+

Etterspørsel etter legeerklæring

+
+
+
+ + +
+

Sak opprettet

+

Sak under behandling

+

Sak avsluttet

+

Sak eskalert

+

Venter på tilbakemelding

+

Saksflyt oppdatert

+
+
+

Sak på vent

+

Sak gjenåpnet

+

Sak avsluttet uten utbetaling

+

Sak overført til juridisk

+

Sak sendt til ekstern vurdering

+

Intern saksvurdering pågår

+
+
+
+
+ ); +}; - const ExampleWrapperComponent = ({ children }: { children: React.ReactNode }) => { - return ( -
- {children} -
- ); - }; +export const PopoverControlledExample: FC = () => { + const [open, setOpen] = React.useState(false); return ( - <> - { - return ( - - ); - }} - > - - - - Brev - SMS - Notat - Be om - Saksflyt - - - -
-

Melding om dødsfall

-

Politi

-

Bytte av forsikringstaker og betaler

-

Avbrutt svangerskap

-

Fritekstbrev med purring og mulighet for basis-/utvidede fullmakter

-

Fritekstbrev uten purring (venter på svar)

-

Statsforvalteren

-

Lege

-

Opplysninger fra tingrett/fullmakt fra etterlatte

-

Samboer

-

Testamente

-

Utbetalingsopplysninger utland

-

Varsel om svik

-

Lege-TestAuto

-
-
-

Utbetalingsbrev NSF barn

-

Fritekstbrev (venter ikke på svar)

-

Avslag samboervilkår/avbrutt svangerskap

-

Info til statsforvalteren

-

Innkreving

-

Utbetaling gruppeliv

-

Ingen dekninger

-

Avslag svik

-

Avslag annet

-
-
-
- - -
-

SMS sendt om betaling

-

Bekreftelse på mottatt dokumentasjon

-

SMS om manglende dokumenter

-

Påminnelse om innsendelse av erklæring

-

Viktig informasjon om forsikringen din

-

Oppfølging av tidligere henvendelse

-

Statusoppdatering på saken din

-
-
-

SMS om saksbehandling

-

Bekreftelse på utbetaling

-

Varsel om forsinkelse

-

SMS om avslag på krav

-

Påminnelse om manglende betaling

-

SMS med saksnummer

-

Oppdatering om klageprosess

-
-
-
- - -
-

Intern notat om saksbehandling

-

Vurdering av medisinsk rapport

-

Oppdatering om videre fremdrift

-

Gjennomgang av innsendte dokumenter

-

Notat om kontakt med kunde

-

Internt notat om mulig svik

-
-
-

Vurdering av klage

-

Notat om utbetaling

-

Gjennomgang av policydokument

-

Notat om interne møter

-

Sammenfatning av saksopplysninger

-

Intern kommunikasjon om sak

-
-
-
- - -
-

Forespørsel om mer informasjon

-

Be om ny medisinsk vurdering

-

Etterspørsel etter manglende dokumentasjon

-

Tilleggsinformasjon fra kunde

-

Be om bekreftelse på dekning

-
-
-

Oppfølging av tidligere forespørsel

-

Be om spesifisering av skade

-

Forespørsel om dokumentasjon på utgifter

-

Avklaring av vilkår

-

Etterspørsel etter legeerklæring

-
-
-
- - -
-

Sak opprettet

-

Sak under behandling

-

Sak avsluttet

-

Sak eskalert

-

Venter på tilbakemelding

-

Saksflyt oppdatert

-
-
-

Sak på vent

-

Sak gjenåpnet

-

Sak avsluttet uten utbetaling

-

Sak overført til juridisk

-

Sak sendt til ekstern vurdering

-

Intern saksvurdering pågår

-
-
-
-
-
-
- + + setOpen?.(!open)} aria-expanded={open} asChild> + + + + + + ); }; -export default PopoverExample; +export const PopoverUnControlledExample: FC = () => { + return ( + + Åpne popover + + + + + ); +}; -export const popoverExampleCode: CodeExample = () => ` +export const popoverControlledExampleCode: CodeExample = () => ` { - return ( - - ); + > + setOpen?.(!open)} aria-expanded={open} asChild> + + + + + + +`; + +export const popoverUnControlledExampleCode: CodeExample = () => ` + - - - - Brev - SMS - Notat - Be om - Saksflyt - - - -
-

Melding om dødsfall

-

Politi

-

Bytte av forsikringstaker og betaler

-

Avbrutt svangerskap

-

Fritekstbrev med purring og mulighet for basis-/utvidede fullmakter

-

Fritekstbrev uten purring (venter på svar)

-

Statsforvalteren

-

Lege

-

Opplysninger fra tingrett/fullmakt fra etterlatte

-

Samboer

-

Testamente

-

Utbetalingsopplysninger utland

-

Varsel om svik

-

Lege-TestAuto

-
-
-

Utbetalingsbrev NSF barn

-

Fritekstbrev (venter ikke på svar)

-

Avslag samboervilkår/avbrutt svangerskap

-

Info til statsforvalteren

-

Innkreving

-

Utbetaling gruppeliv

-

Ingen dekninger

-

Avslag svik

-

Avslag annet

-
-
-
- - -
-

SMS sendt om betaling

-

Bekreftelse på mottatt dokumentasjon

-

SMS om manglende dokumenter

-

Påminnelse om innsendelse av erklæring

-

Viktig informasjon om forsikringen din

-

Oppfølging av tidligere henvendelse

-

Statusoppdatering på saken din

-
-
-

SMS om saksbehandling

-

Bekreftelse på utbetaling

-

Varsel om forsinkelse

-

SMS om avslag på krav

-

Påminnelse om manglende betaling

-

SMS med saksnummer

-

Oppdatering om klageprosess

-
-
-
- - -
-

Intern notat om saksbehandling

-

Vurdering av medisinsk rapport

-

Oppdatering om videre fremdrift

-

Gjennomgang av innsendte dokumenter

-

Notat om kontakt med kunde

-

Internt notat om mulig svik

-
-
-

Vurdering av klage

-

Notat om utbetaling

-

Gjennomgang av policydokument

-

Notat om interne møter

-

Sammenfatning av saksopplysninger

-

Intern kommunikasjon om sak

-
-
-
- - -
-

Forespørsel om mer informasjon

-

Be om ny medisinsk vurdering

-

Etterspørsel etter manglende dokumentasjon

-

Tilleggsinformasjon fra kunde

-

Be om bekreftelse på dekning

-
-
-

Oppfølging av tidligere forespørsel

-

Be om spesifisering av skade

-

Forespørsel om dokumentasjon på utgifter

-

Avklaring av vilkår

-

Etterspørsel etter legeerklæring

-
-
-
- - -
-

Sak opprettet

-

Sak under behandling

-

Sak avsluttet

-

Sak eskalert

-

Venter på tilbakemelding

-

Saksflyt oppdatert

-
-
-

Sak på vent

-

Sak gjenåpnet

-

Sak avsluttet uten utbetaling

-

Sak overført til juridisk

-

Sak sendt til ekstern vurdering

-

Intern saksvurdering pågår

-
-
-
-
+ Åpne popover + +
`; diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx index 7da4bb13c6c..15cbc583721 100644 --- a/packages/popover-react/src/Popover.test.tsx +++ b/packages/popover-react/src/Popover.test.tsx @@ -5,7 +5,7 @@ import { Popover } from "."; describe("Popover", () => { test("open", () => { render( - null} data-testid="popover-id"> +
, ); diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 8efa04dc1e7..51f57b7bd82 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -1,45 +1,57 @@ import { - autoUpdate, - flip, - offset, - Placement, - shift, - Strategy, - useClick, - useDismiss, + type Placement, + type Strategy, useFloating, useFocus, useHover, - useInteractions, - useMergeRefs, + useClick, + useDismiss, + autoUpdate, useRole, + useMergeRefs, + offset, + flip, + shift, + useInteractions, + FloatingPortal, + FloatingFocusManager, } from "@floating-ui/react"; import { Props as UseClickProps } from "@floating-ui/react/src/hooks/useClick"; import { Props as UseFocusProps } from "@floating-ui/react/src/hooks/useFocus"; import { Props as UseHoverProps } from "@floating-ui/react/src/hooks/useHover"; import { Props as UseRoleProps } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; -import React, { CSSProperties, HTMLAttributes, useLayoutEffect } from "react"; -import PopoverContent, { PopoverContentProps } from "./PopoverContent"; +import * as React from "react"; +import { Button } from "../../button-react/src"; -interface PopoverProps extends HTMLAttributes { +interface PopoverOptions { /** - * Popover content. + * Initial open state of the popover. + * @default false + * @see https://floating-ui.com/docs/useFloating#open */ - children: React.ReactNode; + initialOpen?: boolean; /** - * Padding of the popover element. - * @default "none" + * Determines if focus is “modal”, meaning focus is fully trapped inside the floating element and outside content cannot be accessed. This includes screen reader virtual cursors. + * @see https://floating-ui.com/docs/floatingfocusmanager#modal + * @default true */ - padding?: 8 | 16 | 24; + modal?: boolean; /** - * Whether the popover is open or not. + * Controlled open state of the popover. + * @see https://floating-ui.com/docs/useFloating#open */ - open: boolean; + open?: boolean; /** - * onClose callback. + * Callback to change the open state of the popover. + */ + onOpenChange?: (open: boolean) => void; + /** + * Offset of the floating element. + * @see https://floating-ui.com/docs/offset + * @default 4 */ - onClose: React.Dispatch>; + offset?: number; /** * Strategy for positioning the floating element. * @default absolute @@ -52,128 +64,202 @@ interface PopoverProps extends HTMLAttributes { * @see https://floating-ui.com/docs/useFloating#placement */ placement?: Placement; - /** - * Offset of the floating element. - * @see https://floating-ui.com/docs/offset - * @default 4 - */ - offset?: number; /** Hover options * @see {@link UseHoverProps} * @see https://floating-ui.com/docs/useHover * @default { enabled: false } */ - useHoverProps?: UseHoverProps; + hoverProps?: UseHoverProps; /** Focus options * @see {@link UseFocusProps} * @see https://floating-ui.com/docs/useFocus * @default { enabled: false } */ - useFocusProps?: UseFocusProps; + focusProps?: UseFocusProps; /** Click options * @see {@link UseClickProps} * @see https://floating-ui.com/docs/useClick * @default { enabled: false } */ - useClickProps?: UseClickProps; + clickProps?: UseClickProps; /** * Role options * @see {@link UseRoleProps} * @see https://floating-ui.com/docs/useRole * @default { enabled: true, role: "dialog" } */ - useRoleProps?: UseRoleProps; + roleProps?: UseRoleProps; /** - * Reference to the element the popover should be anchored to. + * Floating options + * @see https://floating-ui.com/docs/useFloating */ - referenceElement: (props: { ref: React.Ref }) => React.ReactNode; } -interface PopoverComponent extends React.ForwardRefExoticComponent> { - Content: React.ForwardRefExoticComponent>; -} +const usePopover = ({ + initialOpen = false, + strategy = "absolute", + placement = "bottom-start", + offset: _offset = 4, + modal = false, + open: controlledOpen, + onOpenChange: setControlledOpen, + hoverProps, + focusProps, + clickProps, + roleProps, +}: PopoverOptions) => { + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); -const Popover = React.forwardRef(function Popover( - { - className, - children, - open: isOpen, - padding = "none", - strategy = "absolute", - placement = "bottom-start", - useHoverProps: { enabled: enableHover = false, ...restHoverProps } = {}, - useFocusProps: { enabled: enableFocus = false, ...restFocusProps } = {}, - useClickProps: { enabled: enableClick = false, ...restClickProps } = {}, - useRoleProps: { enabled: enableRole = true, ...restRoleProps } = {}, - onClose, - referenceElement, - ...restProps - }, - ref, -) { - const { - context, - update, - placement: flPlacement, - refs, - floatingStyles, - elements, - } = useFloating({ - open: isOpen, - strategy, + const open = controlledOpen ?? uncontrolledOpen; + const setOpen = setControlledOpen ?? setUncontrolledOpen; + + const data = useFloating({ + open, placement, - middleware: [offset(4), flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), shift({ padding: 12 })], - onOpenChange: onClose, + strategy, + onOpenChange: setOpen, + whileElementsMounted: autoUpdate, + middleware: [ + offset(_offset), + flip({ crossAxis: placement.includes("-"), padding: 5, fallbackPlacements: ["bottom", "top"] }), + shift({ padding: 12 }), + ], + }); + + const context = data.context; + + const role = useRole(context, { + ...roleProps, + enabled: roleProps?.enabled ?? true, + role: roleProps?.role ?? "dialog", }); - const role = useRole(context, { enabled: enableRole, ...restRoleProps }); + const click = useClick(context, { + ...clickProps, + enabled: (controlledOpen || clickProps?.enabled) ?? false, + }); + const hover = useHover(context, { ...hoverProps, enabled: hoverProps?.enabled ?? false }); + const focus = useFocus(context, { ...focusProps, enabled: focusProps?.enabled ?? false }); + const dismiss = useDismiss(context); - const click = useClick(context, { enabled: enableClick, ...restClickProps }); + const interactions = useInteractions([click, dismiss, focus, hover, , role]); - const hover = useHover(context, { enabled: enableHover, ...restHoverProps }); + return React.useMemo( + () => ({ + open, + setOpen, + ...interactions, + ...data, + modal, + }), + [open, setOpen, interactions, data, modal], + ); +}; - const focus = useFocus(context, { enabled: enableFocus, ...restFocusProps }); +type PopoverContextType = ReturnType | null; - const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, click, role]); +const PopoverContext = React.createContext(null); - const floatingRef = useMergeRefs([refs.setFloating, ref]); +const usePopoverContext = () => { + const context = React.useContext(PopoverContext); - useDismiss(context); + if (context == null) { + throw new Error("Popover komponenter må brukes innenfor en komponent"); + } + + return context; +}; + +const Popover = ({ + children, + modal = false, + ...restOptions +}: { + children: React.ReactNode; +} & PopoverOptions) => { + const popover = usePopover({ modal, ...restOptions }); + return {children}; +}; - useLayoutEffect(() => { - if (!isOpen || !elements.reference || !elements.floating) return; +interface PopoverTriggerProps { + children: React.ReactNode; + /** + * Render the trigger as a child of the popover. + * @default false + */ + asChild?: boolean; +} - const cleanup = autoUpdate(elements.reference, elements.floating, update); +const PopoverTrigger = React.forwardRef & PopoverTriggerProps>( + function PopoverTrigger({ children, asChild = false, ...props }, propRef) { + const { refs, getReferenceProps, open, setOpen } = usePopoverContext(); + const childrenRef = (children as any).ref; + const ref = useMergeRefs([refs.setReference, propRef, childrenRef]); - return () => cleanup(); - }, [elements, update, isOpen]); + if (asChild && React.isValidElement(children)) { + return React.cloneElement( + children, + getReferenceProps({ + ref, + ...props, + ...children.props, + }), + ); + } - return ( - <> - {referenceElement({ ref: refs.setReference, ...getReferenceProps() })} -
setOpen?.(!open)} + aria-expanded={open} + {...getReferenceProps(props)} > {children} -
- - ); -}) as PopoverComponent; + + ); + }, +); + +interface PopoverContentProps { + /** + * Padding of the popover content element. + * @default 0 + */ + padding?: 0 | 8 | 16 | 24; +} + +const PopoverContent = React.forwardRef & PopoverContentProps>( + function PopoverContent({ style, className, padding, ...props }, propRef) { + const { context: floatingContext, ...context } = usePopoverContext(); + const ref = useMergeRefs([context.refs.setFloating, propRef]); + + if (!floatingContext.open) return null; + + return ( + + +
+ {props.children} +
+
+
+ ); + }, +); +Popover.Trigger = PopoverTrigger; Popover.Content = PopoverContent; export default Popover; diff --git a/packages/popover-react/src/PopoverContent.tsx b/packages/popover-react/src/PopoverContent.tsx deleted file mode 100644 index 763ecfb3258..00000000000 --- a/packages/popover-react/src/PopoverContent.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import classNames from "classnames"; -import React from "react"; - -export interface PopoverContentProps extends React.HTMLAttributes { - children: React.ReactNode; -} - -const PopoverContent = React.forwardRef(function PopoverContent( - { className, ...rest }, - ref, -) { - return
; -}); - -export default PopoverContent; From f3f66d74ab38d2aef6267eb8f311e34dbc33d9ab Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Mon, 26 Aug 2024 20:18:57 +0200 Subject: [PATCH 08/20] feat: add popover component tests --- packages/popover-react/src/Popover.test.tsx | 107 +++++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx index 15cbc583721..4274e622d2d 100644 --- a/packages/popover-react/src/Popover.test.tsx +++ b/packages/popover-react/src/Popover.test.tsx @@ -1,15 +1,116 @@ import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; import { Popover } from "."; describe("Popover", () => { - test("open", () => { + test("should open when trigger is clicked (uncontrolled)", async () => { + const user = userEvent.setup(); + render( - -
+ + Open Popover + Content , ); + // Popover should be hidden initially + expect(screen.queryByTestId("popover-id")).not.toBeInTheDocument(); + + // Click the trigger to open the popover + await user.click(screen.getByText("Open Popover")); + + // Popover should be visible expect(screen.getByTestId("popover-id")).toBeVisible(); }); + + test("should render when open is controlled", () => { + render( + + Content + , + ); + + expect(screen.getByTestId("popover-id")).toBeVisible(); + }); + + test("should not render when open is controlled and set to false", () => { + render( + + Content + , + ); + + expect(screen.queryByTestId("popover-id")).not.toBeInTheDocument(); + }); + + test("should call onOpenChange when trigger is clicked (controlled)", async () => { + const user = userEvent.setup(); + + const handleOpenChange = jest.fn(); + + render( + + Open Popover + Content + , + ); + + await user.click(screen.getByText("Open Popover")); + + expect(handleOpenChange).toHaveBeenCalledWith(true); + }); + + test("should open on hover when hoverProps are enabled", async () => { + const user = userEvent.setup(); + + render( + + Hover Popover + Content + , + ); + + expect(screen.queryByTestId("popover-id")).not.toBeInTheDocument(); + + await user.hover(screen.getByText("Hover Popover")); + + expect(screen.queryByTestId("popover-id")).toBeInTheDocument(); + + await user.unhover(screen.getByText("Hover Popover")); + + expect(screen.queryByTestId("popover-id")).not.toBeInTheDocument(); + }); + + test("should have role changed when roleProps are set", () => { + render( + + Open Popover + Content + , + ); + + expect(screen.getByTestId("popover-id")).toHaveAttribute("role", "menu"); + }); + + test("should correctly trap focus when modal is enabled", async () => { + const user = userEvent.setup(); + + render( + + Open Popover + + + + , + ); + + await user.tab(); + + await user.keyboard("[Space]"); + + const focusableElement = screen.getByText("Focusable Element"); + + expect(focusableElement).toHaveFocus(); + }); }); From 92c5f840684b84bafedbb679ee833a11939c754f Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Wed, 28 Aug 2024 08:15:58 +0200 Subject: [PATCH 09/20] feat: update popover component styles, init docs and imports --- .../popover-react/documentation/Popover.mdx | 30 +++++++++++++++---- packages/popover-react/src/Popover.tsx | 4 +-- packages/popover/popover.scss | 10 ++----- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/popover-react/documentation/Popover.mdx b/packages/popover-react/documentation/Popover.mdx index 8178ee6f317..fc87a41ac8d 100644 --- a/packages/popover-react/documentation/Popover.mdx +++ b/packages/popover-react/documentation/Popover.mdx @@ -5,11 +5,31 @@ scss: popover group: edit me! --- -import PopoverExample, { popoverExampleCode, popoverExampleKnobs } from "./PopoverExample"; - -Ingress med kort beskrivelse av komponenten - - +import { + PopoverControlledExample, + PopoverUnControlledExample, + popoverControlledExampleCode, + popoverUnControlledExampleCode, + popoverExampleKnobs, +} from "./PopoverExample"; + + + En popover brukes til å vise innhold i et flytende vindu og legger seg over alle andre elementer i grensesnittet. + Man styrer selv hvordan den vises og interageres med, ofte ved å klikke på et element som en knapp. + + +** Popover er en kontrollert komponent som viser innhold i en boks som dukker opp ved siden av en trigger. ** + + + Første eksempel må være synlig uten å scrolle og skal ha et kodeeksempel som oppdateres til å matche valgte parametere. diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 51f57b7bd82..7240e899a6c 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -22,7 +22,7 @@ import { Props as UseHoverProps } from "@floating-ui/react/src/hooks/useHover"; import { Props as UseRoleProps } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; import * as React from "react"; -import { Button } from "../../button-react/src"; +import { Button } from "../../button-react"; interface PopoverOptions { /** @@ -240,7 +240,7 @@ const PopoverContent = React.forwardRef
Date: Wed, 28 Aug 2024 11:26:51 +0200 Subject: [PATCH 10/20] feat: opprett getThemeAndDensity hjelpefunksjon --- .../src/ContextualMenu.tsx | 19 ++------------- portal/src/utils/getThemeAndDensity.ts | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 portal/src/utils/getThemeAndDensity.ts diff --git a/packages/contextual-menu-react/src/ContextualMenu.tsx b/packages/contextual-menu-react/src/ContextualMenu.tsx index 59ee6c90b4a..13eec08fbf8 100644 --- a/packages/contextual-menu-react/src/ContextualMenu.tsx +++ b/packages/contextual-menu-react/src/ContextualMenu.tsx @@ -27,6 +27,7 @@ import cn from "classnames"; import { AnimatePresence, motion } from "framer-motion"; import React, { type ButtonHTMLAttributes, forwardRef, type ReactNode, useEffect, useRef, useState } from "react"; import * as ReactIs from "react-is"; +import { getThemeAndDensity } from "../../../portal/src/utils/getThemeAndDensity"; import { useMenuWideEvents } from "./useMenuWideEvents"; export interface ContextualMenuProps @@ -136,23 +137,7 @@ const ContextualMenuComponent = forwardRef diff --git a/portal/src/utils/getThemeAndDensity.ts b/portal/src/utils/getThemeAndDensity.ts new file mode 100644 index 00000000000..1211d256239 --- /dev/null +++ b/portal/src/utils/getThemeAndDensity.ts @@ -0,0 +1,24 @@ +/** + * Retrieves the theme and density from the computed styles of a given element. + * + * This utility is specifically designed for scenarios where the side menu is rendered + * at the root level, requiring us to fetch the local dark/light theme value from the trigger element. + * This is necessary because the theme can be controlled locally for different parts of the UI. + * + * @param element - The HTML element to compute styles from. + * @returns An object containing the theme ('dark' or 'light') and density ('compact' or 'comfortable'). + */ +export const getThemeAndDensity = (element: HTMLElement | null): { theme?: string; density?: string } => { + if (!element) return {}; + + const computedStyles = getComputedStyle(element); + + const theme = + parseInt(computedStyles.getPropertyValue("--jkl-background-color").replace("#", ""), 16) < 0xffffff / 2 + ? "dark" + : "light"; + + const density = computedStyles.getPropertyValue("--jkl-density") === '"compact"' ? "compact" : "comfortable"; + + return { theme, density }; +}; From 86817729d5b392da4a3406a213dd0c30eee12823 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Wed, 28 Aug 2024 11:28:09 +0200 Subject: [PATCH 11/20] feat: legg til lodash pakke og utvidelse av popover komponent --- packages/popover-react/package.json | 4 +- packages/popover-react/src/Popover.tsx | 180 ++++++++++++++----------- pnpm-lock.yaml | 8 ++ 3 files changed, 109 insertions(+), 83 deletions(-) diff --git a/packages/popover-react/package.json b/packages/popover-react/package.json index 038c5405337..23951f2b560 100644 --- a/packages/popover-react/package.json +++ b/packages/popover-react/package.json @@ -39,7 +39,9 @@ "@floating-ui/react": "^0.24.2", "@fremtind/jkl-core": "^14.0.0", "@fremtind/jkl-popover": "^1.0.0-alpha.0", - "classnames": "^2.5.1" + "@types/lodash": "^4.17.7", + "classnames": "^2.5.1", + "lodash": "^4.17.21" }, "peerDependencies": { "@types/react": "^16.8.6 || ^17.0.0 || ^18.0.0", diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 7240e899a6c..f8930cfafc7 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -1,6 +1,4 @@ import { - type Placement, - type Strategy, useFloating, useFocus, useHover, @@ -15,13 +13,18 @@ import { useInteractions, FloatingPortal, FloatingFocusManager, + UseFloatingOptions, } from "@floating-ui/react"; -import { Props as UseClickProps } from "@floating-ui/react/src/hooks/useClick"; -import { Props as UseFocusProps } from "@floating-ui/react/src/hooks/useFocus"; -import { Props as UseHoverProps } from "@floating-ui/react/src/hooks/useHover"; -import { Props as UseRoleProps } from "@floating-ui/react/src/hooks/useRole"; +import { Props as FloatingFocusManagerOptions } from "@floating-ui/react/src/components/FloatingFocusManager"; +import { Props as UseClickOptions } from "@floating-ui/react/src/hooks/useClick"; +import { Props as UseDismissOptions } from "@floating-ui/react/src/hooks/useDismiss"; +import { Props as UseFocusOptions } from "@floating-ui/react/src/hooks/useFocus"; +import { Props as UseHoverOptions } from "@floating-ui/react/src/hooks/useHover"; +import { Props as UseRoleOptions } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; +import { merge } from "lodash"; import * as React from "react"; +import { getThemeAndDensity } from "../../../portal/src/utils/getThemeAndDensity"; import { Button } from "../../button-react"; interface PopoverOptions { @@ -31,128 +34,138 @@ interface PopoverOptions { * @see https://floating-ui.com/docs/useFloating#open */ initialOpen?: boolean; - /** - * Determines if focus is “modal”, meaning focus is fully trapped inside the floating element and outside content cannot be accessed. This includes screen reader virtual cursors. - * @see https://floating-ui.com/docs/floatingfocusmanager#modal - * @default true - */ - modal?: boolean; - /** - * Controlled open state of the popover. - * @see https://floating-ui.com/docs/useFloating#open - */ - open?: boolean; - /** - * Callback to change the open state of the popover. - */ - onOpenChange?: (open: boolean) => void; /** * Offset of the floating element. * @see https://floating-ui.com/docs/offset * @default 4 - */ + * */ offset?: number; /** * Strategy for positioning the floating element. * @default absolute * @see https://floating-ui.com/docs/useFloating#strategy */ - strategy?: Strategy; - /** - * Placement of the floating element. - * @default bottom-start - * @see https://floating-ui.com/docs/useFloating#placement - */ - placement?: Placement; - /** Hover options - * @see {@link UseHoverProps} + /** Options for hover + * @see {@link UseHoverOptions} * @see https://floating-ui.com/docs/useHover * @default { enabled: false } */ - hoverProps?: UseHoverProps; - /** Focus options - * @see {@link UseFocusProps} + hoverOptions?: UseHoverOptions; + /** Options for focus + * @see {@link UseFocusOptions} * @see https://floating-ui.com/docs/useFocus * @default { enabled: false } */ - focusProps?: UseFocusProps; - /** Click options - * @see {@link UseClickProps} + focusOptions?: UseFocusOptions; + /** Options for click + * @see {@link UseClickOptions} * @see https://floating-ui.com/docs/useClick * @default { enabled: false } */ - clickProps?: UseClickProps; + clickOptions?: UseClickOptions; /** - * Role options - * @see {@link UseRoleProps} + * Options for role + * @see {@link UseRoleOptions} * @see https://floating-ui.com/docs/useRole * @default { enabled: true, role: "dialog" } */ - roleProps?: UseRoleProps; + roleOptions?: UseRoleOptions; /** - * Floating options + * Options for dismiss + * @see {@link UseDismissOptions} + * @see https://floating-ui.com/docs + * @default { enabled: false } + */ + dismissOptions?: UseDismissOptions; + /** + * Options for å posisjonere et flytende element * @see https://floating-ui.com/docs/useFloating */ + floatingOptions?: Omit, "whileElementsMounted">; + /** + * Options for FloatingFocusManager + * @see https://floating-ui.com/docs/floatingfocusmanager + */ + floatingFocusManagerOptions?: Omit; } const usePopover = ({ initialOpen = false, - strategy = "absolute", - placement = "bottom-start", offset: _offset = 4, - modal = false, - open: controlledOpen, - onOpenChange: setControlledOpen, - hoverProps, - focusProps, - clickProps, - roleProps, + hoverOptions, + focusOptions, + clickOptions, + roleOptions, + dismissOptions, + floatingOptions: _floatingOptions, + floatingFocusManagerOptions: _floatingFocusManagerOptions, }: PopoverOptions) => { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); - const open = controlledOpen ?? uncontrolledOpen; - const setOpen = setControlledOpen ?? setUncontrolledOpen; + const open = _floatingOptions?.open ?? uncontrolledOpen; + const onOpenChange = _floatingOptions?.onOpenChange ?? setUncontrolledOpen; - const data = useFloating({ - open, - placement, - strategy, - onOpenChange: setOpen, - whileElementsMounted: autoUpdate, - middleware: [ - offset(_offset), - flip({ crossAxis: placement.includes("-"), padding: 5, fallbackPlacements: ["bottom", "top"] }), - shift({ padding: 12 }), - ], - }); + const floatingOptions = merge( + { + open, + placement: _floatingOptions?.placement ?? "bottom-start", + strategy: _floatingOptions?.strategy ?? "absolute", + middleware: [ + offset(_offset), + flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), + shift({ padding: 12 }), + ], + whileElementsMounted: autoUpdate, + onOpenChange, + }, + _floatingOptions, + ); + + const floatingFocusManagerOptions = merge( + { + order: ["reference", "floating", "content"], + initialFocus: 0, + guards: true, + returnFocus: true, + modal: true, + visuallyHiddenDismiss: true, + closeOnFocusOut: true, + }, + _floatingFocusManagerOptions, + ); + + const data = useFloating({ ...floatingOptions }); const context = data.context; const role = useRole(context, { - ...roleProps, - enabled: roleProps?.enabled ?? true, - role: roleProps?.role ?? "dialog", + ...roleOptions, + enabled: roleOptions?.enabled ?? true, + role: roleOptions?.role ?? "dialog", }); const click = useClick(context, { - ...clickProps, - enabled: (controlledOpen || clickProps?.enabled) ?? false, + ...clickOptions, + enabled: (floatingOptions?.open || clickOptions?.enabled) ?? false, + }); + const hover = useHover(context, { ...hoverOptions, enabled: hoverOptions?.enabled ?? false }); + const focus = useFocus(context, { ...focusOptions, enabled: focusOptions?.enabled ?? false }); + const dismiss = useDismiss(context, { + ...dismissOptions, + enabled: dismissOptions?.enabled ?? false, }); - const hover = useHover(context, { ...hoverProps, enabled: hoverProps?.enabled ?? false }); - const focus = useFocus(context, { ...focusProps, enabled: focusProps?.enabled ?? false }); - const dismiss = useDismiss(context); const interactions = useInteractions([click, dismiss, focus, hover, , role]); return React.useMemo( () => ({ open, - setOpen, + onOpenChange, ...interactions, ...data, - modal, + floatingFocusManagerOptions, }), - [open, setOpen, interactions, data, modal], + [open, onOpenChange, interactions, data, floatingFocusManagerOptions], ); }; @@ -172,12 +185,11 @@ const usePopoverContext = () => { const Popover = ({ children, - modal = false, ...restOptions }: { children: React.ReactNode; } & PopoverOptions) => { - const popover = usePopover({ modal, ...restOptions }); + const popover = usePopover({ ...restOptions }); return {children}; }; @@ -192,7 +204,7 @@ interface PopoverTriggerProps { const PopoverTrigger = React.forwardRef & PopoverTriggerProps>( function PopoverTrigger({ children, asChild = false, ...props }, propRef) { - const { refs, getReferenceProps, open, setOpen } = usePopoverContext(); + const { refs, getReferenceProps, open, onOpenChange } = usePopoverContext(); const childrenRef = (children as any).ref; const ref = useMergeRefs([refs.setReference, propRef, childrenRef]); @@ -211,7 +223,7 @@ const PopoverTrigger = React.forwardRef setOpen?.(!open)} + onClick={() => onOpenChange?.(!open)} aria-expanded={open} {...getReferenceProps(props)} > @@ -231,15 +243,19 @@ interface PopoverContentProps { const PopoverContent = React.forwardRef & PopoverContentProps>( function PopoverContent({ style, className, padding, ...props }, propRef) { - const { context: floatingContext, ...context } = usePopoverContext(); + const { context: floatingContext, floatingFocusManagerOptions, ...context } = usePopoverContext(); const ref = useMergeRefs([context.refs.setFloating, propRef]); + const { theme, density } = getThemeAndDensity(context.refs.reference.current as HTMLElement); + if (!floatingContext.open) return null; return ( - +
Date: Wed, 28 Aug 2024 15:02:59 +0200 Subject: [PATCH 12/20] refactor: mindre refaktorering --- .../src/ContextualMenu.tsx | 19 +++++- .../documentation/PopoverExample.tsx | 10 +-- packages/popover-react/src/Popover.tsx | 67 ++++++++----------- packages/popover-react/src/utils.ts | 14 ++++ portal/src/utils/getThemeAndDensity.ts | 24 ------- 5 files changed, 64 insertions(+), 70 deletions(-) create mode 100644 packages/popover-react/src/utils.ts delete mode 100644 portal/src/utils/getThemeAndDensity.ts diff --git a/packages/contextual-menu-react/src/ContextualMenu.tsx b/packages/contextual-menu-react/src/ContextualMenu.tsx index 13eec08fbf8..59ee6c90b4a 100644 --- a/packages/contextual-menu-react/src/ContextualMenu.tsx +++ b/packages/contextual-menu-react/src/ContextualMenu.tsx @@ -27,7 +27,6 @@ import cn from "classnames"; import { AnimatePresence, motion } from "framer-motion"; import React, { type ButtonHTMLAttributes, forwardRef, type ReactNode, useEffect, useRef, useState } from "react"; import * as ReactIs from "react-is"; -import { getThemeAndDensity } from "../../../portal/src/utils/getThemeAndDensity"; import { useMenuWideEvents } from "./useMenuWideEvents"; export interface ContextualMenuProps @@ -137,7 +136,23 @@ const ContextualMenuComponent = forwardRef diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index 5d8d352a191..9586cbb8dd9 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -152,9 +152,11 @@ export const PopoverControlledExample: FC = () => { return ( @@ -171,7 +173,7 @@ export const PopoverControlledExample: FC = () => { export const PopoverUnControlledExample: FC = () => { return ( diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index f8930cfafc7..f4c0bcfaece 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -16,20 +16,20 @@ import { UseFloatingOptions, } from "@floating-ui/react"; import { Props as FloatingFocusManagerOptions } from "@floating-ui/react/src/components/FloatingFocusManager"; -import { Props as UseClickOptions } from "@floating-ui/react/src/hooks/useClick"; -import { Props as UseDismissOptions } from "@floating-ui/react/src/hooks/useDismiss"; -import { Props as UseFocusOptions } from "@floating-ui/react/src/hooks/useFocus"; -import { Props as UseHoverOptions } from "@floating-ui/react/src/hooks/useHover"; -import { Props as UseRoleOptions } from "@floating-ui/react/src/hooks/useRole"; +import { Props as ClickOptions } from "@floating-ui/react/src/hooks/useClick"; +import { Props as DismissOptions } from "@floating-ui/react/src/hooks/useDismiss"; +import { Props as FocusOptions } from "@floating-ui/react/src/hooks/useFocus"; +import { Props as HoverOptions } from "@floating-ui/react/src/hooks/useHover"; +import { Props as RoleOptions } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; import { merge } from "lodash"; import * as React from "react"; -import { getThemeAndDensity } from "../../../portal/src/utils/getThemeAndDensity"; import { Button } from "../../button-react"; +import { getThemeAndDensity } from "./utils"; interface PopoverOptions { /** - * Initial open state of the popover. + * Initial open state of the popover when uncontrolled. * @default false * @see https://floating-ui.com/docs/useFloating#open */ @@ -46,37 +46,37 @@ interface PopoverOptions { * @see https://floating-ui.com/docs/useFloating#strategy */ /** Options for hover - * @see {@link UseHoverOptions} + * @see {@link HoverOptions} * @see https://floating-ui.com/docs/useHover * @default { enabled: false } */ - hoverOptions?: UseHoverOptions; + hoverOptions?: HoverOptions; /** Options for focus - * @see {@link UseFocusOptions} + * @see {@link FocusOptions} * @see https://floating-ui.com/docs/useFocus * @default { enabled: false } */ - focusOptions?: UseFocusOptions; + focusOptions?: FocusOptions; /** Options for click - * @see {@link UseClickOptions} + * @see {@link ClickOptions} * @see https://floating-ui.com/docs/useClick * @default { enabled: false } */ - clickOptions?: UseClickOptions; + clickOptions?: ClickOptions; /** * Options for role - * @see {@link UseRoleOptions} + * @see {@link RoleOptions} * @see https://floating-ui.com/docs/useRole * @default { enabled: true, role: "dialog" } */ - roleOptions?: UseRoleOptions; + roleOptions?: RoleOptions; /** * Options for dismiss - * @see {@link UseDismissOptions} + * @see {@link DismissOptions} * @see https://floating-ui.com/docs * @default { enabled: false } */ - dismissOptions?: UseDismissOptions; + dismissOptions?: DismissOptions; /** * Options for å posisjonere et flytende element * @see https://floating-ui.com/docs/useFloating @@ -98,7 +98,7 @@ const usePopover = ({ roleOptions, dismissOptions, floatingOptions: _floatingOptions, - floatingFocusManagerOptions: _floatingFocusManagerOptions, + floatingFocusManagerOptions, }: PopoverOptions) => { const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); @@ -108,8 +108,8 @@ const usePopover = ({ const floatingOptions = merge( { open, - placement: _floatingOptions?.placement ?? "bottom-start", - strategy: _floatingOptions?.strategy ?? "absolute", + placement: "bottom-start", + strategy: "absolute", middleware: [ offset(_offset), flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), @@ -121,38 +121,25 @@ const usePopover = ({ _floatingOptions, ); - const floatingFocusManagerOptions = merge( - { - order: ["reference", "floating", "content"], - initialFocus: 0, - guards: true, - returnFocus: true, - modal: true, - visuallyHiddenDismiss: true, - closeOnFocusOut: true, - }, - _floatingFocusManagerOptions, - ); - const data = useFloating({ ...floatingOptions }); const context = data.context; const role = useRole(context, { + enabled: true, + role: "dialog", ...roleOptions, - enabled: roleOptions?.enabled ?? true, - role: roleOptions?.role ?? "dialog", }); const click = useClick(context, { + enabled: false, ...clickOptions, - enabled: (floatingOptions?.open || clickOptions?.enabled) ?? false, }); - const hover = useHover(context, { ...hoverOptions, enabled: hoverOptions?.enabled ?? false }); - const focus = useFocus(context, { ...focusOptions, enabled: focusOptions?.enabled ?? false }); + const hover = useHover(context, { enabled: false, ...hoverOptions }); + const focus = useFocus(context, { enabled: false, ...focusOptions }); const dismiss = useDismiss(context, { + enabled: true, ...dismissOptions, - enabled: dismissOptions?.enabled ?? false, }); const interactions = useInteractions([click, dismiss, focus, hover, , role]); @@ -161,9 +148,9 @@ const usePopover = ({ () => ({ open, onOpenChange, + floatingFocusManagerOptions, ...interactions, ...data, - floatingFocusManagerOptions, }), [open, onOpenChange, interactions, data, floatingFocusManagerOptions], ); diff --git a/packages/popover-react/src/utils.ts b/packages/popover-react/src/utils.ts new file mode 100644 index 00000000000..9307640406b --- /dev/null +++ b/packages/popover-react/src/utils.ts @@ -0,0 +1,14 @@ +export const getThemeAndDensity = (element: HTMLElement | null): { theme?: string; density?: string } => { + if (!element) return {}; + + const computedStyles = getComputedStyle(element); + + const theme = + parseInt(computedStyles.getPropertyValue("--jkl-background-color").replace("#", ""), 16) < 0xffffff / 2 + ? "dark" + : "light"; + + const density = computedStyles.getPropertyValue("--jkl-density") === '"compact"' ? "compact" : "comfortable"; + + return { theme, density }; +}; diff --git a/portal/src/utils/getThemeAndDensity.ts b/portal/src/utils/getThemeAndDensity.ts deleted file mode 100644 index 1211d256239..00000000000 --- a/portal/src/utils/getThemeAndDensity.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Retrieves the theme and density from the computed styles of a given element. - * - * This utility is specifically designed for scenarios where the side menu is rendered - * at the root level, requiring us to fetch the local dark/light theme value from the trigger element. - * This is necessary because the theme can be controlled locally for different parts of the UI. - * - * @param element - The HTML element to compute styles from. - * @returns An object containing the theme ('dark' or 'light') and density ('compact' or 'comfortable'). - */ -export const getThemeAndDensity = (element: HTMLElement | null): { theme?: string; density?: string } => { - if (!element) return {}; - - const computedStyles = getComputedStyle(element); - - const theme = - parseInt(computedStyles.getPropertyValue("--jkl-background-color").replace("#", ""), 16) < 0xffffff / 2 - ? "dark" - : "light"; - - const density = computedStyles.getPropertyValue("--jkl-density") === '"compact"' ? "compact" : "comfortable"; - - return { theme, density }; -}; From eac5cd6a8025e4613d21ffe86704d93156bed57b Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Sun, 8 Sep 2024 20:25:06 +0200 Subject: [PATCH 13/20] refactor: oppdater props kommentarer --- packages/popover-react/src/Popover.tsx | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index f4c0bcfaece..b6115fee0f1 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -29,22 +29,17 @@ import { getThemeAndDensity } from "./utils"; interface PopoverOptions { /** - * Initial open state of the popover when uncontrolled. + * Den initielle åpne tilstanden for popoveren når den er ukontrollert. * @default false * @see https://floating-ui.com/docs/useFloating#open */ initialOpen?: boolean; /** - * Offset of the floating element. + * Offset til det flytende elementet. * @see https://floating-ui.com/docs/offset * @default 4 * */ offset?: number; - /** - * Strategy for positioning the floating element. - * @default absolute - * @see https://floating-ui.com/docs/useFloating#strategy - */ /** Options for hover * @see {@link HoverOptions} * @see https://floating-ui.com/docs/useHover @@ -142,7 +137,7 @@ const usePopover = ({ ...dismissOptions, }); - const interactions = useInteractions([click, dismiss, focus, hover, , role]); + const interactions = useInteractions([click, dismiss, focus, hover, role]); return React.useMemo( () => ({ @@ -183,7 +178,18 @@ const Popover = ({ interface PopoverTriggerProps { children: React.ReactNode; /** - * Render the trigger as a child of the popover. + * Rendrer komponenten som child-elementet sitt, og slår + * sammen egenskaper og props. + * @example + * ```tsx + * + * + * + * + * // Rendrer følgende: + * + * ``` + * * @default false */ asChild?: boolean; @@ -222,7 +228,7 @@ const PopoverTrigger = React.forwardRef Date: Tue, 10 Sep 2024 13:46:20 +0200 Subject: [PATCH 14/20] refactor: fjern default verdier --- packages/popover-react/src/Popover.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index b6115fee0f1..108bb64707f 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -121,8 +121,6 @@ const usePopover = ({ const context = data.context; const role = useRole(context, { - enabled: true, - role: "dialog", ...roleOptions, }); @@ -236,16 +234,17 @@ interface PopoverContentProps { const PopoverContent = React.forwardRef & PopoverContentProps>( function PopoverContent({ style, className, padding, ...props }, propRef) { - const { context: floatingContext, floatingFocusManagerOptions, ...context } = usePopoverContext(); - const ref = useMergeRefs([context.refs.setFloating, propRef]); + const { context, floatingFocusManagerOptions, refs, open, floatingStyles, getFloatingProps } = + usePopoverContext(); + const ref = useMergeRefs([refs.setFloating, propRef]); - const { theme, density } = getThemeAndDensity(context.refs.reference.current as HTMLElement); + const { theme, density } = getThemeAndDensity(refs.reference.current as HTMLElement); - if (!floatingContext.open) return null; + if (!open) return null; return ( - +
{props.children}
From 464618e8c56f73e431b6a1dd075ae1b4fb93477e Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Wed, 11 Sep 2024 13:29:49 +0200 Subject: [PATCH 15/20] refactor: popover component and update documentation --- .../popover-react/documentation/Popover.mdx | 36 ++++++++----------- packages/popover-react/src/Popover.tsx | 5 --- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/packages/popover-react/documentation/Popover.mdx b/packages/popover-react/documentation/Popover.mdx index fc87a41ac8d..1cc231e0880 100644 --- a/packages/popover-react/documentation/Popover.mdx +++ b/packages/popover-react/documentation/Popover.mdx @@ -14,42 +14,36 @@ import { } from "./PopoverExample"; - En popover brukes til å vise innhold i et flytende vindu og legger seg over alle andre elementer i grensesnittet. - Man styrer selv hvordan den vises og interageres med, ofte ved å klikke på et element som en knapp. + Popover brukes til å vise innhold i et flytende vindu og legger seg over alle andre elementer i grensesnittet. Man + styrer selv hvordan den vises og interageres med, ofte ved å klikke på et element som en knapp. -** Popover er en kontrollert komponent som viser innhold i en boks som dukker opp ved siden av en trigger. ** +## Kontrollert vs Ukontrollert + +En kontrollert Popover-komponent gir deg full kontroll over åpning og lukking ved å tillate ekstern håndtering av tilstandene. + +En ukontrollert Popover-komponent håndterer tilstandene selv og åpner og lukker seg basert på interaksjon med trigger-elementet. + -Første eksempel må være synlig uten å scrolle og skal ha et kodeeksempel som oppdateres til å matche valgte parametere. - -Fyll på med mer utfyllende beskrivelse av komponenten her under eksempelet, for eksempel føringer for innhold. List opp de ulike variantene dersom komponenten har dem, sammen med beskrivelse av når den ene varianten bør brukes i stedet for en annen. - -Dokumentér riktig og feil bruk av varianter visuelt med `DoDontExample`. Se for eksempel [Tag](/komponenter/tag) for inspirasjon. - ## Tilgjengelighet -Dokumenter spesielle hensyn for universell utforming. Eksempler kan være spesielle hensyn som må tas for brukere av skjermlesere, for fargeblinde, eller liknende. - -## Når bruker vi Popover? - -Før du bruker Popover er det greit å ha tatt stilling til noen spørsmål: - -- Liste med kontrollspørsmål -- Se [Message box](/komponenter/messagebox#når-bruker-vi-en-melding-) for eksempler - -Dokumentér riktig og feil bruk av komponenten visuelt med `DoDontExample`. Se for eksempel [Alert message](/komponenter/alertmessage#når-bruker-vi-en-varselmelding-) for inspirasjon. +Popover-komponenten tar i bruk useRole-hooken fra [Floating UI](https://floating-ui.com/docs/userole) som automatisk tildeler nødvendige ARIA-roller til trigger- og det flytende elementet. -Om det er relevant, lenk direkte til andre komponenter som er riktig å bruke dersom denne komponenten ikke bør brukes i en gitt situasjon. +Som default er role satt til "dialog". Er innholdet i Popover foreksempel tiltenkt en meny, bør role-proppen i roleOptions settes til "menu". -Kontroller at Gatsby klarer å generere React API-dokumentasjon riktig i bunnen. Om komponenten din ikke dukker opp riktig, sørg for at den har et `displayName`. Om du får flere komponenter enn du ønsker i tabellen kan du bruke [`displayTypes` i frontmatter](https://github.com/fremtind/jokul/blob/60edb292a922eea69e539875359524e2c13eda3e/packages/core/documentation/Link.mdx?plain=1#L6-L8). +```tsx +{/* Popover innhold */} +``` diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 108bb64707f..543e0cbdff1 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -41,33 +41,28 @@ interface PopoverOptions { * */ offset?: number; /** Options for hover - * @see {@link HoverOptions} * @see https://floating-ui.com/docs/useHover * @default { enabled: false } */ hoverOptions?: HoverOptions; /** Options for focus - * @see {@link FocusOptions} * @see https://floating-ui.com/docs/useFocus * @default { enabled: false } */ focusOptions?: FocusOptions; /** Options for click - * @see {@link ClickOptions} * @see https://floating-ui.com/docs/useClick * @default { enabled: false } */ clickOptions?: ClickOptions; /** * Options for role - * @see {@link RoleOptions} * @see https://floating-ui.com/docs/useRole * @default { enabled: true, role: "dialog" } */ roleOptions?: RoleOptions; /** * Options for dismiss - * @see {@link DismissOptions} * @see https://floating-ui.com/docs * @default { enabled: false } */ From 975b2b0e77a23be371c34fa983ccfcb7ff59caee Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Wed, 11 Sep 2024 13:45:52 +0200 Subject: [PATCH 16/20] fix: oppdater tester og docs --- .../documentation/PopoverExample.tsx | 52 ++++++++++--------- packages/popover-react/src/Popover.test.tsx | 12 ++--- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index 9586cbb8dd9..a794ad95f36 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -186,31 +186,35 @@ export const PopoverUnControlledExample: FC = () => { }; export const popoverControlledExampleCode: CodeExample = () => ` - - setOpen?.(!open)} aria-expanded={open} asChild> - - - - - - +const [open, setOpen] = React.useState(false); + + + setOpen?.(!open)} aria-expanded={open} asChild> + + + + + + `; export const popoverUnControlledExampleCode: CodeExample = () => ` - - Åpne popover - - - - + + Åpne popover + + + + `; diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx index 4274e622d2d..d9c917d129a 100644 --- a/packages/popover-react/src/Popover.test.tsx +++ b/packages/popover-react/src/Popover.test.tsx @@ -26,7 +26,7 @@ describe("Popover", () => { test("should render when open is controlled", () => { render( - + Content , ); @@ -36,7 +36,7 @@ describe("Popover", () => { test("should not render when open is controlled and set to false", () => { render( - + Content , ); @@ -50,7 +50,7 @@ describe("Popover", () => { const handleOpenChange = jest.fn(); render( - + Open Popover Content , @@ -65,7 +65,7 @@ describe("Popover", () => { const user = userEvent.setup(); render( - + Hover Popover Content , @@ -84,7 +84,7 @@ describe("Popover", () => { test("should have role changed when roleProps are set", () => { render( - + Open Popover Content , @@ -97,7 +97,7 @@ describe("Popover", () => { const user = userEvent.setup(); render( - + Open Popover From 86b0700a2efa7510d6caf44e22a7aa42f3027656 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Thu, 12 Sep 2024 13:41:33 +0200 Subject: [PATCH 17/20] fix: lag typer ved hjelp av parameters utils og fiks tester --- packages/popover-react/src/Popover.test.tsx | 14 +++++++++----- packages/popover-react/src/Popover.tsx | 13 +++++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/popover-react/src/Popover.test.tsx b/packages/popover-react/src/Popover.test.tsx index d9c917d129a..c22275edd8c 100644 --- a/packages/popover-react/src/Popover.test.tsx +++ b/packages/popover-react/src/Popover.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; import { Popover } from "."; @@ -58,7 +58,9 @@ describe("Popover", () => { await user.click(screen.getByText("Open Popover")); - expect(handleOpenChange).toHaveBeenCalledWith(true); + await waitFor(() => { + expect(handleOpenChange).toHaveBeenCalled(); + }); }); test("should open on hover when hoverProps are enabled", async () => { @@ -82,9 +84,9 @@ describe("Popover", () => { expect(screen.queryByTestId("popover-id")).not.toBeInTheDocument(); }); - test("should have role changed when roleProps are set", () => { + test("should have role changed when roleOptions are set", () => { render( - + Open Popover Content , @@ -111,6 +113,8 @@ describe("Popover", () => { const focusableElement = screen.getByText("Focusable Element"); - expect(focusableElement).toHaveFocus(); + await waitFor(() => { + expect(focusableElement).toHaveFocus(); + }); }); }); diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 543e0cbdff1..8e9d9fd78ae 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -15,18 +15,19 @@ import { FloatingFocusManager, UseFloatingOptions, } from "@floating-ui/react"; -import { Props as FloatingFocusManagerOptions } from "@floating-ui/react/src/components/FloatingFocusManager"; -import { Props as ClickOptions } from "@floating-ui/react/src/hooks/useClick"; -import { Props as DismissOptions } from "@floating-ui/react/src/hooks/useDismiss"; -import { Props as FocusOptions } from "@floating-ui/react/src/hooks/useFocus"; -import { Props as HoverOptions } from "@floating-ui/react/src/hooks/useHover"; -import { Props as RoleOptions } from "@floating-ui/react/src/hooks/useRole"; import classNames from "classnames"; import { merge } from "lodash"; import * as React from "react"; import { Button } from "../../button-react"; import { getThemeAndDensity } from "./utils"; +type FloatingFocusManagerOptions = React.ComponentProps; +type ClickOptions = Parameters[1]; +type DismissOptions = Parameters[1]; +type FocusOptions = Parameters[1]; +type HoverOptions = Parameters[1]; +type RoleOptions = Parameters[1]; + interface PopoverOptions { /** * Den initielle åpne tilstanden for popoveren når den er ukontrollert. From c84f22208aa8fee1c855f0bc91038c3b125064c9 Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Thu, 12 Sep 2024 13:53:31 +0200 Subject: [PATCH 18/20] refactor: ta i bruk native button --- packages/popover-react/src/Popover.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index 8e9d9fd78ae..f772ff0fdb2 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -18,7 +18,6 @@ import { import classNames from "classnames"; import { merge } from "lodash"; import * as React from "react"; -import { Button } from "../../button-react"; import { getThemeAndDensity } from "./utils"; type FloatingFocusManagerOptions = React.ComponentProps; @@ -207,15 +206,9 @@ const PopoverTrigger = React.forwardRef onOpenChange?.(!open)} - aria-expanded={open} - {...getReferenceProps(props)} - > + + ); }, ); From d246d2d2fb7d7e263e1297fd48bfa4c0888e238a Mon Sep 17 00:00:00 2001 From: kristian ulvund Date: Fri, 13 Sep 2024 22:18:38 +0200 Subject: [PATCH 19/20] refactor: forenkling av komponent --- .../documentation/PopoverExample.tsx | 12 +- packages/popover-react/package.json | 3 +- packages/popover-react/src/Popover.test.tsx | 10 +- packages/popover-react/src/Popover.tsx | 116 ++++++++++-------- pnpm-lock.yaml | 3 - 5 files changed, 76 insertions(+), 68 deletions(-) diff --git a/packages/popover-react/documentation/PopoverExample.tsx b/packages/popover-react/documentation/PopoverExample.tsx index a794ad95f36..9b97bc51a6b 100644 --- a/packages/popover-react/documentation/PopoverExample.tsx +++ b/packages/popover-react/documentation/PopoverExample.tsx @@ -152,10 +152,8 @@ export const PopoverControlledExample: FC = () => { return ( ` const [open, setOpen] = React.useState(false); { test("should render when open is controlled", () => { render( - + Content , ); @@ -36,7 +36,7 @@ describe("Popover", () => { test("should not render when open is controlled and set to false", () => { render( - + Content , ); @@ -50,7 +50,7 @@ describe("Popover", () => { const handleOpenChange = jest.fn(); render( - + Open Popover Content , @@ -86,7 +86,7 @@ describe("Popover", () => { test("should have role changed when roleOptions are set", () => { render( - + Open Popover Content , @@ -99,7 +99,7 @@ describe("Popover", () => { const user = userEvent.setup(); render( - + Open Popover diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index f772ff0fdb2..d5ce18031f4 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -16,11 +16,9 @@ import { UseFloatingOptions, } from "@floating-ui/react"; import classNames from "classnames"; -import { merge } from "lodash"; import * as React from "react"; import { getThemeAndDensity } from "./utils"; -type FloatingFocusManagerOptions = React.ComponentProps; type ClickOptions = Parameters[1]; type DismissOptions = Parameters[1]; type FocusOptions = Parameters[1]; @@ -29,89 +27,108 @@ type RoleOptions = Parameters[1]; interface PopoverOptions { /** - * Den initielle åpne tilstanden for popoveren når den er ukontrollert. - * @default false + * Angir om popoveren er åpen eller lukket. * @see https://floating-ui.com/docs/useFloating#open */ - initialOpen?: boolean; + open?: boolean; + /** + * Callback som trigges når popoveren åpnes eller lukkes. + * @see https://floating-ui.com/docs/useFloating#onOpenChange + */ + onOpenChange?: UseFloatingOptions["onOpenChange"]; + /** + * Bestemmer plasseringen av popoveren. + * @default "bottom-start" + * @see https://floating-ui.com/docs/useFloating#placement + */ + placement?: UseFloatingOptions["placement"]; + /** + * Definerer strategien for posisjonering av popoveren. + * @default "absolute" + * @see https://floating-ui.com/docs/useFloating#strategy + */ + strategy?: UseFloatingOptions["strategy"]; + /** + * Angir om popoveren skal fungere som en modal, der fokus er låst til det flytende elementet + * og innhold utenfor ikke kan interageres med. + * @default true + * @see https://floating-ui.com/docs/useFloating#modal + */ + modal?: boolean; /** * Offset til det flytende elementet. * @see https://floating-ui.com/docs/offset * @default 4 * */ + /** + * Justerer avstanden mellom referanse-elementet og popoveren. + * @see https://floating-ui.com/docs/offset + * @default 4 + */ offset?: number; - /** Options for hover + /** + * Options for hover-interaksjoner. * @see https://floating-ui.com/docs/useHover * @default { enabled: false } */ hoverOptions?: HoverOptions; - /** Options for focus + /** + * Options for fokus-interaksjoner. * @see https://floating-ui.com/docs/useFocus * @default { enabled: false } */ focusOptions?: FocusOptions; - /** Options for click + /** + * Options for klikk-interaksjoner. * @see https://floating-ui.com/docs/useClick * @default { enabled: false } */ clickOptions?: ClickOptions; /** - * Options for role + * Konfigurerer rollen for popoveren. * @see https://floating-ui.com/docs/useRole * @default { enabled: true, role: "dialog" } */ roleOptions?: RoleOptions; /** - * Options for dismiss - * @see https://floating-ui.com/docs - * @default { enabled: false } + * Options for å lukke popoveren når en dismissal skjer, + * som ved å klikke utenfor eller trykke på "Escape"-tasten. + * @see https://floating-ui.com/docs/useDismiss + * @default { enabled: true } */ dismissOptions?: DismissOptions; - /** - * Options for å posisjonere et flytende element - * @see https://floating-ui.com/docs/useFloating - */ - floatingOptions?: Omit, "whileElementsMounted">; - /** - * Options for FloatingFocusManager - * @see https://floating-ui.com/docs/floatingfocusmanager - */ - floatingFocusManagerOptions?: Omit; } const usePopover = ({ - initialOpen = false, + open: _open, + onOpenChange: _onOpenChange, + placement = "bottom-start", + strategy = "absolute", + modal = true, offset: _offset = 4, hoverOptions, focusOptions, clickOptions, roleOptions, dismissOptions, - floatingOptions: _floatingOptions, - floatingFocusManagerOptions, }: PopoverOptions) => { - const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen); - - const open = _floatingOptions?.open ?? uncontrolledOpen; - const onOpenChange = _floatingOptions?.onOpenChange ?? setUncontrolledOpen; + const [uncontrolledOpen, setUncontrolledOpen] = React.useState(_open); - const floatingOptions = merge( - { - open, - placement: "bottom-start", - strategy: "absolute", - middleware: [ - offset(_offset), - flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), - shift({ padding: 12 }), - ], - whileElementsMounted: autoUpdate, - onOpenChange, - }, - _floatingOptions, - ); + const open = _open ?? uncontrolledOpen; + const onOpenChange = _onOpenChange ?? setUncontrolledOpen; - const data = useFloating({ ...floatingOptions }); + const data = useFloating({ + open, + onOpenChange, + placement, + strategy, + middleware: [ + offset(_offset), + flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }), + shift({ padding: 12 }), + ], + whileElementsMounted: autoUpdate, + }); const context = data.context; @@ -136,11 +153,11 @@ const usePopover = ({ () => ({ open, onOpenChange, - floatingFocusManagerOptions, + modal, ...interactions, ...data, }), - [open, onOpenChange, interactions, data, floatingFocusManagerOptions], + [open, onOpenChange, modal, interactions, data], ); }; @@ -223,8 +240,7 @@ interface PopoverContentProps { const PopoverContent = React.forwardRef & PopoverContentProps>( function PopoverContent({ style, className, padding, ...props }, propRef) { - const { context, floatingFocusManagerOptions, refs, open, floatingStyles, getFloatingProps } = - usePopoverContext(); + const { context, modal, refs, open, floatingStyles, getFloatingProps } = usePopoverContext(); const ref = useMergeRefs([refs.setFloating, propRef]); const { theme, density } = getThemeAndDensity(refs.reference.current as HTMLElement); @@ -233,7 +249,7 @@ const PopoverContent = React.forwardRef - +
Date: Fri, 13 Sep 2024 22:27:48 +0200 Subject: [PATCH 20/20] refactor: popover component and update documentation --- packages/popover-react/src/Popover.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/popover-react/src/Popover.tsx b/packages/popover-react/src/Popover.tsx index d5ce18031f4..70f25442db8 100644 --- a/packages/popover-react/src/Popover.tsx +++ b/packages/popover-react/src/Popover.tsx @@ -132,20 +132,14 @@ const usePopover = ({ const context = data.context; - const role = useRole(context, { - ...roleOptions, - }); - const click = useClick(context, { enabled: false, ...clickOptions, }); const hover = useHover(context, { enabled: false, ...hoverOptions }); const focus = useFocus(context, { enabled: false, ...focusOptions }); - const dismiss = useDismiss(context, { - enabled: true, - ...dismissOptions, - }); + const dismiss = useDismiss(context, dismissOptions); + const role = useRole(context, roleOptions); const interactions = useInteractions([click, dismiss, focus, hover, role]);