@reach/descendants
Advanced tools
Comparing version 0.17.0 to 0.18.0-pre.0
{ | ||
"name": "@reach/descendants", | ||
"version": "0.17.0", | ||
"version": "0.18.0-pre.0", | ||
"description": "A descendant index solution for better accessibility support in compound components", | ||
"author": "React Training <hello@reacttraining.com>", | ||
"license": "MIT", | ||
"sideEffects": [ | ||
"*.css" | ||
], | ||
"repository": { | ||
@@ -16,8 +13,11 @@ "type": "git", | ||
"dependencies": { | ||
"@reach/utils": "0.17.0", | ||
"tslib": "^2.3.0" | ||
"@reach/utils": "0.18.0-pre.0" | ||
}, | ||
"devDependencies": { | ||
"@reach-internal/dev": "0.0.0", | ||
"@reach-internal/test": "0.0.0", | ||
"@reach-internal/tsconfig": "0.0.0", | ||
"react": "^17.0.2", | ||
"react-dom": "^17.0.2" | ||
"react-dom": "^17.0.2", | ||
"tsup": "^6.1.3" | ||
}, | ||
@@ -28,14 +28,18 @@ "peerDependencies": { | ||
}, | ||
"main": "dist/reach-descendants.cjs.js", | ||
"module": "dist/reach-descendants.esm.js", | ||
"types": "dist/reach-descendants.cjs.d.ts", | ||
"main": "./dist/index.cjs.js", | ||
"types": "./dist/index.d.ts", | ||
"files": [ | ||
"CHANGELOG.md", | ||
"LICENSE", | ||
"README.md", | ||
"dist" | ||
"dist", | ||
"styles.css" | ||
], | ||
"eslintIgnore": [ | ||
"node_modules", | ||
"dist" | ||
], | ||
"gitHead": "d206aefac2bede58c06a54b18d48eee7537096e0" | ||
} | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"module": "./dist/index.mjs", | ||
"scripts": { | ||
"build": "tsup" | ||
} | ||
} |
470
README.md
@@ -56,5 +56,5 @@ # @reach/descendants | ||
<Menu> | ||
<MenuItem onSelect={download}>Download</MenuItem> | ||
<MenuItem onSelect={save}>Save</MenuItem> | ||
<MenuItem onSelect={preview}>Preview</MenuItem> | ||
<MenuItem onSelect={download}>Download</MenuItem> | ||
<MenuItem onSelect={save}>Save</MenuItem> | ||
<MenuItem onSelect={preview}>Preview</MenuItem> | ||
</Menu> | ||
@@ -71,39 +71,39 @@ ``` | ||
<Menu | ||
items={[ | ||
{ label: "Download", onSelect: download }, | ||
{ label: "Save", onSelect: save }, | ||
{ label: "Preview", onSelect: preview }, | ||
]} | ||
items={[ | ||
{ label: "Download", onSelect: download }, | ||
{ label: "Save", onSelect: save }, | ||
{ label: "Preview", onSelect: preview }, | ||
]} | ||
/>; | ||
function Menu({ items }) { | ||
let [activeIndex, setActiveIndex] = React.useState(); | ||
return ( | ||
<div data-menu aria-activedescendant={activeIndex}> | ||
{items.map((item, index) => ( | ||
<MenuItem | ||
// easy to tell the index | ||
index={index} | ||
activeIndex={activeIndex} | ||
onSelect={item.onSelect} | ||
> | ||
{item.label} | ||
</MenuItem> | ||
))} | ||
</div> | ||
); | ||
let [activeIndex, setActiveIndex] = React.useState(); | ||
return ( | ||
<div data-menu aria-activedescendant={activeIndex}> | ||
{items.map((item, index) => ( | ||
<MenuItem | ||
// easy to tell the index | ||
index={index} | ||
activeIndex={activeIndex} | ||
onSelect={item.onSelect} | ||
> | ||
{item.label} | ||
</MenuItem> | ||
))} | ||
</div> | ||
); | ||
} | ||
function MenuItem({ index, activeIndex, onSelect, children }) { | ||
// and now we can style | ||
let isActive = index === activeIndex; | ||
return ( | ||
<div | ||
// and add an ID | ||
id={index} | ||
data-highlighted={isActive ? "" : undefined} | ||
> | ||
{children} | ||
</div> | ||
); | ||
// and now we can style | ||
let isActive = index === activeIndex; | ||
return ( | ||
<div | ||
// and add an ID | ||
id={index} | ||
data-highlighted={isActive ? "" : undefined} | ||
> | ||
{children} | ||
</div> | ||
); | ||
} | ||
@@ -118,34 +118,34 @@ ``` | ||
<Menu | ||
options={[ | ||
{ label: "Download", onSelect: download }, | ||
{ label: "Save", onSelect: save }, | ||
{ label: "Preview", onSelect: preview }, | ||
]} | ||
// stuff like this | ||
optionClassNames="cool" | ||
// or shoot, we need more than classNames | ||
optionsProps={{ | ||
className: "cool", | ||
onMouseEnter: handler, | ||
}} | ||
// dangit we need to do it differently depending on the option | ||
getOptionProps={(option, index) => { | ||
return index === 2 ? "bg-blue" : "bg-white"; | ||
}} | ||
// ah forget it, here you do it, enjoy the branching! | ||
renderOption={(option, index) => ( | ||
<MenuItem | ||
className={index === 2 ? "bg-blue" : "bg-white"} | ||
aria-label={index === 2 ? "Preview Invoice" : undefined} | ||
> | ||
{index === 0 ? ( | ||
<DownloadIcon /> | ||
) : index === 1 ? ( | ||
<SaveIcon /> | ||
) : index === 2 ? ( | ||
<PreviewIcon /> | ||
) : null} | ||
{option.label} | ||
</MenuItem> | ||
)} | ||
options={[ | ||
{ label: "Download", onSelect: download }, | ||
{ label: "Save", onSelect: save }, | ||
{ label: "Preview", onSelect: preview }, | ||
]} | ||
// stuff like this | ||
optionClassNames="cool" | ||
// or shoot, we need more than classNames | ||
optionsProps={{ | ||
className: "cool", | ||
onMouseEnter: handler, | ||
}} | ||
// dangit we need to do it differently depending on the option | ||
getOptionProps={(option, index) => { | ||
return index === 2 ? "bg-blue" : "bg-white"; | ||
}} | ||
// ah forget it, here you do it, enjoy the branching! | ||
renderOption={(option, index) => ( | ||
<MenuItem | ||
className={index === 2 ? "bg-blue" : "bg-white"} | ||
aria-label={index === 2 ? "Preview Invoice" : undefined} | ||
> | ||
{index === 0 ? ( | ||
<DownloadIcon /> | ||
) : index === 1 ? ( | ||
<SaveIcon /> | ||
) : index === 2 ? ( | ||
<PreviewIcon /> | ||
) : null} | ||
{option.label} | ||
</MenuItem> | ||
)} | ||
/> | ||
@@ -162,15 +162,15 @@ ``` | ||
<Menu> | ||
<MenuItem className="bg-white" onSelect={download}> | ||
<DownloadIcon /> Download | ||
</MenuItem> | ||
<MenuItem className="bg-white" onSelect={save}> | ||
<SaveIcon /> Save | ||
</MenuItem> | ||
<MenuItem | ||
className="bg-white" | ||
onSelect={preview} | ||
aria-label="Preview Invoice" | ||
> | ||
<PreviewIcon /> Preview | ||
</MenuItem> | ||
<MenuItem className="bg-white" onSelect={download}> | ||
<DownloadIcon /> Download | ||
</MenuItem> | ||
<MenuItem className="bg-white" onSelect={save}> | ||
<SaveIcon /> Save | ||
</MenuItem> | ||
<MenuItem | ||
className="bg-white" | ||
onSelect={preview} | ||
aria-label="Preview Invoice" | ||
> | ||
<PreviewIcon /> Preview | ||
</MenuItem> | ||
</Menu> | ||
@@ -187,20 +187,20 @@ ``` | ||
function Menu({ children }) { | ||
let [activeIndex, setActiveIndex] = React.useState(); | ||
return ( | ||
<div data-menu aria-activedescendant={activeIndex}> | ||
{React.Children.map(children, (child, index) => | ||
React.cloneElement(child, { index, activeIndex }) | ||
)} | ||
</div> | ||
); | ||
let [activeIndex, setActiveIndex] = React.useState(); | ||
return ( | ||
<div data-menu aria-activedescendant={activeIndex}> | ||
{React.Children.map(children, (child, index) => | ||
React.cloneElement(child, { index, activeIndex }) | ||
)} | ||
</div> | ||
); | ||
} | ||
function MenuItem({ index, activeIndex, onSelect, children }) { | ||
// index came from the clone | ||
let isActive = index === activeIndex; | ||
return ( | ||
<div id={index} data-highlighted={isActive ? "" : undefined}> | ||
{children} | ||
</div> | ||
); | ||
// index came from the clone | ||
let isActive = index === activeIndex; | ||
return ( | ||
<div id={index} data-highlighted={isActive ? "" : undefined}> | ||
{children} | ||
</div> | ||
); | ||
} | ||
@@ -217,6 +217,6 @@ ``` | ||
<Menu> | ||
<div> | ||
<MenuItem /> | ||
</div> | ||
<MenuItem /> | ||
<div> | ||
<MenuItem /> | ||
</div> | ||
<MenuItem /> | ||
</Menu> | ||
@@ -231,8 +231,8 @@ ``` | ||
function BlueItem(props) { | ||
return <MenuItem {...props} className="bg-blue" />; | ||
return <MenuItem {...props} className="bg-blue" />; | ||
} | ||
<Menu> | ||
<MenuItem /> | ||
<BlueItem /> | ||
<MenuItem /> | ||
<BlueItem /> | ||
</Menu>; | ||
@@ -257,6 +257,6 @@ ``` | ||
import { | ||
createDescendantContext, | ||
DescendantProvider, | ||
useDescendant, | ||
useDescendantsInit, | ||
createDescendantContext, | ||
DescendantProvider, | ||
useDescendant, | ||
useDescendantsInit, | ||
} from "@reach/descendants"; | ||
@@ -268,97 +268,97 @@ | ||
function Menu({ id, children }) { | ||
// We could be less explicit here and set this up in the DescendantProvider, | ||
// but you may want to do something with `descendants` in your top-level | ||
// component and we don't want to force creating an arbitrary child | ||
// component just so we can consume the context. | ||
let [descendants, setDescendants] = useDescendantsInit(); | ||
let [activeIndex, setActiveIndex] = React.useState(-1); | ||
return ( | ||
<DescendantProvider | ||
context={DescendantContext} | ||
items={descendants} | ||
set={setDescendants} | ||
> | ||
<MenuContext.Provider | ||
value={{ buttonId: `button-${useId(id)}`, activeIndex, setActiveIndex }} | ||
> | ||
{children} | ||
</MenuContext.Provider> | ||
</DescendantProvider> | ||
); | ||
// We could be less explicit here and set this up in the DescendantProvider, | ||
// but you may want to do something with `descendants` in your top-level | ||
// component and we don't want to force creating an arbitrary child | ||
// component just so we can consume the context. | ||
let [descendants, setDescendants] = useDescendantsInit(); | ||
let [activeIndex, setActiveIndex] = React.useState(-1); | ||
return ( | ||
<DescendantProvider | ||
context={DescendantContext} | ||
items={descendants} | ||
set={setDescendants} | ||
> | ||
<MenuContext.Provider | ||
value={{ buttonId: `button-${useId(id)}`, activeIndex, setActiveIndex }} | ||
> | ||
{children} | ||
</MenuContext.Provider> | ||
</DescendantProvider> | ||
); | ||
} | ||
function MenuList(props) { | ||
let { buttonId, activeIndex } = useContext(MenuContext); | ||
return ( | ||
<Popover> | ||
<div | ||
role="menu" | ||
aria-labelledby={buttonId} | ||
aria-activedescendant={activeIndex} | ||
tabIndex={-1} | ||
> | ||
{children} | ||
</div> | ||
</Popover> | ||
); | ||
let { buttonId, activeIndex } = useContext(MenuContext); | ||
return ( | ||
<Popover> | ||
<div | ||
role="menu" | ||
aria-labelledby={buttonId} | ||
aria-activedescendant={activeIndex} | ||
tabIndex={-1} | ||
> | ||
{children} | ||
</div> | ||
</Popover> | ||
); | ||
} | ||
function MenuItem({ index: explicitIndex, ...props }) { | ||
let { activeIndex, setActiveIndex } = useContext(MenuContext); | ||
let ref = React.useRef(null); | ||
let { activeIndex, setActiveIndex } = useContext(MenuContext); | ||
let ref = React.useRef(null); | ||
// We need a ref to the element for our descendant object, but we also | ||
// need to update state after the ref is placed. We can set the ref in a | ||
// callback and use the stateful `element` to ensure our descendant is | ||
// updated once we have DOM node. | ||
let [element, setElement] = useState(null); | ||
let handleRefSet = useCallback((refValue) => { | ||
ref.current = refValue; | ||
setState(refValue); | ||
}, []); | ||
// We need a ref to the element for our descendant object, but we also | ||
// need to update state after the ref is placed. We can set the ref in a | ||
// callback and use the stateful `element` to ensure our descendant is | ||
// updated once we have DOM node. | ||
let [element, setElement] = useState(null); | ||
let handleRefSet = useCallback((refValue) => { | ||
ref.current = refValue; | ||
setElement(refValue); | ||
}, []); | ||
// The descendant should be memoized to prevent endless render loops after | ||
// the collection state is updated | ||
let descendant = React.useMemo(() => { | ||
return { | ||
// Assign the DOM node using a stateful reference. This should be safer | ||
// than passing the ref directly. | ||
element, | ||
// You can pass arbitrary data into a descendant object which can come | ||
// in handy for features like typeahead! | ||
key: props.label, | ||
}; | ||
}, [element, props.label]); | ||
// The descendant should be memoized to prevent endless render loops after | ||
// the collection state is updated | ||
let descendant = React.useMemo(() => { | ||
return { | ||
// Assign the DOM node using a stateful reference. This should be safer | ||
// than passing the ref directly. | ||
element, | ||
// You can pass arbitrary data into a descendant object which can come | ||
// in handy for features like typeahead! | ||
key: props.label, | ||
}; | ||
}, [element, props.label]); | ||
let index = useDescendant( | ||
descendant, | ||
// Tell the useDescendant hook to use a specific context. | ||
// This is key in case you have a compound component that needs index | ||
// tracking in separate correlating descendant components (like `Tabs`) | ||
DescendantContext, | ||
// If you want to declare a specific index value, you can pass it as the | ||
// third argument here. This is almost never needed but we provide it as an | ||
// escape hatch for special circumstances. | ||
explicitIndex | ||
); | ||
let index = useDescendant( | ||
descendant, | ||
// Tell the useDescendant hook to use a specific context. | ||
// This is key in case you have a compound component that needs index | ||
// tracking in separate correlating descendant components (like `Tabs`) | ||
DescendantContext, | ||
// If you want to declare a specific index value, you can pass it as the | ||
// third argument here. This is almost never needed but we provide it as an | ||
// escape hatch for special circumstances. | ||
explicitIndex | ||
); | ||
// Now we know the index, so let's use it! | ||
let isSelected = index === activeIndex; | ||
function select() { | ||
if (!isSelected) { | ||
setActiveIndex(index); | ||
} | ||
} | ||
// Now we know the index, so let's use it! | ||
let isSelected = index === activeIndex; | ||
function select() { | ||
if (!isSelected) { | ||
setActiveIndex(index); | ||
} | ||
} | ||
return ( | ||
<div | ||
role="menuitem" | ||
// Don't forget to pass the callback ref to the rendered element! | ||
ref={handleRefSet} | ||
data-selected={isSelected ? "" : undefined} | ||
tabIndex={-1} | ||
onMouseEnter={select} | ||
{...props} | ||
/> | ||
); | ||
return ( | ||
<div | ||
role="menuitem" | ||
// Don't forget to pass the callback ref to the rendered element! | ||
ref={handleRefSet} | ||
data-selected={isSelected ? "" : undefined} | ||
tabIndex={-1} | ||
onMouseEnter={select} | ||
{...props} | ||
/> | ||
); | ||
} | ||
@@ -377,58 +377,58 @@ ``` | ||
function Comp() { | ||
<Listbox> | ||
{/* | ||
<Listbox> | ||
{/* | ||
The button needs to know which value is selected to render its label! | ||
The label will be empty on the server if we depend on descendant hooks | ||
*/} | ||
<ListboxButton /> | ||
<ListboxList> | ||
<ListboxOption value="Apple" /> | ||
<ListboxOption value="Orange" /> | ||
<ListboxOption value="Banana" /> | ||
</ListboxList> | ||
</Listbox>; | ||
<ListboxButton /> | ||
<ListboxList> | ||
<ListboxOption value="Apple" /> | ||
<ListboxOption value="Orange" /> | ||
<ListboxOption value="Banana" /> | ||
</ListboxList> | ||
</Listbox>; | ||
} | ||
function CompSSR() { | ||
// This limits composition, but now you have your data in one place at the top | ||
let options = ["Apple", "Orange", "Banana"]; | ||
let [activeOption, setActiveOption] = React.useState(options[0]); | ||
<Listbox onChange={setActiveOption} selected={activeOption}> | ||
{/* The button needs to know which value is selected to render its label! */} | ||
<ListboxButton>{activeOption}</ListboxButton> | ||
<ListboxList> | ||
{options.map((option) => ( | ||
<ListboxOption value={option} key={option} /> | ||
))} | ||
</ListboxList> | ||
</Listbox>; | ||
// This limits composition, but now you have your data in one place at the top | ||
let options = ["Apple", "Orange", "Banana"]; | ||
let [activeOption, setActiveOption] = React.useState(options[0]); | ||
<Listbox onChange={setActiveOption} selected={activeOption}> | ||
{/* The button needs to know which value is selected to render its label! */} | ||
<ListboxButton>{activeOption}</ListboxButton> | ||
<ListboxList> | ||
{options.map((option) => ( | ||
<ListboxOption value={option} key={option} /> | ||
))} | ||
</ListboxList> | ||
</Listbox>; | ||
} | ||
function ComposableSSR() { | ||
// You can manage state at the top and still get back some composition, you'll | ||
// just have to deal with a bit of repitition | ||
let [activeOption, setActiveOption] = React.useState("Apple"); | ||
<Listbox onChange={setActiveOption} selected={activeOption}> | ||
{/* The button needs to know which value is selected to render its label! */} | ||
<ListboxButton>{activeOption}</ListboxButton> | ||
<ListboxList> | ||
<ListboxOption value="Apple"> | ||
Apple <span aria-hidden>🍎</span> | ||
</ListboxOption> | ||
<ListboxOption | ||
value="Orange" | ||
aria-labelledby="orange-label" | ||
aria-describedby="orange-description" | ||
> | ||
<span id="orange-label"> | ||
Orange <span aria-hidden>🍊</span> | ||
</span> | ||
<span id="orange-description">Fun fact: Oranges are delicious!</span> | ||
</ListboxOption> | ||
<ListboxOption value="Banana"> | ||
Banana <span aria-hidden>🍌</span> | ||
</ListboxOption> | ||
</ListboxList> | ||
</Listbox>; | ||
// You can manage state at the top and still get back some composition, you'll | ||
// just have to deal with a bit of repitition | ||
let [activeOption, setActiveOption] = React.useState("Apple"); | ||
<Listbox onChange={setActiveOption} selected={activeOption}> | ||
{/* The button needs to know which value is selected to render its label! */} | ||
<ListboxButton>{activeOption}</ListboxButton> | ||
<ListboxList> | ||
<ListboxOption value="Apple"> | ||
Apple <span aria-hidden>🍎</span> | ||
</ListboxOption> | ||
<ListboxOption | ||
value="Orange" | ||
aria-labelledby="orange-label" | ||
aria-describedby="orange-description" | ||
> | ||
<span id="orange-label"> | ||
Orange <span aria-hidden>🍊</span> | ||
</span> | ||
<span id="orange-description">Fun fact: Oranges are delicious!</span> | ||
</ListboxOption> | ||
<ListboxOption value="Banana"> | ||
Banana <span aria-hidden>🍌</span> | ||
</ListboxOption> | ||
</ListboxList> | ||
</Listbox>; | ||
} | ||
``` |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
60847
3
10
1
6
633
3
1
+ Added@reach/utils@0.18.0-pre.0(transitive)
- Removedtslib@^2.3.0
- Removed@reach/utils@0.17.0(transitive)
- Removedtiny-warning@1.0.3(transitive)
- Removedtslib@2.6.3(transitive)
Updated@reach/utils@0.18.0-pre.0