From 04edf544d9a09592f14f6b1ac20a70b6ec89792f Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Wed, 4 Dec 2024 16:51:55 -0800
Subject: [PATCH 01/10] implement RAC Popover component
---
.../src/components/Popover/Popover.css | 12 ++++++++++++
.../src/components/Popover/Popover.tsx | 12 ++++++++++++
.../react-components/src/components/Popover/index.ts | 2 ++
packages/react-components/src/components/index.ts | 1 +
4 files changed, 27 insertions(+)
create mode 100644 packages/react-components/src/components/Popover/Popover.css
create mode 100644 packages/react-components/src/components/Popover/Popover.tsx
create mode 100644 packages/react-components/src/components/Popover/index.ts
diff --git a/packages/react-components/src/components/Popover/Popover.css b/packages/react-components/src/components/Popover/Popover.css
new file mode 100644
index 00000000..f0d4b854
--- /dev/null
+++ b/packages/react-components/src/components/Popover/Popover.css
@@ -0,0 +1,12 @@
+.bcds-react-aria-Popover {
+ background-color: var(--surface-color-forms-default);
+ border: var(--layout-border-width-small) solid
+ var(--surface-color-border-default);
+ border-radius: var(--layout-border-radius-medium);
+ box-shadow: var(--surface-shadow-medium);
+ box-sizing: border-box;
+ padding: var(--layout-padding-small) var(--layout-padding-small);
+ width: var(
+ --trigger-width
+ ); /* Variable provided by Menu component: https://react-spectrum.adobe.com/react-aria/Menu.html#popover-1 */
+}
diff --git a/packages/react-components/src/components/Popover/Popover.tsx b/packages/react-components/src/components/Popover/Popover.tsx
new file mode 100644
index 00000000..7c153c7f
--- /dev/null
+++ b/packages/react-components/src/components/Popover/Popover.tsx
@@ -0,0 +1,12 @@
+import { Popover as ReactAriaPopover } from "react-aria-components";
+import type { PopoverProps } from "react-aria-components";
+
+import "./Popover.css";
+
+export default function Popover(props: PopoverProps) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/packages/react-components/src/components/Popover/index.ts b/packages/react-components/src/components/Popover/index.ts
new file mode 100644
index 00000000..a2546c9c
--- /dev/null
+++ b/packages/react-components/src/components/Popover/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./Popover";
+export type { PopoverProps } from "react-aria-components";
diff --git a/packages/react-components/src/components/index.ts b/packages/react-components/src/components/index.ts
index db0dcff9..ffe67252 100644
--- a/packages/react-components/src/components/index.ts
+++ b/packages/react-components/src/components/index.ts
@@ -15,6 +15,7 @@ export { default as Form } from "./Form";
export { default as Header } from "./Header";
export { default as InlineAlert } from "./InlineAlert";
export { default as Modal } from "./Modal";
+export { default as Popover } from "./Popover";
export { default as Radio } from "./Radio";
export { default as RadioGroup } from "./RadioGroup";
export { default as Select } from "./Select";
From 8f55e4095e1d3e01cf63d00325de85367671fca1 Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Wed, 4 Dec 2024 17:21:10 -0800
Subject: [PATCH 02/10] scaffolding popover docs and stories
---
.../react-components/src/stories/Popover.mdx | 43 +++++++++++++
.../src/stories/Popover.stories.tsx | 63 +++++++++++++++++++
2 files changed, 106 insertions(+)
create mode 100644 packages/react-components/src/stories/Popover.mdx
create mode 100644 packages/react-components/src/stories/Popover.stories.tsx
diff --git a/packages/react-components/src/stories/Popover.mdx b/packages/react-components/src/stories/Popover.mdx
new file mode 100644
index 00000000..390a6530
--- /dev/null
+++ b/packages/react-components/src/stories/Popover.mdx
@@ -0,0 +1,43 @@
+{/* Popover.mdx */}
+
+import {
+ Canvas,
+ Controls,
+ Meta,
+ Primary,
+ Source,
+ Story,
+ Subtitle,
+} from "@storybook/blocks";
+
+import * as PopoverStories from "./Popover.stories";
+
+
+
+# Popover
+
+
+ A popover is a content container that overlays the interface. It may be shown
+ and hidden programmatically, or using a trigger element like a button.
+
+
+
+
+## Usage and resources
+
+Learn more about working with the Popover component:
+
+- [Usage and best practice guidance]()
+- [View the popover component in Figma](https://www2.gov.bc.ca/gov/content?id=8E36BE1D10E04A17B0CD4D913FA7AC43#designers)
+
+This component is based on [React Aria Popover](https://react-spectrum.adobe.com/react-aria/Popover.html).
+
+## Controls
+
+
+
+
+## Configuration
diff --git a/packages/react-components/src/stories/Popover.stories.tsx b/packages/react-components/src/stories/Popover.stories.tsx
new file mode 100644
index 00000000..c6b34148
--- /dev/null
+++ b/packages/react-components/src/stories/Popover.stories.tsx
@@ -0,0 +1,63 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { Text } from "react-aria-components";
+import { Button, DialogTrigger, Popover } from "../components";
+import { PopoverProps } from "../components/Popover";
+
+const meta = {
+ title: "Components/Popover",
+ component: Popover,
+ parameters: { layout: "centered" },
+ argTypes: {
+ placement: {
+ control: { type: "radio" },
+ options: ["top", "bottom", "left", "right"],
+ description: "Position of the popover relative to its anchor element",
+ },
+ children: {
+ control: { type: "object" },
+ description: "Populate the content of the popover",
+ },
+ shouldFlip: {
+ control: { type: "boolean" },
+ description:
+ "Controls whether a popover can flip its orientation if there's not enough space for it to render fully",
+ },
+ offset: {
+ control: { type: "number" },
+ description:
+ "Adjust offset along the main axis from the popvoer's anchor element",
+ },
+ crossOffset: {
+ control: { type: "number" },
+ description:
+ "Adjust offset along the cross-axis, relative to the popover's anchor element",
+ },
+ containerPadding: {
+ control: { type: "number" },
+ description:
+ "Apply additional padding between the popover and its surrounding container",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const PopoverTemplate: Story = {
+ args: {
+ children: [
+
+ Pass some content or components as children to populate a popover's
+ content.
+ ,
+ ],
+ placement: "bottom",
+ shouldFlip: true,
+ },
+ render: ({ ...args }: PopoverProps) => (
+
+
+
+
+ ),
+};
From 3276fef39e3cf321ac9e51e4ac73b08ea7ed2a1b Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Tue, 10 Dec 2024 13:48:13 -0800
Subject: [PATCH 03/10] refit Select to use Popover component
---
.../src/components/Popover/Popover.css | 2 +-
.../src/components/Select/Select.css | 12 ------------
.../src/components/Select/Select.tsx | 4 ++--
3 files changed, 3 insertions(+), 15 deletions(-)
diff --git a/packages/react-components/src/components/Popover/Popover.css b/packages/react-components/src/components/Popover/Popover.css
index f0d4b854..d60d6849 100644
--- a/packages/react-components/src/components/Popover/Popover.css
+++ b/packages/react-components/src/components/Popover/Popover.css
@@ -5,7 +5,7 @@
border-radius: var(--layout-border-radius-medium);
box-shadow: var(--surface-shadow-medium);
box-sizing: border-box;
- padding: var(--layout-padding-small) var(--layout-padding-small);
+ padding: var(--layout-padding-xsmall) var(--layout-padding-xsmall);
width: var(
--trigger-width
); /* Variable provided by Menu component: https://react-spectrum.adobe.com/react-aria/Menu.html#popover-1 */
diff --git a/packages/react-components/src/components/Select/Select.css b/packages/react-components/src/components/Select/Select.css
index 582ab4dd..7da3f5f3 100644
--- a/packages/react-components/src/components/Select/Select.css
+++ b/packages/react-components/src/components/Select/Select.css
@@ -90,18 +90,6 @@
}
/* Dropdown menu panel */
-.bcds-react-aria-Select--Popover {
- background-color: var(--surface-color-forms-default);
- border: 1px solid var(--surface-color-border-default);
- border-radius: var(--layout-border-radius-medium);
- box-shadow: var(--surface-shadow-medium);
- box-sizing: border-box;
- overflow-y: auto;
- padding: var(--layout-padding-hair) var(--layout-padding-xsmall);
- width: var(
- --trigger-width
- ); /* Variable provided by Select component https://react-spectrum.adobe.com/react-aria/Select.html#popover-1 */
-}
.bcds-react-aria-Select--ListBox[data-focused] {
outline: none;
}
diff --git a/packages/react-components/src/components/Select/Select.tsx b/packages/react-components/src/components/Select/Select.tsx
index 398569bd..977478c7 100644
--- a/packages/react-components/src/components/Select/Select.tsx
+++ b/packages/react-components/src/components/Select/Select.tsx
@@ -9,7 +9,6 @@ import {
ListBoxItem,
ListBoxItemProps as ReactAriaListBoxItemProps,
ListBoxSection,
- Popover,
Select as ReactAriaSelect,
SelectProps as ReactAriaSelectProps,
SelectValue,
@@ -17,6 +16,7 @@ import {
ValidationResult,
} from "react-aria-components";
+import Popover from "../Popover";
import SvgExclamationIcon from "../Icons/SvgExclamationIcon";
import SvgChevronUpIcon from "../Icons/SvgChevronUpIcon";
import SvgChevronDownIcon from "../Icons/SvgChevronDownIcon";
@@ -108,7 +108,7 @@ export default function Select({
{errorMessage}
-
+
Date: Thu, 12 Dec 2024 14:18:31 -0800
Subject: [PATCH 04/10] port WIP from menu branch
---
.../SvgChevronRightIcon.tsx | 18 +++++++
.../Icons/SvgChevronRightIcon/index.ts | 1 +
.../src/components/Menu/Menu.css | 28 ++++++++++
.../src/components/Menu/Menu.tsx | 26 ++++++++++
.../src/components/Menu/index.ts | 8 +++
.../src/components/MenuItem/MenuItem.css | 52 +++++++++++++++++++
.../src/components/MenuItem/MenuItem.tsx | 31 +++++++++++
.../src/components/MenuItem/index.ts | 2 +
.../react-components/src/components/index.ts | 9 ++++
9 files changed, 175 insertions(+)
create mode 100644 packages/react-components/src/components/Icons/SvgChevronRightIcon/SvgChevronRightIcon.tsx
create mode 100644 packages/react-components/src/components/Icons/SvgChevronRightIcon/index.ts
create mode 100644 packages/react-components/src/components/Menu/Menu.css
create mode 100644 packages/react-components/src/components/Menu/Menu.tsx
create mode 100644 packages/react-components/src/components/Menu/index.ts
create mode 100644 packages/react-components/src/components/MenuItem/MenuItem.css
create mode 100644 packages/react-components/src/components/MenuItem/MenuItem.tsx
create mode 100644 packages/react-components/src/components/MenuItem/index.ts
diff --git a/packages/react-components/src/components/Icons/SvgChevronRightIcon/SvgChevronRightIcon.tsx b/packages/react-components/src/components/Icons/SvgChevronRightIcon/SvgChevronRightIcon.tsx
new file mode 100644
index 00000000..cf0f402c
--- /dev/null
+++ b/packages/react-components/src/components/Icons/SvgChevronRightIcon/SvgChevronRightIcon.tsx
@@ -0,0 +1,18 @@
+/* The component implements the Chevron Right icon from Font Awesome: https://fontawesome.com/icons/chevron-right */
+export default function SvgChevronRightIcon({ id = "chevron-right-icon" }) {
+ return (
+
+ );
+}
diff --git a/packages/react-components/src/components/Icons/SvgChevronRightIcon/index.ts b/packages/react-components/src/components/Icons/SvgChevronRightIcon/index.ts
new file mode 100644
index 00000000..9fd6d5a2
--- /dev/null
+++ b/packages/react-components/src/components/Icons/SvgChevronRightIcon/index.ts
@@ -0,0 +1 @@
+export { default } from "./SvgChevronRightIcon";
diff --git a/packages/react-components/src/components/Menu/Menu.css b/packages/react-components/src/components/Menu/Menu.css
new file mode 100644
index 00000000..84bdf52e
--- /dev/null
+++ b/packages/react-components/src/components/Menu/Menu.css
@@ -0,0 +1,28 @@
+.bcds-react-aria-Menu {
+ display: flex;
+ flex-direction: column;
+ gap: var(--layout-margin-small);
+}
+
+.bcds-react-aria-Popover {
+ background-color: var(--surface-color-forms-default);
+ border: 1px solid var(--surface-color-border-default);
+ border-radius: var(--layout-border-radius-medium);
+ box-shadow: var(--surface-shadow-medium);
+ box-sizing: border-box;
+ padding: var(--layout-padding-small) var(--layout-padding-small);
+ width: var(
+ --trigger-width
+ ); /* Variable provided by Menu component: https://react-spectrum.adobe.com/react-aria/Menu.html#popover-1 */
+}
+
+/* Sections */
+.bcds-react-aria-Menu .react-aria-Header {
+ font: var(--typography-bold-small-body);
+}
+
+.bcds-react-aria-Menu .react-aria-MenuSection {
+ display: flex;
+ flex-direction: column;
+ gap: var(--layout-margin-small);
+}
diff --git a/packages/react-components/src/components/Menu/Menu.tsx b/packages/react-components/src/components/Menu/Menu.tsx
new file mode 100644
index 00000000..c464fd6e
--- /dev/null
+++ b/packages/react-components/src/components/Menu/Menu.tsx
@@ -0,0 +1,26 @@
+import {
+ MenuTrigger,
+ Menu as ReactAriaMenu,
+ MenuProps as ReactAriaMenuProps,
+ MenuSection,
+ Header as MenuSectionHeader,
+ SubmenuTrigger,
+} from "react-aria-components";
+
+import Popover from "../Popover";
+import "./Menu.css";
+
+export default function Menu({
+ children,
+ ...props
+}: ReactAriaMenuProps) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export { MenuTrigger, SubmenuTrigger, MenuSection, MenuSectionHeader };
diff --git a/packages/react-components/src/components/Menu/index.ts b/packages/react-components/src/components/Menu/index.ts
new file mode 100644
index 00000000..1564e0de
--- /dev/null
+++ b/packages/react-components/src/components/Menu/index.ts
@@ -0,0 +1,8 @@
+export {
+ default,
+ MenuTrigger,
+ SubmenuTrigger,
+ MenuSection,
+ MenuSectionHeader,
+} from "./Menu";
+export type { MenuProps } from "react-aria-components";
diff --git a/packages/react-components/src/components/MenuItem/MenuItem.css b/packages/react-components/src/components/MenuItem/MenuItem.css
new file mode 100644
index 00000000..ed1b6bb1
--- /dev/null
+++ b/packages/react-components/src/components/MenuItem/MenuItem.css
@@ -0,0 +1,52 @@
+.bcds-react-aria-Menu-Item {
+ color: var(--typography-color-secondary);
+ display: inline-flex;
+ gap: var(--layout-margin-xsmall);
+ cursor: pointer;
+}
+
+/* Link styling */
+a.bcds-react-aria-Menu-Item {
+ color: var(--typography-color-link);
+ text-decoration: underline;
+ text-underline-offset: var(--layout-padding-hair);
+}
+
+a.bcds-react-aria-Menu-Item[data-hovered] {
+ color: var(--surface-color-border-active);
+}
+
+/* Sizing */
+.bcds-react-aria-Menu-Item.small {
+ font: var(--typography-regular-small-body);
+}
+
+.bcds-react-aria-Menu-Item.medium {
+ font: var(--typography-regular-body);
+}
+
+/* Icon displayed when an item has a submenu */
+.bcds-react-aria-Menu-Item > svg {
+ width: var(--icons-size-xsmall);
+ height: var(--icons-size-xsmall);
+ padding: var(--layout-padding-hair);
+ align-self: center;
+}
+
+/* States */
+
+.bcds-react-aria-Menu-Item[data-focus-visible] {
+ outline: solid var(--layout-border-width-medium)
+ var(--surface-color-border-active);
+ outline-offset: var(--layout-margin-xsmall);
+ border-radius: var(--layout-border-radius-small);
+}
+
+.bcds-react-aria-Menu-Item[data-hovered] {
+ color: var(--surface-color-border-active);
+}
+
+.bcds-react-aria-Menu-Item[data-disabled] {
+ color: var(--typography-color-disabled);
+ cursor: not-allowed;
+}
diff --git a/packages/react-components/src/components/MenuItem/MenuItem.tsx b/packages/react-components/src/components/MenuItem/MenuItem.tsx
new file mode 100644
index 00000000..3595044f
--- /dev/null
+++ b/packages/react-components/src/components/MenuItem/MenuItem.tsx
@@ -0,0 +1,31 @@
+import {
+ MenuItem as ReactAriaMenuItem,
+ MenuItemProps as ReactAriaMenuItemProps,
+} from "react-aria-components";
+
+import "./MenuItem.css";
+import SvgChevronRightIcon from "../Icons/SvgChevronRightIcon";
+
+export interface MenuItemProps extends ReactAriaMenuItemProps {
+ size?: "small" | "medium";
+}
+
+export default function MenuItem({ size = "small", ...props }: MenuItemProps) {
+ const textValue =
+ props.textValue ||
+ (typeof props.children === "string" ? props.children : undefined);
+ return (
+
+ {({ hasSubmenu }) => (
+ <>
+ {props.children}
+ {hasSubmenu && }
+ >
+ )}
+
+ );
+}
diff --git a/packages/react-components/src/components/MenuItem/index.ts b/packages/react-components/src/components/MenuItem/index.ts
new file mode 100644
index 00000000..434b3138
--- /dev/null
+++ b/packages/react-components/src/components/MenuItem/index.ts
@@ -0,0 +1,2 @@
+export { default } from "./MenuItem";
+export type { MenuItemProps } from "./MenuItem";
diff --git a/packages/react-components/src/components/index.ts b/packages/react-components/src/components/index.ts
index ffe67252..8dcdf6b2 100644
--- a/packages/react-components/src/components/index.ts
+++ b/packages/react-components/src/components/index.ts
@@ -14,6 +14,14 @@ export { default as Footer, FooterLinks } from "./Footer";
export { default as Form } from "./Form";
export { default as Header } from "./Header";
export { default as InlineAlert } from "./InlineAlert";
+export {
+ default as Menu,
+ MenuTrigger,
+ SubmenuTrigger,
+ MenuSection,
+ MenuSectionHeader,
+} from "./Menu";
+export { default as MenuItem } from "./MenuItem";
export { default as Modal } from "./Modal";
export { default as Popover } from "./Popover";
export { default as Radio } from "./Radio";
@@ -29,6 +37,7 @@ export { default as SvgExclamationIcon } from "./Icons/SvgExclamationIcon";
export { default as SvgExclamationCircleIcon } from "./Icons/SvgExclamationCircleIcon";
export { default as SvgChevronUpIcon } from "./Icons/SvgChevronUpIcon";
export { default as SvgChevronDownIcon } from "./Icons/SvgChevronDownIcon";
+export { default as SvgChevronRightIcon } from "./Icons/SvgChevronRightIcon";
export { default as SvgCloseIcon } from "./Icons/SvgCloseIcon";
export { default as SvgInfoIcon } from "./Icons/SvgInfoIcon";
export { default as SvgTooltipArrowUp } from "./Icons/SvgTooltipArrowUp";
From 2169c5fc4b1eed8ebfcaae1565fc904b8de1aade Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:01:44 -0800
Subject: [PATCH 05/10] remove Popover from Storybook
---
.../react-components/src/stories/Popover.mdx | 43 -------------
.../src/stories/Popover.stories.tsx | 63 -------------------
2 files changed, 106 deletions(-)
delete mode 100644 packages/react-components/src/stories/Popover.mdx
delete mode 100644 packages/react-components/src/stories/Popover.stories.tsx
diff --git a/packages/react-components/src/stories/Popover.mdx b/packages/react-components/src/stories/Popover.mdx
deleted file mode 100644
index 390a6530..00000000
--- a/packages/react-components/src/stories/Popover.mdx
+++ /dev/null
@@ -1,43 +0,0 @@
-{/* Popover.mdx */}
-
-import {
- Canvas,
- Controls,
- Meta,
- Primary,
- Source,
- Story,
- Subtitle,
-} from "@storybook/blocks";
-
-import * as PopoverStories from "./Popover.stories";
-
-
-
-# Popover
-
-
- A popover is a content container that overlays the interface. It may be shown
- and hidden programmatically, or using a trigger element like a button.
-
-
-
-
-## Usage and resources
-
-Learn more about working with the Popover component:
-
-- [Usage and best practice guidance]()
-- [View the popover component in Figma](https://www2.gov.bc.ca/gov/content?id=8E36BE1D10E04A17B0CD4D913FA7AC43#designers)
-
-This component is based on [React Aria Popover](https://react-spectrum.adobe.com/react-aria/Popover.html).
-
-## Controls
-
-
-
-
-## Configuration
diff --git a/packages/react-components/src/stories/Popover.stories.tsx b/packages/react-components/src/stories/Popover.stories.tsx
deleted file mode 100644
index c6b34148..00000000
--- a/packages/react-components/src/stories/Popover.stories.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react";
-import { Text } from "react-aria-components";
-import { Button, DialogTrigger, Popover } from "../components";
-import { PopoverProps } from "../components/Popover";
-
-const meta = {
- title: "Components/Popover",
- component: Popover,
- parameters: { layout: "centered" },
- argTypes: {
- placement: {
- control: { type: "radio" },
- options: ["top", "bottom", "left", "right"],
- description: "Position of the popover relative to its anchor element",
- },
- children: {
- control: { type: "object" },
- description: "Populate the content of the popover",
- },
- shouldFlip: {
- control: { type: "boolean" },
- description:
- "Controls whether a popover can flip its orientation if there's not enough space for it to render fully",
- },
- offset: {
- control: { type: "number" },
- description:
- "Adjust offset along the main axis from the popvoer's anchor element",
- },
- crossOffset: {
- control: { type: "number" },
- description:
- "Adjust offset along the cross-axis, relative to the popover's anchor element",
- },
- containerPadding: {
- control: { type: "number" },
- description:
- "Apply additional padding between the popover and its surrounding container",
- },
- },
-} satisfies Meta;
-
-export default meta;
-type Story = StoryObj;
-
-export const PopoverTemplate: Story = {
- args: {
- children: [
-
- Pass some content or components as children to populate a popover's
- content.
- ,
- ],
- placement: "bottom",
- shouldFlip: true,
- },
- render: ({ ...args }: PopoverProps) => (
-
-
-
-
- ),
-};
From f6749eae278b3138e5b7068ab50ab37c7a9373a2 Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Thu, 16 Jan 2025 11:36:42 -0800
Subject: [PATCH 06/10] adding docs and stories for Menu
---
.../react-components/src/stories/Menu.mdx | 92 +++++++++++++++++++
.../src/stories/Menu.stories.tsx | 86 +++++++++++++++++
2 files changed, 178 insertions(+)
create mode 100644 packages/react-components/src/stories/Menu.mdx
create mode 100644 packages/react-components/src/stories/Menu.stories.tsx
diff --git a/packages/react-components/src/stories/Menu.mdx b/packages/react-components/src/stories/Menu.mdx
new file mode 100644
index 00000000..41d5bc76
--- /dev/null
+++ b/packages/react-components/src/stories/Menu.mdx
@@ -0,0 +1,92 @@
+{/* Menu.mdx */}
+
+import {
+ Canvas,
+ Controls,
+ Meta,
+ Primary,
+ Source,
+ Story,
+ Subtitle,
+} from "@storybook/blocks";
+
+import * as MenuStories from "./Menu.stories";
+
+
+
+# Menu
+
+
+ The menu component displays a collapsible list of items in a dropdown menu.
+
+
+
+
+## Usage and resources
+
+Learn more about working with the select component:
+
+- [Usage and best practice guidance](https://www2.gov.bc.ca/gov/content?id=94129932F3384565A638034B69E0C943)
+- [View the menu component in Figma](https://www2.gov.bc.ca/gov/content?id=8E36BE1D10E04A17B0CD4D913FA7AC43#designers)
+
+This component is based on [React Aria Menu](https://react-spectrum.adobe.com/react-aria/Menu.html).
+
+### Anatomy
+
+A menu is composed similarly to a [dialog](/docs/components-dialogs--docs). You need:
+
+- A `` element
+- A trigger element, positioned as the first child inside the ``
+- The `
+
+`} language="typescript"/>
diff --git a/packages/react-components/src/stories/Menu.stories.tsx b/packages/react-components/src/stories/Menu.stories.tsx
new file mode 100644
index 00000000..934e051b
--- /dev/null
+++ b/packages/react-components/src/stories/Menu.stories.tsx
@@ -0,0 +1,86 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import {
+ Button,
+ Menu,
+ MenuItem,
+ MenuTrigger,
+ SubmenuTrigger,
+ SvgChevronDownIcon,
+} from "../components";
+import { MenuProps } from "../components/Menu";
+
+const meta = {
+ title: "Components/Menu/Menu",
+ component: Menu,
+ parameters: { layout: "centered" },
+ argTypes: {
+ children: {
+ control: { type: "object" },
+ description: "Expects an array of `MenuItem` components",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+const MenuItems = [
+ { id: 1, name: "Item 1" },
+ { id: 2, name: "Item 2" },
+ { id: 3, name: "Item 3" },
+ { id: 4, name: "Item 4" },
+];
+
+export const MenuTemplate: Story = {
+ args: {
+ children: [],
+ },
+ render: ({ ...args }: MenuProps
`} language="typescript"/>
+
+#### Disabled menu items
+
+Pass the `isDisabled` prop to a `MenuItem` to disable it:
+
+
+
+### Menu sections
+
+You can use the `MenuSection` and `MenuSectionHeader` subcomponents to create sections within a menu:
+
+
+
+The example above is implemented like this:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`}
+ language="typescript"
+/>
+
+### Submenus
+
+Use the `SubmenuTrigger` component to [create a submenu](https://react-spectrum.adobe.com/react-aria/Menu.html#submenus) within a `Menu`.
+
+
+
+The first child inside a `SubmenuTrigger` should be the `MenuItem` that will activate the submenu:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`}
+ language="typescript"
+/>
diff --git a/packages/react-components/src/stories/Menu.stories.tsx b/packages/react-components/src/stories/Menu.stories.tsx
index 934e051b..0bab63a5 100644
--- a/packages/react-components/src/stories/Menu.stories.tsx
+++ b/packages/react-components/src/stories/Menu.stories.tsx
@@ -4,11 +4,12 @@ import {
Button,
Menu,
MenuItem,
+ MenuSection,
MenuTrigger,
SubmenuTrigger,
SvgChevronDownIcon,
} from "../components";
-import { MenuProps } from "../components/Menu";
+import { MenuProps, MenuSectionHeader } from "../components/Menu";
const meta = {
title: "Components/Menu/Menu",
@@ -55,8 +56,8 @@ export const MenuWithSubmenu: Story = {
Submenu
- Submenu item 1
- Submenu item 2
+ Submenu item 1
+ Submenu item 2
,
],
@@ -84,3 +85,26 @@ export const MenuWithDynamicItems: Story = {
),
};
+
+export const MenuWithSections: Story = {
+ args: {
+ children: [
+
+ Section 1
+ Item 1
+ Item 2
+ ,
+
+ Section 2
+ Item 3
+ Item 4
+ ,
+ ],
+ },
+ render: ({ ...args }: MenuProps) => (
+
+
+
+
+ ),
+};
diff --git a/packages/react-components/src/stories/MenuItem.stories.tsx b/packages/react-components/src/stories/MenuItem.stories.tsx
index 5938684b..07a1b0cc 100644
--- a/packages/react-components/src/stories/MenuItem.stories.tsx
+++ b/packages/react-components/src/stories/MenuItem.stories.tsx
@@ -82,3 +82,25 @@ export const MenuItemTemplate: Story = {
),
};
+
+export const DisabledMenuItem: Story = {
+ args: {
+ size: "medium",
+ children: ["Item 2 is disabled"],
+ id: "2",
+ isDisabled: true,
+ },
+ render: ({ ...args }: MenuItemProps) => (
+
+
+
+
+ Item 1
+
+
+
+
+ ),
+};
From f5cc136ea1c08c6c7490a61580536d630f754c6c Mon Sep 17 00:00:00 2001
From: Marcus Kernohan <135075821+mkernohanbc@users.noreply.github.com>
Date: Mon, 20 Jan 2025 14:01:31 -0800
Subject: [PATCH 10/10] add stories for MenuTrigger and SubMenuTrigger
---
.../react-components/src/stories/Menu.mdx | 18 ++++++
.../src/stories/Menu.stories.tsx | 16 ++++++
.../src/stories/MenuTrigger.stories.tsx | 56 ++++++++++++++++++
.../src/stories/SubmenuTrigger.stories.tsx | 57 +++++++++++++++++++
4 files changed, 147 insertions(+)
create mode 100644 packages/react-components/src/stories/MenuTrigger.stories.tsx
create mode 100644 packages/react-components/src/stories/SubmenuTrigger.stories.tsx
diff --git a/packages/react-components/src/stories/Menu.mdx b/packages/react-components/src/stories/Menu.mdx
index da7f0940..8d6ed5b7 100644
--- a/packages/react-components/src/stories/Menu.mdx
+++ b/packages/react-components/src/stories/Menu.mdx
@@ -12,6 +12,8 @@ import {
import * as MenuStories from "./Menu.stories";
import * as MenuItemStories from "./MenuItem.stories";
+import * as MenuTriggerStories from "./MenuTrigger.stories";
+import * as SubmenuTriggerStories from "./SubmenuTrigger.stories";
@@ -103,6 +105,8 @@ Pass the `isDisabled` prop to a `MenuItem` to disable it:
+You can also use `disabledKeys` to disable some number of keys, using their unique identifiers.
+
### Menu sections
You can use the `MenuSection` and `MenuSectionHeader` subcomponents to create sections within a menu:
@@ -158,3 +162,17 @@ The first child inside a `SubmenuTrigger` should be the `MenuItem` that will act
`}
language="typescript"
/>
+
+### Menu triggers
+
+`MenuTrigger` and `SubmenuTrigger` have their own props you can use to customise their behaviour:
+
+#### MenuTrigger
+
+
+
+
+#### SubmenuTrigger
+
+
+
diff --git a/packages/react-components/src/stories/Menu.stories.tsx b/packages/react-components/src/stories/Menu.stories.tsx
index 0bab63a5..f1338839 100644
--- a/packages/react-components/src/stories/Menu.stories.tsx
+++ b/packages/react-components/src/stories/Menu.stories.tsx
@@ -20,6 +20,22 @@ const meta = {
control: { type: "object" },
description: "Expects an array of `MenuItem` components",
},
+ items: {
+ control: { type: "object" },
+ description: "Array of objects to be rendered as Menu items",
+ },
+ disabledKeys: {
+ control: { type: "object" },
+ description: "Selectively disables individual menu items",
+ },
+ selectedKeys: {
+ control: { type: "object" },
+ description: "The currently selected keys in the collection (controlled)",
+ },
+ defaultSelectedKeys: {
+ control: { type: "object" },
+ description: "The initial selected keys in the collection (uncontrolled)",
+ },
},
} satisfies Meta;
diff --git a/packages/react-components/src/stories/MenuTrigger.stories.tsx b/packages/react-components/src/stories/MenuTrigger.stories.tsx
new file mode 100644
index 00000000..1ccc0e54
--- /dev/null
+++ b/packages/react-components/src/stories/MenuTrigger.stories.tsx
@@ -0,0 +1,56 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import {
+ Button,
+ Menu,
+ MenuItem,
+ MenuTrigger,
+ SvgChevronDownIcon,
+} from "../components";
+import { MenuTriggerProps } from "react-aria-components";
+
+const meta = {
+ title: "Components/Menu/MenuTrigger",
+ component: MenuTrigger,
+ parameters: { layout: "centered" },
+ argTypes: {
+ children: {
+ control: { type: "object" },
+ description:
+ "Expects a trigger element (like a `Button`) and a `Menu` component",
+ },
+ trigger: {
+ control: { type: "text" },
+ description: "How the menu is triggered",
+ },
+ isOpen: {
+ control: { type: "boolean" },
+ description: "Whether the overlay is open by default (controlled)",
+ },
+ defaultOpen: {
+ control: { type: "boolean" },
+ description: "Whether the overlay is open by default (uncontrolled)",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const MenuTriggerTemplate: Story = {
+ args: {
+ children: [
+ ,
+
+ Item 1
+ Item 2
+ Item 3
+ ,
+ ],
+ trigger: "press",
+ defaultOpen: false,
+ },
+ render: ({ ...args }: MenuTriggerProps) => ,
+};
diff --git a/packages/react-components/src/stories/SubmenuTrigger.stories.tsx b/packages/react-components/src/stories/SubmenuTrigger.stories.tsx
new file mode 100644
index 00000000..fd0026c4
--- /dev/null
+++ b/packages/react-components/src/stories/SubmenuTrigger.stories.tsx
@@ -0,0 +1,57 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import {
+ Button,
+ Menu,
+ MenuItem,
+ MenuTrigger,
+ SubmenuTrigger,
+ SvgChevronDownIcon,
+} from "../components";
+import { SubmenuTriggerProps } from "react-aria-components";
+
+const meta = {
+ title: "Components/Menu/SubmenuTrigger",
+ component: SubmenuTrigger,
+ parameters: { layout: "centered" },
+ argTypes: {
+ children: {
+ control: { type: "object" },
+ description:
+ "Expects a trigger element (`MenuItem`) and a submenu (`Menu`)",
+ },
+ delay: {
+ control: { type: "number" },
+ description:
+ "Delay time in miliseconds before submenu appears when hovered",
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const SubmenuTriggerTemplate: Story = {
+ args: {
+ children: [
+ ,
+
+ Submenu item 1
+ Submenu item 2
+ Submenu item 3
+ ,
+ ],
+ delay: 200,
+ },
+ render: ({ ...args }: SubmenuTriggerProps) => (
+
+
+
+ Item 1
+
+
+
+ ),
+};