started introduction of splitting into tabs
This commit is contained in:
parent
1472bd8116
commit
b03f80f34f
10 changed files with 255 additions and 159 deletions
|
@ -10,21 +10,22 @@ export interface FileEntry {
|
|||
name: string;
|
||||
id: string;
|
||||
kind: 'file';
|
||||
meta: File;
|
||||
handle: FileSystemFileHandle;
|
||||
directory: FileSystemDirectoryHandle;
|
||||
meta: File;
|
||||
}
|
||||
|
||||
export interface FolderEntry {
|
||||
name: string;
|
||||
id: string;
|
||||
kind: 'folder';
|
||||
handle: FileSystemDirectoryHandle;
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
export type Entry = FileEntry | FolderEntry;
|
||||
|
||||
export const emptyFolder: FolderEntry = { name: '', id: '', kind: 'folder', entries: [] } as const;
|
||||
export const emptyFolder: FolderEntry = { name: '', id: '', kind: 'folder', entries: [], handle: undefined as unknown as FileSystemDirectoryHandle } as const;
|
||||
|
||||
export async function* walk(directory: FileSystemDirectoryHandle, filters: RegExp[] = [], depth = 0): AsyncGenerator<Entry, void, never> {
|
||||
if (depth === 10) {
|
||||
|
@ -39,10 +40,10 @@ export async function* walk(directory: FileSystemDirectoryHandle, filters: RegEx
|
|||
const id = await handle.getUniqueId();
|
||||
|
||||
if (handle.kind === 'file') {
|
||||
yield { name: handle.name, id, kind: 'file', meta: await handle.getFile(), handle, directory };
|
||||
yield { name: handle.name, id, handle, kind: 'file', meta: await handle.getFile(), directory };
|
||||
}
|
||||
else {
|
||||
yield { name: handle.name, id, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) };
|
||||
yield { name: handle.name, id, handle, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ interface TreeContextType {
|
|||
|
||||
const TreeContext = createContext<TreeContextType>();
|
||||
|
||||
export const Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>) => JSX.Element, open?: TreeContextType['open'] }> = (props) => {
|
||||
export const Tree: Component<{ entries: Entry[], children: readonly [(folder: Accessor<FolderEntry>) => JSX.Element, (file: Accessor<FileEntry>) => JSX.Element], open?: TreeContextType['open'] }> = (props) => {
|
||||
const [selection, setSelection] = createSignal<object[]>([]);
|
||||
|
||||
const context = {
|
||||
|
@ -67,7 +68,7 @@ export const Tree: Component<{ entries: Entry[], children: (file: Accessor<FileE
|
|||
</SelectionProvider>;
|
||||
}
|
||||
|
||||
const _Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>) => JSX.Element }> = (props) => {
|
||||
const _Tree: Component<{ entries: Entry[], children: readonly [(folder: Accessor<FolderEntry>) => JSX.Element, (file: Accessor<FileEntry>) => JSX.Element] }> = (props) => {
|
||||
const context = useContext(TreeContext);
|
||||
|
||||
return <For each={props.entries.sort(sort_by('kind'))}>{
|
||||
|
@ -77,17 +78,17 @@ const _Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>)
|
|||
}</Show>
|
||||
|
||||
<Show when={entry.kind === 'file' ? entry : undefined}>{
|
||||
file => <span use:selectable={{ value: file() }} ondblclick={() => context?.open(file().meta)}><AiFillFile /> {props.children(file)}</span>
|
||||
file => <span use:selectable={{ value: file() }} ondblclick={() => context?.open(file().meta)}><AiFillFile /> {props.children[1](file)}</span>
|
||||
}</Show>
|
||||
</>
|
||||
}</For>
|
||||
}
|
||||
|
||||
const Folder: Component<{ folder: FolderEntry, children: (file: Accessor<FileEntry>) => JSX.Element }> = (props) => {
|
||||
const Folder: Component<{ folder: FolderEntry, children: readonly [(folder: Accessor<FolderEntry>) => JSX.Element, (file: Accessor<FileEntry>) => JSX.Element] }> = (props) => {
|
||||
const [open, setOpen] = createSignal(true);
|
||||
|
||||
return <details open={open()} ontoggle={() => debounce(() => setOpen(o => !o), 1)}>
|
||||
<summary><Show when={open()} fallback={<AiFillFolder />}><AiFillFolderOpen /></Show> {props.folder.name}</summary>
|
||||
<summary><Show when={open()} fallback={<AiFillFolder />}><AiFillFolderOpen /></Show> {props.children[0](() => props.folder)}</summary>
|
||||
<_Tree entries={props.folder.entries} children={props.children} />
|
||||
</details>;
|
||||
};
|
||||
|
|
|
@ -1,13 +1,41 @@
|
|||
.root {
|
||||
.tabs {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid: auto minmax(0, 1fr) / repeat(var(--tab-count), auto);
|
||||
grid: auto minmax(0, 1fr) / 100%;
|
||||
justify-content: start;
|
||||
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
|
||||
& > header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
border-block-end: 1px solid var(--surface-5);
|
||||
|
||||
& > button {
|
||||
background-color: var(--surface-1);
|
||||
color: var(--text-2);
|
||||
padding: var(--padding-m) var(--padding-l);
|
||||
border: none;
|
||||
|
||||
&.active {
|
||||
background-color: var(--surface-3);
|
||||
color: var(--text-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: contents;
|
||||
position: absolute;
|
||||
grid-area: 2 / 1 / span 1 / span 1;
|
||||
inline-size: 100%;
|
||||
block-size: 100%;
|
||||
|
||||
&:not(.active) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > summary {
|
||||
grid-row: 1 / 1;
|
||||
|
|
|
@ -1,46 +1,83 @@
|
|||
import { Accessor, children, createContext, createMemo, createSignal, createUniqueId, For, JSX, ParentComponent, useContext } from "solid-js";
|
||||
import { Accessor, children, createContext, createEffect, createMemo, createRenderEffect, createSignal, createUniqueId, For, JSX, onMount, ParentComponent, Show, useContext } from "solid-js";
|
||||
import css from "./tabs.module.css";
|
||||
|
||||
interface TabsContextType {
|
||||
activate(id: string): void;
|
||||
active: Accessor<string | undefined>;
|
||||
isActive(id: string): Accessor<boolean>;
|
||||
register(id: string, label: string): Accessor<boolean>;
|
||||
}
|
||||
|
||||
const TabsContext = createContext<TabsContextType>();
|
||||
|
||||
const useTabs = () => {
|
||||
const context = useContext(TabsContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('<Tab /> is used outside of a <Tabs />')
|
||||
}
|
||||
|
||||
return context!;
|
||||
}
|
||||
|
||||
export const Tabs: ParentComponent = (props) => {
|
||||
const [active, setActive] = createSignal<string | undefined>(undefined);
|
||||
const numberOfTabs = createMemo(() => children(() => props.children).toArray().length);
|
||||
const [tabs, setTabs] = createSignal<{ id: string, label: string }[]>([]);
|
||||
// const resolved = children(() => props.children);
|
||||
// const resolvedArray = createMemo(() => resolved.toArray());
|
||||
// const tabs = createMemo(() => resolvedArray().map(t => ({ id: t.id, label: t.dataset?.label ?? '' })));
|
||||
|
||||
return <TabsContext.Provider value={{
|
||||
activate(id: string) {
|
||||
setActive(id);
|
||||
},
|
||||
// createEffect(() => {
|
||||
// for (const t of resolvedArray()) {
|
||||
// console.log(t);
|
||||
// }
|
||||
// });
|
||||
|
||||
active,
|
||||
createEffect(() => {
|
||||
setActive(tabs().at(-1)?.id);
|
||||
});
|
||||
|
||||
// createRenderEffect(() => {
|
||||
// if (isServer) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (const t of resolvedArray().filter(t => t instanceof HTMLElement)) {
|
||||
// if (active() === t.id) {
|
||||
// t.classList.add(css.active);
|
||||
// } else {
|
||||
// t.classList.remove(css.active);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
const ctx = {
|
||||
register(id: string, label: string) {
|
||||
setTabs(tabs => [...tabs, { id, label }]);
|
||||
|
||||
isActive(id: string) {
|
||||
return createMemo(() => active() === id);
|
||||
},
|
||||
}}>
|
||||
<div class={css.root} style={{ '--tab-count': numberOfTabs() }}>
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
console.log(tabs());
|
||||
});
|
||||
|
||||
return <TabsContext.Provider value={ctx}>
|
||||
<div class={css.tabs}>
|
||||
<header>
|
||||
<For each={tabs()}>{
|
||||
tab => <button onpointerdown={() => setActive(tab.id)} classList={{ [css.active]: active() === tab.id }}>{tab.label}</button>
|
||||
}</For>
|
||||
</header>
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
</TabsContext.Provider>;
|
||||
}
|
||||
|
||||
export const Tab: ParentComponent<{ label: string }> = (props) => {
|
||||
const id = `tab-${createUniqueId()}`;
|
||||
const context = useContext(TabsContext);
|
||||
export const Tab: ParentComponent<{ id: string, label: string }> = (props) => {
|
||||
const context = useTabs();
|
||||
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
const isActive = context.register(props.id, props.label);
|
||||
const resolved = children(() => props.children);
|
||||
|
||||
return <details class={css.tab} id={id} open={context.active() === id} ontoggle={(e: ToggleEvent) => e.newState === 'open' && context.activate(id)}>
|
||||
<summary>{props.label}</summary>
|
||||
|
||||
{props.children}
|
||||
</details>
|
||||
return <Show when={isActive()}>{resolved()}</Show>;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue