mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
UI: always prefer session storage on Tabs
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
---
|
||||
|
||||
Tabs: always prefer session storage
|
||||
@@ -74,8 +74,10 @@ id.
|
||||
|
||||
### Persistent
|
||||
|
||||
You can enable persistent by passing a `persist` property. The value will be
|
||||
stored in `localStorage`, with its id as the key.
|
||||
When `groupId` is specified, their value will be temporarily stored in `sessionStorage`.
|
||||
|
||||
To persist the value, you can specify a `persist` prop. The value will be
|
||||
stored in `localStorage`, with its group ID as storage key.
|
||||
|
||||
```mdx
|
||||
<Tabs groupId="language" items={['Javascript', 'Rust']} persist>
|
||||
@@ -84,8 +86,6 @@ stored in `localStorage`, with its id as the key.
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
> Persistent only works if you have passed an `id`.
|
||||
|
||||
### Default Value
|
||||
|
||||
Set a default value by passing `defaultIndex`.
|
||||
|
||||
@@ -180,7 +180,7 @@ Fumadocs UI does this with a grid system:
|
||||
minmax(min-content, 1fr);
|
||||
|
||||
--fd-docs-row-1: var(--fd-banner-height, 0px);
|
||||
--fd-docs-row-2: calc(var(--fd-docs-row-1) + var(--fd-header-height) 0px);
|
||||
--fd-docs-row-2: calc(var(--fd-docs-row-1) + var(--fd-header-height));
|
||||
--fd-docs-row-3: calc(var(--fd-docs-row-2) + var(--fd-toc-popover-height));
|
||||
--fd-sidebar-col: var(--fd-sidebar-width);
|
||||
--fd-page-col: calc(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
useContext,
|
||||
use,
|
||||
useEffectEvent,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
@@ -14,21 +14,7 @@ import * as Primitive from '@radix-ui/react-tabs';
|
||||
import { mergeRefs } from '@/utils/merge-refs';
|
||||
|
||||
type ChangeListener = (v: string) => void;
|
||||
const listeners = new Map<string, ChangeListener[]>();
|
||||
|
||||
function addChangeListener(id: string, listener: ChangeListener): void {
|
||||
const list = listeners.get(id) ?? [];
|
||||
list.push(listener);
|
||||
listeners.set(id, list);
|
||||
}
|
||||
|
||||
function removeChangeListener(id: string, listener: ChangeListener): void {
|
||||
const list = listeners.get(id) ?? [];
|
||||
listeners.set(
|
||||
id,
|
||||
list.filter((item) => item !== listener),
|
||||
);
|
||||
}
|
||||
const listeners = new Map<string, Set<ChangeListener>>();
|
||||
|
||||
export interface TabsProps extends ComponentProps<typeof Primitive.Tabs> {
|
||||
/**
|
||||
@@ -52,7 +38,7 @@ const TabsContext = createContext<{
|
||||
} | null>(null);
|
||||
|
||||
function useTabContext() {
|
||||
const ctx = useContext(TabsContext);
|
||||
const ctx = use(TabsContext);
|
||||
if (!ctx) throw new Error('You must wrap your component in <Tabs>');
|
||||
return ctx;
|
||||
}
|
||||
@@ -61,9 +47,6 @@ export const TabsList = Primitive.TabsList;
|
||||
|
||||
export const TabsTrigger = Primitive.TabsTrigger;
|
||||
|
||||
/**
|
||||
* @internal You better not use it
|
||||
*/
|
||||
export function Tabs({
|
||||
ref,
|
||||
groupId,
|
||||
@@ -75,27 +58,27 @@ export function Tabs({
|
||||
...props
|
||||
}: TabsProps) {
|
||||
const tabsRef = useRef<HTMLDivElement>(null);
|
||||
const valueToIdMap = useMemo(() => new Map<string, string>(), []);
|
||||
const [value, setValue] =
|
||||
_value === undefined
|
||||
? // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
|
||||
useState(defaultValue)
|
||||
: [_value, _onValueChange ?? (() => undefined)];
|
||||
|
||||
const onChange = useEffectEvent((v: string) => setValue(v));
|
||||
const valueToIdMap = useMemo(() => new Map<string, string>(), []);
|
||||
: // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
|
||||
[_value, useEffectEvent((v: string) => _onValueChange?.(v))];
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!groupId) return;
|
||||
const previous = persist
|
||||
? localStorage.getItem(groupId)
|
||||
: sessionStorage.getItem(groupId);
|
||||
let previous = sessionStorage.getItem(groupId);
|
||||
if (persist) previous ??= localStorage.getItem(groupId);
|
||||
if (previous) setValue(previous);
|
||||
|
||||
if (previous) onChange(previous);
|
||||
addChangeListener(groupId, onChange);
|
||||
const groupListeners = listeners.get(groupId) ?? new Set();
|
||||
groupListeners.add(setValue);
|
||||
listeners.set(groupId, groupListeners);
|
||||
return () => {
|
||||
removeChangeListener(groupId, onChange);
|
||||
groupListeners.delete(setValue);
|
||||
};
|
||||
}, [groupId, persist]);
|
||||
}, [groupId, persist, setValue]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
@@ -103,12 +86,12 @@ export function Tabs({
|
||||
|
||||
for (const [value, id] of valueToIdMap.entries()) {
|
||||
if (id === hash) {
|
||||
onChange(value);
|
||||
setValue(value);
|
||||
tabsRef.current?.scrollIntoView();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [valueToIdMap]);
|
||||
}, [setValue, valueToIdMap]);
|
||||
|
||||
return (
|
||||
<Primitive.Tabs
|
||||
@@ -124,23 +107,22 @@ export function Tabs({
|
||||
}
|
||||
|
||||
if (groupId) {
|
||||
listeners.get(groupId)?.forEach((item) => {
|
||||
item(v);
|
||||
});
|
||||
const groupListeners = listeners.get(groupId);
|
||||
if (groupListeners) {
|
||||
for (const listener of groupListeners) listener(v);
|
||||
}
|
||||
|
||||
sessionStorage.setItem(groupId, v);
|
||||
if (persist) localStorage.setItem(groupId, v);
|
||||
else sessionStorage.setItem(groupId, v);
|
||||
} else {
|
||||
setValue(v);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<TabsContext.Provider
|
||||
value={useMemo(() => ({ valueToIdMap }), [valueToIdMap])}
|
||||
>
|
||||
<TabsContext value={useMemo(() => ({ valueToIdMap }), [valueToIdMap])}>
|
||||
{props.children}
|
||||
</TabsContext.Provider>
|
||||
</TabsContext>
|
||||
</Primitive.Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user