Skip to content

Commit

Permalink
add examples for descendants
Browse files Browse the repository at this point in the history
  • Loading branch information
chaance committed Aug 2, 2021
1 parent a59c428 commit 2f535ab
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 0 deletions.
162 changes: 162 additions & 0 deletions packages/descendants/examples/add-remove.example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import * as React from "react";
import { useStatefulRefValue } from "@reach/utils/use-stateful-ref-value";
import {
createDescendantContext,
DescendantProvider,
useDescendant,
useDescendantsInit,
} from "@reach/descendants";
import type { Descendant } from "@reach/descendants";
import { isFunction } from "@reach/utils/type-check";

/*
For dynamic lists where items may be rearranged, you should explicitly pass an
index prop. Of course you don't *need* the descendants package at all in this
case, this is to illustrate that the explicit index takes precedence over the
calculated index.
*/

const name = "Adding/Removing";

function Example() {
let addBtn = React.useRef<HTMLInputElement>(null);
let [{ items, inputValue }, set] = React.useState(() => ({
items: [] as string[],
inputValue: "",
}));

return (
<div>
<form
onSubmit={(event) => {
event.preventDefault();
let value = inputValue.trim();
if (!value) {
console.log("Please enter a value!");
return;
} else if (items.includes(value)) {
console.log("Items in the list must be unique");
return;
}

set(({ items }) => ({
items: [...items, value],
inputValue: "",
}));
}}
>
<div>
<label>
<input
maxLength={7}
type="text"
name="add"
ref={addBtn}
autoComplete="off"
required
onChange={(e) => {
let next = e.target.value;
if (!next.includes(" ")) {
set(({ items }) => ({
items,
inputValue: next,
}));
}
}}
value={inputValue}
/>
</label>
</div>
<button type="submit" disabled={!inputValue || undefined}>
Add
</button>
<button
type="button"
disabled={items.length < 1 || undefined}
onClick={() => {
if (items.length < 1) {
return;
}
set(({ items, inputValue }) => {
let i = Math.floor(Math.random() * items.length);
return {
inputValue,
items: [
...items.slice(0, i),
...items.slice(i + 1, items.length),
],
};
});
}}
>
Remove Random Item
</button>
</form>

<hr />
<ListProvider>
{items.map((item, i) => (
<ListItem
key={item}
index={i}
style={{
display: "flex",
justifyContent: "space-between",
width: 200,
maxWidth: "100%",
gap: 10,
fontFamily: "monospace",
}}
>
{({ index }) => (
<React.Fragment>
<div>Item: {item}</div>
<div>Index: {index}</div>
</React.Fragment>
)}
</ListItem>
))}
</ListProvider>
</div>
);
}

Example.storyName = name;
export { Example };

const DescendantContext =
createDescendantContext<DescendantType>("DescendantContext");

const ListProvider: React.FC = ({ children }) => {
let [descendants, setDescendants] = useDescendantsInit<DescendantType>();
return (
<DescendantProvider
context={DescendantContext}
items={descendants}
set={setDescendants}
>
{children}
</DescendantProvider>
);
};

const ListItem: React.FC<{
children: React.ReactNode | ((props: { index: number }) => React.ReactNode);
index?: number;
style?: React.CSSProperties;
}> = ({ children, index: indexProp, ...rest }) => {
let ref = React.useRef<HTMLDivElement | null>(null);
let [element, handleRefSet] = useStatefulRefValue(ref, null);
let descendant: Omit<DescendantType, "index"> = React.useMemo(() => {
return { element };
}, [element]);
let index = useDescendant(descendant, DescendantContext, indexProp);

return (
<div data-index={index} ref={handleRefSet} {...rest}>
{isFunction(children) ? children({ index }) : children}
</div>
);
};

type DescendantType = Descendant<HTMLDivElement>;
6 changes: 6 additions & 0 deletions packages/descendants/examples/index.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { Example as AddRemove } from "./add-remove.example";
export { Example as Sorting } from "./sorting.example";

export default {
title: "Descendants",
};
101 changes: 101 additions & 0 deletions packages/descendants/examples/sorting.example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as React from "react";
import { action } from "@storybook/addon-actions";
import { useStatefulRefValue } from "@reach/utils/use-stateful-ref-value";
import {
createDescendantContext,
DescendantProvider,
useDescendant,
useDescendantsInit,
} from "@reach/descendants";
import type { Descendant } from "@reach/descendants";
import { isFunction } from "@reach/utils/type-check";

/*
For dynamic lists where items may be rearranged, you should explicitly pass an
index prop. Of course you don't *need* the descendants package at all in this
case, this is to illustrate that the explicit index takes precedence over the
calculated index.
*/

const name = "Sorting";

function Example() {
let [items, setItems] = React.useState([0, 1, 2, 3, 4, 5]);
return (
<div>
<button
onClick={() => {
setItems((items) => {
return [...items].sort(() => 0.5 - Math.random());
});
}}
>
Randomize
</button>
<hr />
<ListProvider>
{items.map((item, i) => (
<ListItem
key={item}
index={i}
style={{
display: "flex",
justifyContent: "space-between",
width: 200,
maxWidth: "100%",
gap: 10,
fontFamily: "monospace",
}}
>
{({ index }) => (
<React.Fragment>
<div>Item: {item}</div>
<div>Index: {index}</div>
</React.Fragment>
)}
</ListItem>
))}
</ListProvider>
</div>
);
}

Example.storyName = name;
export { Example };

const DescendantContext =
createDescendantContext<DescendantType>("DescendantContext");

const ListProvider: React.FC = ({ children }) => {
let [descendants, setDescendants] = useDescendantsInit<DescendantType>();
return (
<DescendantProvider
context={DescendantContext}
items={descendants}
set={setDescendants}
>
{children}
</DescendantProvider>
);
};

const ListItem: React.FC<{
children: React.ReactNode | ((props: { index: number }) => React.ReactNode);
index?: number;
style?: React.CSSProperties;
}> = ({ children, index: indexProp, ...rest }) => {
let ref = React.useRef<HTMLDivElement | null>(null);
let [element, handleRefSet] = useStatefulRefValue(ref, null);
let descendant: Omit<DescendantType, "index"> = React.useMemo(() => {
return { element };
}, [element]);
let index = useDescendant(descendant, DescendantContext, indexProp);

return (
<div data-index={index} ref={handleRefSet} {...rest}>
{isFunction(children) ? children({ index }) : children}
</div>
);
};

type DescendantType = Descendant<HTMLDivElement>;

0 comments on commit 2f535ab

Please sign in to comment.