Skip to content

Commit

Permalink
WB-1588: Allow custom ActionItem elements (#2135)
Browse files Browse the repository at this point in the history
## Summary:

1. Allow custom `ActionItem` components by using `Cell` internally.

- Removed `ClickableBehavior` from `ActionItem` and replaced it with
  `CompactCell` (which internally uses `Clickable`).
- Removed `skipClientNav` from `ActionItem` as it is no longer used/needed.
- Added stories and better documentation for `ActionMenu` and `ActionItem`.

2. Modified Cell to support two new props required for the `ActionItem` changes:
- `role` is used to set the `role` attribute on the cell's root element.
- `rootStyle` is used to override the `style` attribute on the cell's root
  element.


Issue: WB-1588

## Test plan:

### ActionMenu:

1. Navigate to http://localhost:6061/?path=/story/dropdown-actionmenu--custom-action-items
2. Verify that the custom action items are rendered as expected.
3. Verify that the custom action items are clickable and that the click handler
   is called as expected.


https://github.com/Khan/wonder-blocks/assets/843075/b4ba22c8-5472-466c-a72a-045c27e22942

### ActionItem docs:

1. Navigate to http://localhost:6061/?path=/docs/dropdown-actionitem--docs
2. Verify that the documentation for `ActionItem` is correct and up to date.

<img width="836" alt="Screenshot 2023-12-05 at 12 52 01 PM" src="https://github.com/Khan/wonder-blocks/assets/843075/042715cb-5b37-486e-abe1-1ba66e4fdfe8">

Author: jandrade

Reviewers: jeresig, jandrade

Required Reviewers:

Approved By: jeresig

Checks: ✅ Chromatic - Get results on regular PRs (ubuntu-latest, 16.x), ✅ codecov/project, ✅ Check build sizes (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 2/2), ✅ Lint (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 1/2), ✅ Chromatic - Build on regular PRs / chromatic (ubuntu-latest, 16.x), ✅ Publish npm snapshot (ubuntu-latest, 16.x), ⏭  Chromatic - Skip on Release PR (changesets), ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 16.x), ✅ gerald, ⏭  dependabot

Pull Request URL: #2135
  • Loading branch information
jandrade authored Dec 5, 2023
1 parent 9092363 commit 860d9ef
Show file tree
Hide file tree
Showing 15 changed files with 640 additions and 157 deletions.
9 changes: 9 additions & 0 deletions .changeset/empty-kids-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@khanacademy/wonder-blocks-dropdown": major
---

Allow custom `ActionItem` components by using Cell internally.

- Removed `ClickableBehavior` from `ActionItem` and replaced it with
`CompactCell` (which internally uses `Clickable`).
- Removed `skipClientNav` from `ActionItem` as it is no longer used/needed.
9 changes: 9 additions & 0 deletions .changeset/new-lamps-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@khanacademy/wonder-blocks-cell": minor
---

Add `role` and `rootStyle` props.

- `role` is used to set the `role` attribute on the cell's root element.
- `rootStyle` is used to override the `style` attribute on the cell's root
element.
36 changes: 36 additions & 0 deletions __docs__/wonder-blocks-cell/compact-cell.argtypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ export default {
},
},
},
rootStyle: {
description:
`Optional custom styles applied to the top node.\n\n` +
`**NOTE:** This is the top node of the cell, not the cell ` +
`container. If possible, try to use this prop carefully and use ` +
`\`style\` instead.`,
control: {type: "object"},
table: {
category: "Styling",
type: {
summary: "StyleType",
},
},
},
style: {
description: "Optional custom styles.",
control: {type: "object"},
Expand Down Expand Up @@ -195,4 +209,26 @@ export default {
},
},
},
role: {
description:
"The role of the Cell component, can be a role of type `ClickableRole`",
control: {type: "select"},
options: [
"button",
"checkbox",
"link",
"listbox",
"menu",
"menuitem",
"radio",
"tab",
],
table: {
category: "Accessibility",
type: {
summary: "ClickableRole",
detail: `"button" | "link" | "checkbox" | "radio" | "listbox" | "option" | "menuitem" | "menu" | "tab"`,
},
},
},
} satisfies Record<string, InputType>;
109 changes: 109 additions & 0 deletions __docs__/wonder-blocks-dropdown/action-item.argtypes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as React from "react";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import Pill from "@khanacademy/wonder-blocks-pill";
import {IconMappings} from "../wonder-blocks-icon/phosphor-icon.argtypes";

const AccessoryMappings = {
none: null,
icon: <PhosphorIcon icon={IconMappings.play} size="medium" />,
pill: <Pill kind="accent">New</Pill>,
};

export default {
label: {
control: {type: "text"},
description: "Display text of the action item.",
table: {
type: {summary: "string"},
},
type: {name: "string", required: true},
},
disabled: {
control: {type: "boolean"},
description: "Whether or not the action item is disabled.",
table: {
defaultValue: {summary: false},
type: {summary: "boolean"},
},
type: {name: "boolean", required: true},
},
onClick: {
control: {type: null},
description:
`Callback when the action item is clicked.\n\n` +
`Note: \`onClick\` is optional if \`href\` is present, but must ` +
`be defined if \`href\` is not present.`,
table: {
type: {summary: "() => void"},
},
type: {name: "function", required: false},
},
href: {
control: {type: "text"},
description:
`URL to navigate to when the action item is clicked.\n\n` +
`Note: \`href\` must be defined if \`onClick\` is not present.`,

table: {
type: {summary: "string"},
},
type: {name: "string", required: false},
},
target: {
control: {type: "text"},
description: "A target destination window for a link to open in.",
table: {
type: {summary: "string"},
},
type: {name: "string", required: false},
},
lang: {
control: {type: "text"},
description:
`Optional attribute to indicate to the Screen Reader which ` +
`language the item text is in.`,
table: {
type: {summary: "string"},
},
type: {name: "string", required: false},
},
testId: {
control: {type: "text"},
description: "Test ID used for e2e testing.",
table: {
type: {summary: "string"},
},
type: {name: "string", required: false},
},
horizontalRule: {
options: ["none", "inset", "full-width"],
control: {type: "select"},
description: "Adds a horizontal rule at the bottom of the action item.",
defaultValue: "none",
table: {
defaultValue: {summary: "none"},
type: {summary: `"none" | "inset" | "full-width"`},
},
type: {name: `"none" | "inset" | "full-width"`, required: false},
},
leftAccessory: {
options: Object.keys(AccessoryMappings) as Array<React.ReactNode>,
mapping: AccessoryMappings,
control: {type: "select"},
description: "Adds an accessory to the left of the action item.",
table: {
type: {summary: "React.Node"},
},
type: {name: "React.Node", required: false},
},
rightAccessory: {
options: Object.keys(AccessoryMappings) as Array<React.ReactNode>,
mapping: AccessoryMappings,
control: {type: "select"},
description: "Adds an accessory to the right of the action item.",
table: {
type: {summary: "React.Node"},
},
type: {name: "React.Node", required: false},
},
};
158 changes: 158 additions & 0 deletions __docs__/wonder-blocks-dropdown/action-item.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {Meta} from "@storybook/react";
import * as React from "react";
import {StyleSheet} from "aphrodite";
import Color from "@khanacademy/wonder-blocks-color";
import {PropsFor, View} from "@khanacademy/wonder-blocks-core";
import {ActionItem} from "@khanacademy/wonder-blocks-dropdown";
import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
import Spacing from "@khanacademy/wonder-blocks-spacing";

import ComponentInfo from "../../.storybook/components/component-info";
import packageConfig from "../../packages/wonder-blocks-dropdown/package.json";
import {IconMappings} from "../wonder-blocks-icon/phosphor-icon.argtypes";
import actionItemArgtypes from "./action-item.argtypes";

const defaultArgs = {
label: "Action Item",
onClick: () => {},
disabled: false,
testId: "",
lang: "",
role: "menuitem",
style: {},
horizontalRule: "none",
leftAccessory: null,
rightAccessory: null,
};

const styles = StyleSheet.create({
example: {
background: Color.offWhite,
padding: Spacing.medium_16,
width: 300,
},
items: {
background: Color.white,
},
});

/**
* The action item trigger actions, such as navigating to a different page or
* opening a modal. Supply the `href` and/or `onClick` props. This component is
* as a child of `ActionMenu`.
*
* ### Usage
*
* ```tsx
* import {ActionItem, ActionMenu} from "@khanacademy/wonder-blocks-dropdown";
*
* <ActionMenu {...props}>
* <ActionItem label="Action Item" onClick={() => {}} />
* </ActionMenu>
* ```
*/
export default {
title: "Dropdown/ActionItem",
component: ActionItem,
argTypes: actionItemArgtypes,
args: defaultArgs,
decorators: [
(Story): React.ReactElement<React.ComponentProps<typeof View>> => (
<View style={styles.example}>
<Story />
</View>
),
],
parameters: {
componentSubtitle: (
<ComponentInfo
name={packageConfig.name}
version={packageConfig.version}
/>
),
},
} as Meta<typeof ActionItem>;

/**
* The default action item with a `label` and an `onClick` handler. This is used
* to trigger actions, such as opening a modal.
*/
export const Default = {
args: {
label: "Action Item",
onClick: () => {},
},
};

/**
* The action item with a `label` and an `href` prop. This is used to trigger
* navigation to a different page.
*/
export const WithHref = {
args: {
label: "Action Item",
href: "https://khanacademy.org",
},
parameters: {
chromatic: {
// Disabling because this doesn't test anything visual.
disableSnapshot: true,
},
},
};

/**
* ActionItem can be `disabled`. This is used to indicate that the action is not
* available.
*/
export const Disabled = {
args: {
label: "Action Item",
onClick: () => {},
disabled: true,
},
};

/**
* ActionItem can have more complex content, such as icons.
*
* This can be done by passing in a `leftAccessory` and/or `rightAccessory`
* prop. These can be any React node, and internally use the WB Cell component
* to render.
*/
export const CustomActionItem = {
args: {
label: "Action Item",
onClick: () => {},
leftAccessory: (
<PhosphorIcon icon={IconMappings.calendar} size="medium" />
),
rightAccessory: (
<PhosphorIcon icon={IconMappings.caretRight} size="medium" />
),
},
};

/**
* `horizontalRule` can be used to separate items within ActionMenu instances.
* It defaults to `none`, but can be set to `inset` or `full-width` to add a
* horizontal rule at the bottom of the cell.
*/
export const HorizontalRule = {
args: {
label: "Action Item",
onClick: () => {},
},
render: (args: PropsFor<typeof ActionItem>): React.ReactNode => (
<View style={styles.items}>
<ActionItem
{...args}
label="full-width"
horizontalRule="full-width"
/>
<ActionItem {...args} label="inset" horizontalRule="inset" />
<ActionItem {...args} label="none" />
<ActionItem {...args} />
</View>
),
};
Loading

0 comments on commit 860d9ef

Please sign in to comment.