lots of work
This commit is contained in:
parent
552ba7f3c9
commit
75bd06cac3
19 changed files with 523 additions and 314 deletions
|
@ -1,20 +0,0 @@
|
|||
.increment {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 1em 2em;
|
||||
color: #335d92;
|
||||
background-color: rgba(68, 107, 158, 0.1);
|
||||
border-radius: 2em;
|
||||
border: 2px solid rgba(68, 107, 158, 0);
|
||||
outline: none;
|
||||
width: 200px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.increment:focus {
|
||||
border: 2px solid #335d92;
|
||||
}
|
||||
|
||||
.increment:active {
|
||||
background-color: rgba(68, 107, 158, 0.2);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import { createSignal } from "solid-js";
|
||||
import "./Counter.css";
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = createSignal(0);
|
||||
return (
|
||||
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
|
||||
Clicks: {count()}
|
||||
</button>
|
||||
);
|
||||
}
|
25
src/components/filetree.module.css
Normal file
25
src/components/filetree.module.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style: none;
|
||||
padding-inline-start: 0;
|
||||
|
||||
& details {
|
||||
& > summary::marker {
|
||||
content: none;
|
||||
color: var(--text-1) !important;
|
||||
}
|
||||
|
||||
& span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
& ul {
|
||||
padding-inline-start: 1.25em;
|
||||
}
|
||||
|
||||
& span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
64
src/components/filetree.tsx
Normal file
64
src/components/filetree.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { Accessor, Component, createSignal, For, JSX, Show } from "solid-js";
|
||||
import css from "./filetree.module.css";
|
||||
import { AiFillFile, AiFillFolder, AiFillFolderOpen } from "solid-icons/ai";
|
||||
|
||||
export interface FileEntry {
|
||||
name: string;
|
||||
kind: 'file';
|
||||
meta: File;
|
||||
}
|
||||
|
||||
export interface FolderEntry {
|
||||
name: string;
|
||||
kind: 'folder';
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
export type Entry = FileEntry | FolderEntry;
|
||||
|
||||
export const emptyFolder: FolderEntry = { name: '', kind: 'folder', entries: [] } as const;
|
||||
|
||||
export async function* walk(directory: FileSystemDirectoryHandle, filters: RegExp[] = [], depth = 0): AsyncGenerator<Entry, void, never> {
|
||||
if (depth === 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
for await (const handle of directory.values()) {
|
||||
|
||||
if (filters.some(f => f.test(handle.name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (handle.kind === 'file') {
|
||||
yield { name: handle.name, kind: 'file', meta: await handle.getFile() };
|
||||
}
|
||||
else {
|
||||
yield { name: handle.name, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<Show when={entry.kind === 'file' ? entry : undefined}>{
|
||||
file => <><AiFillFile />{props.children(file)}</>
|
||||
}</Show>
|
||||
</li>
|
||||
}</For>
|
||||
</ul>
|
||||
}
|
||||
|
||||
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)}>
|
||||
<summary><Show when={open()} fallback={<AiFillFolder />}><AiFillFolderOpen /></Show> {props.folder.name}</summary>
|
||||
<Tree entries={props.folder.entries} children={props.children} />
|
||||
</details>;
|
||||
};
|
21
src/components/sidebar.module.css
Normal file
21
src/components/sidebar.module.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
.root {
|
||||
display: grid;
|
||||
grid: auto 1fr / 100%;
|
||||
|
||||
& > button {
|
||||
inline-size: max-content;
|
||||
padding: 0;
|
||||
background-color: var(--surface-1);
|
||||
color: var(--text-1);
|
||||
border: none;
|
||||
font-size: var(--text-l);
|
||||
}
|
||||
|
||||
& > .content {
|
||||
overflow: auto clip;
|
||||
}
|
||||
|
||||
&.closed > .content {
|
||||
inline-size: 0;
|
||||
}
|
||||
}
|
29
src/components/sidebar.tsx
Normal file
29
src/components/sidebar.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
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 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 name = createMemo(() => props.name ?? 'sidebar');
|
||||
|
||||
const toggle = () => setOpen(o => !o);
|
||||
|
||||
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}
|
||||
</Dynamic>
|
||||
};
|
20
src/components/tabs.module.css
Normal file
20
src/components/tabs.module.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
.root {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
.tab {
|
||||
display: contents;
|
||||
|
||||
& > summary {
|
||||
grid-row: 1 / 1;
|
||||
|
||||
&::marker {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
&::details-content {
|
||||
grid-area: 2 / 1;
|
||||
}
|
||||
}
|
||||
}
|
97
src/components/tabs.tsx
Normal file
97
src/components/tabs.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { Accessor, children, Component, createContext, createEffect, createMemo, createSignal, createUniqueId, For, JSX, ParentComponent, useContext } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
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>();
|
||||
|
||||
export const TabsSimple: ParentComponent = (props) => {
|
||||
const [active, setActive] = createSignal<string | undefined>(undefined);
|
||||
|
||||
return <TabsSimpleContext.Provider value={{
|
||||
activate(id: string) {
|
||||
setActive(id);
|
||||
// setState('active', id);
|
||||
},
|
||||
|
||||
active,
|
||||
|
||||
isActive(id: string) {
|
||||
return createMemo(() => active() === id);
|
||||
},
|
||||
}}>
|
||||
<div class={css.root}>
|
||||
{props.children}
|
||||
</div>
|
||||
</TabsSimpleContext.Provider>;
|
||||
}
|
||||
|
||||
export const TabSimple: ParentComponent<{ label: string }> = (props) => {
|
||||
const id = `tab-${createUniqueId()}`;
|
||||
const context = useContext(TabsSimpleContext);
|
||||
|
||||
if (!context) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue