From e1984d96cb451e4c82f0b7e83f2cd93fac6e7c0a Mon Sep 17 00:00:00 2001 From: Miguel Andrade Date: Wed, 10 Apr 2024 00:23:49 +0100 Subject: [PATCH 01/17] implement Menu (WIP) --- docs-app/package.json | 2 +- docs-app/public/docs/5-floaty-bits/menu.md | 94 +++++++++++++++++++ docs-app/public/docs/5-floaty-bits/popover.md | 2 +- ember-primitives/package.json | 3 +- ember-primitives/src/components/menu.gts | 84 +++++++++++++++++ ember-primitives/src/components/popover.gts | 6 +- ember-primitives/src/index.ts | 1 + pnpm-lock.yaml | 16 ++-- 8 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 docs-app/public/docs/5-floaty-bits/menu.md create mode 100644 ember-primitives/src/components/menu.gts diff --git a/docs-app/package.json b/docs-app/package.json index e9afea6a..efe3c586 100644 --- a/docs-app/package.json +++ b/docs-app/package.json @@ -36,7 +36,7 @@ "ember-primitives": "workspace:*", "ember-repl": "3.0.0-beta.8", "ember-route-template": "^1.0.3", - "ember-velcro": "^2.1.3", + "ember-velcro": "^2.2.0", "hash": "^0.2.1", "highlight.js": "^11.8.0", "highlightjs-glimmer": "^2.2.1", diff --git a/docs-app/public/docs/5-floaty-bits/menu.md b/docs-app/public/docs/5-floaty-bits/menu.md new file mode 100644 index 00000000..97ec2eef --- /dev/null +++ b/docs-app/public/docs/5-floaty-bits/menu.md @@ -0,0 +1,94 @@ +# Menu + +Menus are built with Popovers, with added features for keyboard navigation and accessibility. + +The `` component uses portals in a way that totally solves layering issues. No more worrying about tooltips on varying layers of your UI sometimes appearing behind other floaty bits. See the `` and `` pages for more information. + + + + +## API Reference + +```gjs live no-shadow +import { ComponentSignature } from 'docs-app/docs-support'; + + +``` + +## Accessibility + +The `Content` of a popover is focusable, so that keyboard (and screenreader) users can interact with the Popover content. Generally this is great for modals, but also extends to things like tooltips, so that folks can copy the content out. + +Since a `Popover` isn't an explicit design pattern provided by W3, but instead, `Popover` is a low level primitive that could be used to build the W3 examples of +- [Modal Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/) +- [Date Picker Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/) +- [Date Picker Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-datepicker/) +- [Select-Only Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/) +- and more + + + diff --git a/docs-app/public/docs/5-floaty-bits/popover.md b/docs-app/public/docs/5-floaty-bits/popover.md index bd0d8306..05c90732 100644 --- a/docs-app/public/docs/5-floaty-bits/popover.md +++ b/docs-app/public/docs/5-floaty-bits/popover.md @@ -4,7 +4,7 @@ Popovers are built with [ember-velcro][gh-e-velcro], which is an ergonomic wrapp diff --git a/ember-primitives/package.json b/ember-primitives/package.json index 7000c570..eaafa914 100644 --- a/ember-primitives/package.json +++ b/ember-primitives/package.json @@ -41,7 +41,7 @@ "@floating-ui/dom": "^1.5.1", "decorator-transforms": "^1.1.0", "ember-element-helper": "^0.8.4", - "ember-velcro": "^2.1.3", + "ember-velcro": "^2.2.0", "reactiveweb": "^1.2.0", "tabster": "^7.0.1", "tracked-built-ins": "^3.2.0", @@ -110,6 +110,7 @@ "./components/external-link.js": "./dist/_app_/components/external-link.js", "./components/form.js": "./dist/_app_/components/form.js", "./components/link.js": "./dist/_app_/components/link.js", + "./components/menu.js": "./dist/_app_/components/menu.js", "./components/popover.js": "./dist/_app_/components/popover.js", "./components/portal-targets.js": "./dist/_app_/components/portal-targets.js", "./components/portal.js": "./dist/_app_/components/portal.js", diff --git a/ember-primitives/src/components/menu.gts b/ember-primitives/src/components/menu.gts new file mode 100644 index 00000000..c04752b5 --- /dev/null +++ b/ember-primitives/src/components/menu.gts @@ -0,0 +1,84 @@ +import { hash } from '@ember/helper'; + +import { modifier } from 'ember-modifier'; +import { cell } from 'ember-resources'; + +import { Popover, type Signature as PopoverSignature } from './popover.gts'; + +import type { TOC } from '@ember/component/template-only'; +import type { ModifierLike, WithBoundArgs } from '@glint/template'; + +export interface Signature { + Args: PopoverSignature['Args']; + Blocks: { + default: [ + { + trigger: ModifierLike; + arrow: PopoverSignature['Blocks']['default'][0]['arrow']; + Content: WithBoundArgs; + }, + ]; + }; +} + +const Content: TOC<{ + Element: HTMLDivElement; + Args: { + isOpen: boolean; + PopoverContent: PopoverSignature['Blocks']['default'][0]['Content']; + }; + Blocks: { default: [] }; +}> = ; + +interface TriggerSignature { + Element: HTMLElement; + Args: { + Named: { + setHook: (element: HTMLElement | SVGElement) => void; + isOpen: ReturnType>; + }; + }; +} + +const trigger = modifier((element, _: [], named) => { + named.setHook?.(element); + + element.addEventListener('click', named.isOpen.toggle); + + return () => { + element.removeEventListener('click', named.isOpen.toggle); + }; +}); + +const IsOpen: () => ReturnType> = () => cell(false); + +export const Menu: TOC = ; + +export default Menu; diff --git a/ember-primitives/src/components/popover.gts b/ember-primitives/src/components/popover.gts index 4cf4b1fb..7b8a7a42 100644 --- a/ember-primitives/src/components/popover.gts +++ b/ember-primitives/src/components/popover.gts @@ -10,7 +10,7 @@ import { Portal } from './portal.gts'; import { TARGETS } from './portal-targets.gts'; import type { TOC } from '@ember/component/template-only'; -import type { Middleware, MiddlewareData } from '@floating-ui/dom'; +import type { ElementContext, Middleware, MiddlewareData } from '@floating-ui/dom'; import type { ModifierLike, WithBoundArgs } from '@glint/template'; import type { Signature as HookSignature } from 'ember-velcro/modifiers/velcro'; @@ -76,6 +76,7 @@ export interface Signature { default: [ { hook: ModifierLike; + setHook: (element: HTMLElement | SVGElement) => void; Content: WithBoundArgs; data: MiddlewareData; arrow: WithBoundArgs, 'arrowElement' | 'data'>; @@ -206,7 +207,7 @@ function maybeAddArrow(middleware: Middleware[] | undefined, element: Element | function flipOptions(options: HookSignature['Args']['Named']['flipOptions']) { return { - elementContext: 'reference', + elementContext: 'reference' as ElementContext, ...options, }; } @@ -225,6 +226,7 @@ export const Popover: TOC =