mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
feat: updateAnchor option for <Tabs /> component (#1070)
* feat: option to update anchor for tab components * refactor: sort deps order * fix: use value props from child instead of index * fix: apply toValue on map key after truthy check * refactor: deconstruct children from props * refactor: use ref and context suggested by fuma-nama * fix: useMemo for valueToIdMap * style: rename ValueChangeContext to TabsContext * docs: usage for <Tabs /> updateAnchor * fix: useMemo for context value * style: format with prettier * chore: added changesets
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': minor
|
||||
---
|
||||
|
||||
added the updateAnchor option for the Tabs ui component
|
||||
@@ -80,6 +80,35 @@ You can add the hash `#tab-cpp` to your URL and reload, the C++ tab will be acti
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Additionally, the `updateAnchor` property can be set to `true` in the `Tabs` component
|
||||
to automatically update the URL hash whenever time a new tab is selected:
|
||||
|
||||
```mdx
|
||||
<Tabs items={['Javascript', 'Rust', 'C++']} updateAnchor>
|
||||
<Tab id="tab-js" value="Javascript">
|
||||
Javascript is weird
|
||||
</Tab>
|
||||
<Tab id="tab-rs" value="Rust">
|
||||
Rust is fast
|
||||
</Tab>
|
||||
<Tab id="tab-cpp" value="C++">
|
||||
`Hello World`
|
||||
</Tab>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
<Tabs items={['Javascript', 'Rust', 'C++']} updateAnchor>
|
||||
<Tab id="tab-js" value="Javascript">
|
||||
Javascript is weird
|
||||
</Tab>
|
||||
<Tab id="tab-rs" value="Rust">
|
||||
Rust is fast
|
||||
</Tab>
|
||||
<Tab id="tab-cpp" value="C++">
|
||||
`Hello World`
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Advanced
|
||||
|
||||
You can use the styled Radix UI primitive directly from exported `Primitive`.
|
||||
|
||||
@@ -52,15 +52,28 @@ export interface TabsProps extends BaseProps {
|
||||
defaultIndex?: number;
|
||||
|
||||
items?: string[];
|
||||
|
||||
/**
|
||||
* If true, updates the URL hash based on the tab's id
|
||||
*/
|
||||
updateAnchor?: boolean;
|
||||
}
|
||||
|
||||
const ValueChangeContext = createContext<(v: string) => void>(() => undefined);
|
||||
const TabsContext = createContext<{
|
||||
onValueChange: (v: string) => void;
|
||||
valueToIdMap: Map<string, string>;
|
||||
}>({
|
||||
onValueChange: () => undefined,
|
||||
valueToIdMap: new Map(),
|
||||
});
|
||||
|
||||
export function Tabs({
|
||||
groupId,
|
||||
items = [],
|
||||
persist = false,
|
||||
defaultIndex = 0,
|
||||
updateAnchor = false,
|
||||
children,
|
||||
...props
|
||||
}: TabsProps): React.ReactElement {
|
||||
const values = useMemo(() => items.map((item) => toValue(item)), [items]);
|
||||
@@ -68,6 +81,8 @@ export function Tabs({
|
||||
const valuesRef = useRef(values);
|
||||
valuesRef.current = values;
|
||||
|
||||
const valueToIdMapRef = useRef(new Map<string, string>());
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!groupId) return;
|
||||
|
||||
@@ -88,6 +103,13 @@ export function Tabs({
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(v: string) => {
|
||||
if (updateAnchor) {
|
||||
const id = valueToIdMapRef.current.get(v);
|
||||
if (id) {
|
||||
window.history.replaceState(null, '', `#${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupId) {
|
||||
listeners.get(groupId)?.forEach((item) => {
|
||||
item(v);
|
||||
@@ -99,7 +121,15 @@ export function Tabs({
|
||||
setValue(v);
|
||||
}
|
||||
},
|
||||
[groupId, persist],
|
||||
[groupId, persist, updateAnchor],
|
||||
);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
onValueChange,
|
||||
valueToIdMap: valueToIdMapRef.current,
|
||||
}),
|
||||
[onValueChange],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -116,9 +146,9 @@ export function Tabs({
|
||||
</Primitive.TabsTrigger>
|
||||
))}
|
||||
</Primitive.TabsList>
|
||||
<ValueChangeContext.Provider value={onValueChange}>
|
||||
{props.children}
|
||||
</ValueChangeContext.Provider>
|
||||
<TabsContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
</Primitive.Tabs>
|
||||
);
|
||||
}
|
||||
@@ -133,7 +163,7 @@ export function Tab({
|
||||
...props
|
||||
}: TabsContentProps): React.ReactElement {
|
||||
const v = toValue(value);
|
||||
const onValueChange = useContext(ValueChangeContext);
|
||||
const { onValueChange, valueToIdMap } = useContext(TabsContext);
|
||||
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
@@ -141,7 +171,17 @@ export function Tab({
|
||||
if (hash === props.id) {
|
||||
onValueChange(v);
|
||||
}
|
||||
}, [onValueChange, props.id, v]);
|
||||
|
||||
if (props.id) {
|
||||
valueToIdMap.set(v, props.id);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (props.id) {
|
||||
valueToIdMap.delete(v);
|
||||
}
|
||||
};
|
||||
}, [onValueChange, props.id, v, valueToIdMap]);
|
||||
|
||||
return (
|
||||
<Primitive.TabsContent
|
||||
|
||||
Reference in New Issue
Block a user