This commit is contained in:
Chris Kruining 2024-10-15 16:39:24 +02:00
parent 75bd06cac3
commit 40f46eba1d
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
17 changed files with 426 additions and 150 deletions

View file

@ -2,24 +2,29 @@
display: flex;
flex-direction: column;
list-style: none;
padding-inline-start: 0;
& details {
& > summary::marker {
content: none;
color: var(--text-1) !important;
& > summary {
padding: var(--padding-s);
&::marker {
content: none;
color: var(--text-1) !important;
}
}
& span {
cursor: pointer;
&::details-content {
display: flex;
flex-direction: column;
list-style: none;
padding-inline-start: 1.25em;
}
}
& ul {
padding-inline-start: 1.25em;
}
& span {
cursor: pointer;
white-space: nowrap;
padding: var(--padding-s);
}
}

View file

@ -1,6 +1,7 @@
import { Accessor, Component, createSignal, For, JSX, Show } from "solid-js";
import { Accessor, Component, createContext, createSignal, For, JSX, Show, useContext } from "solid-js";
import css from "./filetree.module.css";
import { AiFillFile, AiFillFolder, AiFillFolderOpen } from "solid-icons/ai";
import { SelectionProvider, selectable } from "~/features/selectable";
export interface FileEntry {
name: string;
@ -38,27 +39,61 @@ export async function* walk(directory: FileSystemDirectoryHandle, filters: RegEx
}
}
export const Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>) => JSX.Element }> = (props) => {
return <ul class={css.root}>
<For each={props.entries}>{
(entry, index) => <li style={`order: ${(entry.kind === 'file' ? 200 : 100) + index()}`}>
<Show when={entry.kind === 'folder' ? entry : undefined}>{
folder => <Folder folder={folder()} children={props.children} />
}</Show>
interface TreeContextType {
open(file: File): void;
}
<Show when={entry.kind === 'file' ? entry : undefined}>{
file => <><AiFillFile />{props.children(file)}</>
}</Show>
</li>
}</For>
</ul>
const TreeContext = createContext<TreeContextType>();
export const Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>) => JSX.Element, open: TreeContextType['open'] }> = (props) => {
const [selection, setSelection] = createSignal();
// createEffect(() => {
// console.log(selection());
// });
const context = {
open: props.open,
// open(file: File) {
// console.log(`open ${file.name}`)
// },
};
return <SelectionProvider selection={setSelection}>
<TreeContext.Provider value={context}>
<div class={css.root}><_Tree entries={props.entries} children={props.children} /></div>
</TreeContext.Provider>
</SelectionProvider>;
}
const _Tree: Component<{ entries: Entry[], children: (file: Accessor<FileEntry>) => JSX.Element }> = (props) => {
const context = useContext(TreeContext);
return <For each={props.entries.sort(sort_by('kind'))}>{
entry => <>
<Show when={entry.kind === 'folder' ? entry : undefined}>{
folder => <Folder folder={folder()} children={props.children} />
}</Show>
<Show when={entry.kind === 'file' ? entry : undefined}>{
file => <span use:selectable={file()} ondblclick={() => context?.open(file().meta)}><AiFillFile /> {props.children(file)}</span>
}</Show>
</>
}</For>
}
const Folder: Component<{ folder: FolderEntry, children: (file: Accessor<FileEntry>) => JSX.Element }> = (props) => {
const [open, setOpen] = createSignal(false);
return <details open={open()} on:toggle={() => setOpen(o => !o)}>
return <details open={open()} ontoggle={() => setOpen(o => !o)}>
<summary><Show when={open()} fallback={<AiFillFolder />}><AiFillFolderOpen /></Show> {props.folder.name}</summary>
<Tree entries={props.folder.entries} children={props.children} />
<_Tree entries={props.folder.entries} children={props.children} />
</details>;
};
const sort_by = (key: string) => (objA: Record<string, any>, objB: Record<string, any>) => {
const a = objA[key];
const b = objB[key];
return Number(a < b) - Number(b < a);
};

View file

@ -1,29 +1,25 @@
import { TbLayoutSidebarLeftCollapse, TbLayoutSidebarLeftExpand } from "solid-icons/tb";
import { createMemo, createSignal, onMount, ParentComponent, Show } from "solid-js";
import { Dynamic, Portal, render } from "solid-js/web";
import { createMemo, createSignal, ParentComponent, Show } from "solid-js";
import { Dynamic } from "solid-js/web";
import css from "./sidebar.module.css";
export const Sidebar: ParentComponent<{ as?: string, open?: boolean, name?: string }> = (props) => {
const [open, setOpen] = createSignal(props.open ?? true)
const cssClass = createMemo(() => open() ? css.open : css.closed);
const [open, setOpen] = createSignal(props.open ?? true);
const name = createMemo(() => props.name ?? 'sidebar');
const toggle = () => setOpen(o => !o);
return <Dynamic component={props.as ?? 'div'} class={`${css.root} ${open() ? css.open : css.closed}`}>
<button
role="button"
onclick={() => setOpen(o => !o)}
title={`${open() ? 'close' : 'open'} ${name()}`}
>
<Show when={open()} fallback={<TbLayoutSidebarLeftExpand />}>
<TbLayoutSidebarLeftCollapse />
</Show>
</button>
let ref: Element;
return <Dynamic component={props.as ?? 'div'} class={`${css.root} ${cssClass()}`} ref={ref}>
<Portal mount={ref!} useShadow={true}>
<button onclick={() => toggle()} role="button" title={`${open() ? 'close' : 'open'} ${name()}`}>
<Show when={open()} fallback={<TbLayoutSidebarLeftExpand />}>
<TbLayoutSidebarLeftCollapse />
</Show>
</button>
<div class={css.content}>
<slot />
</div>
</Portal>
{props.children}
<div class={css.content}>
{props.children}
</div>
</Dynamic>
};

View file

@ -1,6 +1,10 @@
.root {
display: grid;
grid-template-rows: auto 1fr;
grid: auto minmax(0, 1fr) / repeat(var(--tab-count), auto);
justify-content: start;
inline-size: 100%;
block-size: 100%;
.tab {
display: contents;
@ -8,13 +12,37 @@
& > summary {
grid-row: 1 / 1;
padding: var(--padding-s) var(--padding-m);
&::marker {
content: none;
}
}
&::details-content {
grid-area: 2 / 1;
grid-area: 2 / 1 / span 1 / span var(--tab-count);
display: none;
grid: 100% / 100%;
inline-size: 100%;
block-size: 100%;
overflow: auto;
}
&[open] {
& > summary {
background-color: var(--surface-2);
}
&::details-content {
display: grid;
}
}
}
}
@property --tab-count {
syntax: '<integer>';
inherits: true;
initial-value: 0;
}

View file

@ -1,72 +1,21 @@
import { Accessor, children, Component, createContext, createEffect, createMemo, createSignal, createUniqueId, For, JSX, ParentComponent, useContext } from "solid-js";
import { createStore } from "solid-js/store";
import { Accessor, children, createContext, createMemo, createSignal, createUniqueId, For, JSX, ParentComponent, useContext } from "solid-js";
import css from "./tabs.module.css";
import { Portal } from "solid-js/web";
interface TabsContextType {
isActive(): boolean;
}
interface TabsState {
tabs: TabType[];
}
interface TabType {
id: string;
label: string;
}
const TabsContext = createContext<TabsContextType>();
export const Tabs: Component<{ children?: JSX.Element }> = (props) => {
const [state, setState] = createStore<TabsState>({ tabs: [] });
createEffect(() => {
const tabs = children(() => props.children).toArray();
console.log(tabs);
setState('tabs', tabs.map(t => ({ id: t.id, label: t.getAttribute('data-label') })))
});
const ctx: TabsContextType = {
isActive() {
return false;
}
};
return <TabsContext.Provider value={ctx}>
<header>
<For each={state.tabs}>{
tab => <button type="button" onpointerdown={() => activate(tab.id)}>{tab.label}</button>
}</For>
</header>
{props.children}
</TabsContext.Provider>
};
export const Tab: ParentComponent<{ label: string }> = (props) => {
const context = useContext(TabsContext);
return <div id={createUniqueId()} data-label={props.label}>{props.children}</div>
}
interface TabsSimpleContextType {
activate(id: string): void;
active: Accessor<string | undefined>;
isActive(id: string): Accessor<boolean>;
}
const TabsSimpleContext = createContext<TabsSimpleContextType>();
const TabsContext = createContext<TabsContextType>();
export const TabsSimple: ParentComponent = (props) => {
export const Tabs: ParentComponent = (props) => {
const [active, setActive] = createSignal<string | undefined>(undefined);
const numberOfTabs = createMemo(() => children(() => props.children).toArray().length);
return <TabsSimpleContext.Provider value={{
return <TabsContext.Provider value={{
activate(id: string) {
setActive(id);
// setState('active', id);
},
active,
@ -75,15 +24,15 @@ export const TabsSimple: ParentComponent = (props) => {
return createMemo(() => active() === id);
},
}}>
<div class={css.root}>
<div class={css.root} style={{ '--tab-count': numberOfTabs() }}>
{props.children}
</div>
</TabsSimpleContext.Provider>;
</TabsContext.Provider>;
}
export const TabSimple: ParentComponent<{ label: string }> = (props) => {
export const Tab: ParentComponent<{ label: string }> = (props) => {
const id = `tab-${createUniqueId()}`;
const context = useContext(TabsSimpleContext);
const context = useContext(TabsContext);
if (!context) {
return undefined;