From 54286ce294ecf87aa0adced32d644396b20f9cfd Mon Sep 17 00:00:00 2001 From: chancestrickland Date: Fri, 7 Feb 2020 16:22:10 -0800 Subject: [PATCH] accordion: support as props for panel and item (#459) --- packages/accordion/src/index.tsx | 21 +++--- website/src/pages/accordion.mdx | 71 ++++++++++++------ website/src/pages/menu-button.mdx | 116 ++++++++++++++---------------- website/src/styles/app.scss | 17 ++++- 4 files changed, 131 insertions(+), 94 deletions(-) diff --git a/packages/accordion/src/index.tsx b/packages/accordion/src/index.tsx index 6c9112b85..46009eb37 100644 --- a/packages/accordion/src/index.tsx +++ b/packages/accordion/src/index.tsx @@ -351,9 +351,9 @@ if (__DEV__) { * * @see Docs https://reacttraining.com/reach-ui/accordion#accordionitem */ -export const AccordionItem = forwardRef( +export const AccordionItem = forwardRefWithAs( function AccordionItem( - { children, disabled = false, ...props }, + { as: Comp = "div", children, disabled = false, ...props }, forwardedRef ) { const { accordionId, openPanels, readOnly } = useAccordionContext(); @@ -391,14 +391,14 @@ export const AccordionItem = forwardRef( return ( -
{children} -
+
); } @@ -407,7 +407,7 @@ export const AccordionItem = forwardRef( /** * @see Docs https://reacttraining.com/reach-ui/accordion#accordionitem-props */ -export type AccordionItemProps = React.HTMLProps & { +export type AccordionItemProps = { /** * An `AccordionItem` expects to receive an `AccordionButton` and * `AccordionPanel` components as its children, though you can also nest other @@ -584,8 +584,11 @@ if (__DEV__) { * * @see Docs https://reacttraining.com/reach-ui/accordion#accordionpanel */ -export const AccordionPanel = forwardRef( - function AccordionPanel({ children, ...props }, forwardedRef) { +export const AccordionPanel = forwardRefWithAs( + function AccordionPanel( + { as: Comp = "div", children, ...props }, + forwardedRef + ) { const { dataAttributes, panelId, @@ -594,7 +597,7 @@ export const AccordionPanel = forwardRef( } = useAccordionItemContext(); return ( - + ); } ); diff --git a/website/src/pages/accordion.mdx b/website/src/pages/accordion.mdx index 5646f6026..ee3f12eda 100644 --- a/website/src/pages/accordion.mdx +++ b/website/src/pages/accordion.mdx @@ -38,7 +38,7 @@ import { Accordion, AccordionItem, AccordionButton, - AccordionPanel + AccordionPanel, } from "@reach/accordion"; import "@reach/accordion/styles.css"; ``` @@ -224,15 +224,15 @@ return ( #### Accordion Props -| Prop | Type | Required | -| --------------------------------------- | ---------------------- | -------- | -| [children](#accordion-children) | `node` | true | -| [collapsible](#accordion-collapsible) | `boolean` | false | -| [defaultIndex](#accordion-defaultindex) | `number` \| `number[]` | false | -| [index](#accordion-index) | `number` \| `number[]` | false | -| [multiple](#accordion-multiple) | `boolean` | false | -| [onChange](#accordion-onchange) | `func` | false | -| [readOnly](#accordion-readonly) | `boolean` | false | +| Prop | Type | Required | +| ----------------------------------------- | ---------------------- | -------- | +| [`children`](#accordion-children) | `node` | true | +| [`collapsible`](#accordion-collapsible) | `boolean` | false | +| [`defaultIndex`](#accordion-defaultindex) | `number` \| `number[]` | false | +| [`index`](#accordion-index) | `number` \| `number[]` | false | +| [`multiple`](#accordion-multiple) | `boolean` | false | +| [`onChange`](#accordion-onchange) | `func` | false | +| [`readOnly`](#accordion-readonly) | `boolean` | false | ##### Accordion `children` @@ -365,10 +365,19 @@ Please see the [styling guide](/styling). #### AccordionItem Props -| Prop | Type | Required | -| ----------------------------------- | --------- | -------- | -| [children](#accordionitem-children) | `node` | true | -| [disabled](#accordionitem-disabled) | `boolean` | false | +| Prop | Type | Required | +| ------------------------------------- | -------------------- | -------- | +| [`as`](#accordionitem-as) | `string | Component` | false | +| [`children`](#accordionitem-children) | `node` | true | +| [`disabled`](#accordionitem-disabled) | `boolean` | false | + +##### AccordionItem `as` + +`as: keyof JSX.IntrinsicElements | React.ComponentType` + +A string representing an HTML element or a React component that will tell the `AccordionItem` what element to render. Defaults to `div`. + +> **NOTE:** Many semantic elements, like `button` elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users. ##### AccordionItem `children` @@ -422,9 +431,18 @@ Please see the [styling guide](/styling). #### AccordionButton Props -| Prop | Type | Required | -| ------------------------------------- | ------ | -------- | -| [children](#accordionbutton-children) | `node` | true | +| Prop | Type | Required | +| --------------------------------------- | -------------------- | -------- | +| [`as`](#accordionbutton-as) | `string | Component` | false | +| [`children`](#accordionbutton-children) | `node` | true | + +##### AccordionButton `as` + +`as: keyof JSX.IntrinsicElements | React.ComponentType` + +A string representing an HTML element or a React component that will tell the `AccordionButton` what element to render. Defaults to `button`. + +> **NOTE:** Many semantic elements, like `button` elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users. ##### AccordionButton `children` @@ -486,7 +504,7 @@ If you need to group interactive elements within an accordion's button, we recom margin: "9px 0", display: "flex", justifyContent: "space-between", - padding: "4px 10px" + padding: "4px 10px", }} > {children} @@ -553,9 +571,18 @@ Please see the [styling guide](/styling). #### AccordionPanel Props -| Prop | Type | Required | -| ------------------------------------ | ------ | -------- | -| [children](#accordionpanel-children) | `node` | true | +| Prop | Type | Required | +| -------------------------------------- | -------------------- | -------- | +| [`as`](#accordionpanel-as) | `string | Component` | false | +| [`children`](#accordionpanel-children) | `node` | true | + +##### AccordionPanel `as` + +`as: keyof JSX.IntrinsicElements | React.ComponentType` + +A string representing an HTML element or a React component that will tell the `AccordionPanel` what element to render. Defaults to `div`. + +> **NOTE:** Many semantic elements, like `button` elements, have meaning to assistive devices and browsers that provide context for the user and, in many cases, provide or restrict interactive behaviors. Use caution when overriding our defaults and make sure that the element you choose to render provides the same experience for all users. ##### AccordionPanel `children` diff --git a/website/src/pages/menu-button.mdx b/website/src/pages/menu-button.mdx index 4783321cb..6b5ccba58 100644 --- a/website/src/pages/menu-button.mdx +++ b/website/src/pages/menu-button.mdx @@ -82,11 +82,11 @@ The wrapper component for the other components. No DOM element is rendered. #### Menu Props -| Prop | Type | Required | -| -------------------------- | ---- | -------- | -| [children](#menu-children) | node | false | +| Prop | Type | Required | +| ---------------------------- | ------ | -------- | +| [`children`](#menu-children) | `node` | false | -##### Menu children +##### Menu `children` _Type_: `oneOfType(node, function)` @@ -172,16 +172,14 @@ If you'd like to target when the menu is open use `aria-expanded`: #### MenuButton Props -| Prop | Type | Required | -| ---------------------------------------- | -------------------- | -------- | -| [button props](#menubutton-button-props) | spread | n/a | -| [children](#menubutton-children) | node | false | -| onClick | preventableEventFunc | false | -| onKeyDown | preventableEventFunc | false | - -##### MenuButton button props +| Prop | Type | Required | +| ------------------------------------------ | ---------------------- | -------- | +| [`button` props](#menubutton-button-props) | | | +| [`children`](#menubutton-children) | `node` | false | +| `onClick` | `preventableEventFunc` | false | +| `onKeyDown` | `preventableEventFunc` | false | -_Type_: `spread` +##### MenuButton `button` props Any props not listed above will be spread onto the underlying button element. You can treat it like any other button in your app for styling. @@ -199,11 +197,11 @@ Any props not listed above will be spread onto the underlying button element. Yo ``` -##### MenuButton as +##### MenuButton `as` _Type_: `` -##### MenuButton children +##### MenuButton `children` _Type_: `node` @@ -241,14 +239,12 @@ Wraps a DOM element that renders the menu items. Must be rendered inside of a `< #### MenuList Props -| Prop | Type | Required | -| ----------------------------------------- | ------ | -------- | -| [element props](#menuitems-element-props) | spread | n/a | -| [children](#menuitems-children) | node | false | +| Prop | Type | Required | +| ----------------------------------- | ------ | -------- | +| [`div` props](#menuitems-div-props) | | | +| [`children`](#menuitems-children) | `node` | false | -##### MenuList element props - -_Type_: `spread` +##### MenuList `div` props All props are spread to the underlying element. Here we apply a `className` the element. @@ -291,7 +287,7 @@ The stylesheet contains these rules to create the animation. } ``` -##### MenuList children +##### MenuList `children` _Type_: `node` @@ -330,11 +326,11 @@ A low-level wrapper for the popover that appears when a menu button is open. You #### MenuPopover Props -| Prop | Type | Required | -| --------------------------------- | ---- | -------- | -| [children](#menupopover-children) | node | true | +| Prop | Type | Required | +| ----------------------------------- | ------ | -------- | +| [`children`](#menupopover-children) | `node` | true | -##### MenuPopover children props +##### MenuPopover `children` props _Type_: `node` @@ -410,17 +406,15 @@ function Example() { #### MenuItem Props -| Prop | Type | Required | -| ---------------------------------------- | ------ | -------- | -| [element props](#menuitem-element-props) | spread | n/a | -| [as](#menuitem-as) | any | false | -| [children](#menuitem-children) | node | false | -| [onSelect](#menuitem-onselect) | func | true | +| Prop | Type | Required | +| ---------------------------------------- | -------------------- | -------- | +| [element props](#menuitem-element-props) | | | +| [`as`](#menuitem-as) | `string | Component` | false | +| [`children`](#menuitem-children) | `node` | false | +| [`onSelect`](#menuitem-onselect) | `func` | true | ##### MenuItem element props -_Type_: `spread` - All props are spread to the underlying element. In this example the `onFocus` prop is passed down to the element. @@ -449,7 +443,7 @@ function Example(props) { } ``` -##### MenuItem as +##### MenuItem `as` _Type_: `any` @@ -487,7 +481,7 @@ function Example() { } ``` -##### MenuItem children +##### MenuItem `children` _Type_: `node` @@ -519,7 +513,7 @@ function Example(props) { } ``` -##### MenuItem onSelect +##### MenuItem `onSelect` _Type_: `func` @@ -564,17 +558,15 @@ To change the styles of a highlighted menu item, use this pseudo-pseudo selector #### MenuLink Props -| Prop | Type | Required | -| ---------------------------------------- | ------ | -------- | -| [element props](#menulink-element-props) | spread | n/a | -| [as](#menulink-as) | any | true | -| [children](#menulink-children) | node | false | -| [onSelect](#menulink-onselect) | func | false | +| Prop | Type | Required | +| ---------------------------------------- | -------------------- | -------- | +| [element props](#menulink-element-props) | | | +| [`as`](#menulink-as) | `string | Component` | true | +| [`children`](#menulink-children) | `node` | false | +| [`onSelect`](#menulink-onselect) | `func` | false | ##### MenuLink element props -_Type_: `spread` - All props are spread to the underlying element ```jsx @@ -587,7 +579,7 @@ All props are spread to the underlying element Official React Site ``` -##### MenuLink as +##### MenuLink `as` _Type_: `any` @@ -614,7 +606,7 @@ import GatsbyLink from "gatsby/link"; ; ``` -##### MenuLink children +##### MenuLink `children` _Type_: `node` @@ -627,7 +619,7 @@ You can render any kind of content inside of a MenuLink. ``` -##### MenuLink onSelect +##### MenuLink `onSelect` _Type_: `func` @@ -754,19 +746,21 @@ This won't work because we actually do not call the `onClick` handler to activat As such, events for each component look more like this: -- `MenuButton`: Activates `Menu` in `onMouseDown` or `onKeyDown` (`"Enter"` or space `" "` keys) -- `MenuItem`: Selects itself in `onMouseUp` or `onKeyDown` (`"Enter"` or space `" "` keys) -- `MenuLink`: Selects itself in `onMouseUp` or `onKeyDown` (`"Enter"` or space `" "` keys). +- `MenuButton`: Activates `Menu` in `onMouseDown` or `onKeyDown` (Enter or Space keys) +- `MenuItem`: Selects itself in `onMouseUp` or `onKeyDown` (Enter or Space keys) +- `MenuLink`: Selects itself in `onMouseUp` or `onKeyDown` (Enter or Space keys). - For `MenuLink`, the `click` event is fired after the selection events. So if you only need to intercept the event that triggers the anchor link, you can still use `onClick`, but the rest of the event handlers called in `MenuLink` will still be which means the `Menu` will close and your `onSelect` handler will be triggered. ### Keyboard Accessibility -| Key | Action | -| --------------- | ------------------------ | -| `Enter` | Open/close | -| `ArrowUp` | Highlight previous item | -| `ArrowDown` | Highlight next item | -| `Enter` | Select item | -| `Escape` | Close | -| `Tab` | No effect | -| Type characters | Highlights matching item | +| Key | Action | +| -------------------- | ------------------------------------------------------------------------------ | +| Enter | Open/close while focused on the `MenuButton`; Selects an item when navigating. | +| Space | Open/close while focused on the `MenuButton`; Selects an item when navigating. | +| ArrowUp | Highlight previous item | +| ArrowDown | Highlight next item | +| Home | Highlight first item | +| End | Highlight last item | +| Escape | Close menu | +| Tab | No effect | +| Type characters | Highlights matching item | diff --git a/website/src/styles/app.scss b/website/src/styles/app.scss index ec179a91e..db808c130 100644 --- a/website/src/styles/app.scss +++ b/website/src/styles/app.scss @@ -53,7 +53,7 @@ .slide-down[data-reach-menu-list], .slide-down[data-reach-menu-items] { - border-radius: 5px; + border-radius: 0.25rem; animation: slide-down 0.2s ease; } @@ -125,7 +125,7 @@ code { padding: 0.2rem 0.5rem; margin: 0; font-size: 90%; - border-radius: 4px; + border-radius: 0.25rem; background: var(--input-background-color); } @@ -184,6 +184,19 @@ pre > code { margin: 0; } +kbd { + box-sizing: border-box; + border-radius: 0.25rem; + font-family: var(--font-family-mono); + background-color: var(--input-background-color); + padding: 0.2em 0.4em 0.125em; + border-color: var(--input-border-color); + border-style: solid; + border-width: 1px 1px 2px; + font-size: 0.825em; + line-height: inherit; +} + h1 { color: $color-primary; }