feat(core): sub-page for setting panel (#11678)

**setting panel sub-page impl, with cascading pages support.**

## Usage
```tsx
// inside setting content
const island = useSubPageIsland();
const [open, setOpen] = useState(false);

if (!island) {
  return null;
}

return (
  <SubPageProvider
    island={island}
    open={open}
    onClose={() => setOpen(false)}
    backText="Back"
  />
);
```

### Preview

![CleanShot 2025-04-14 at 16.56.30.gif](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/LakojjjzZNf6ogjOVwKE/c042300d-c442-4708-a07a-54cd9f044abf.gif)
This commit is contained in:
CatsJuice
2025-04-23 07:57:22 +00:00
parent 7e48dcc467
commit af69154f1c
18 changed files with 659 additions and 252 deletions

View File

@@ -1,4 +1,5 @@
import { LiveData, useLiveData } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import {
forwardRef,
type Ref,
@@ -10,9 +11,10 @@ import { createPortal } from 'react-dom';
export const createIsland = () => {
const targetLiveData$ = new LiveData<HTMLDivElement | null>(null);
const provided$ = new LiveData<boolean>(false);
let mounted = false;
let provided = false;
return {
id: nanoid(),
Target: forwardRef(function IslandTarget(
{ ...other }: React.HTMLProps<HTMLDivElement>,
ref: Ref<HTMLDivElement>
@@ -36,16 +38,17 @@ export const createIsland = () => {
Provider: ({ children }: React.PropsWithChildren) => {
const target = useLiveData(targetLiveData$);
useEffect(() => {
if (provided === true && BUILD_CONFIG.debug) {
if (provided$.value === true && BUILD_CONFIG.debug) {
throw new Error('Island should not be provided more than once');
}
provided = true;
provided$.next(true);
return () => {
provided = false;
provided$.next(false);
};
}, []);
return target ? createPortal(children, target) : null;
},
provided$,
};
};