Skip to content

Commit

Permalink
feat(admin/components/tree/menu): 添加新组件 menu
Browse files Browse the repository at this point in the history
caixw committed Jul 29, 2024

Verified

This commit was signed with the committer’s verified signature.
caixw caixw
1 parent 1d13bbe commit 2d8cff6
Showing 14 changed files with 257 additions and 18 deletions.
2 changes: 1 addition & 1 deletion admin/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -10,6 +10,6 @@ export * from './drawer';
export * from './dropdown';
export * from './error';
export * from './form';
export * from './list';
export * from './notify';
export * from './tree';

3 changes: 3 additions & 0 deletions admin/src/components/tree/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# tree

当前目录提供了一些树形结构的组件
9 changes: 9 additions & 0 deletions admin/src/components/tree/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2024 caixw
//
// SPDX-License-Identifier: MIT

export type { Item } from './item';

export * from './list';
export * from './menu';

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@
//
// SPDX-License-Identifier: MIT

import { paletteSelector } from '@/components/base/demo';
import { createSignal } from 'solid-js';
import { Item } from './item';

import { paletteSelector } from '@/components/base/demo';
import { Item } from '@/components/tree/item';
import { default as List } from './list';

export default function() {
@@ -21,6 +22,7 @@ export default function() {
{type: 'item', value: 'v233', label: 'v233'},
{type: 'item', value: 'v234', label: 'v234', items: [
{type: 'item', value: 'v2341', label: 'v2341'},
{type: 'divider'},
{type: 'item', value: 'v2342', label: 'v2342'},
{type: 'item', value: 'v2343', label: 'v2343'},
]},
Original file line number Diff line number Diff line change
@@ -2,8 +2,6 @@
//
// SPDX-License-Identifier: MIT

export type { Item } from './item';

export { default as List } from './list';
export type { Props as ListProps } from './list';

Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import { Dynamic } from 'solid-js/web';

import { BaseProps, renderElementProp } from '@/components/base';
import { Divider } from '@/components/divider';
import type { Item, Value } from './item';
import type { Item, Value } from '@/components/tree/item';

export interface Props extends BaseProps {
/**
@@ -59,14 +59,14 @@ export default function (props: Props): JSX.Element {

return <Switch>
<Match when={p.item.items && p.item.items.length > 0}>
<details class="items" onToggle={()=>setOpen(!open())} open={open()}>
<summary style={{ 'margin-left': `${p.indent}` + 'px' }} class="item">
<details onToggle={()=>setOpen(!open())} open={open()}>
<summary style={{ 'padding-left': `calc(${p.indent} * var(--item-space))` }} class="item">
{renderElementProp(p.item.label)}
<span class="tail material-symbols-outlined">{ open() ?'keyboard_arrow_up' : 'keyboard_arrow_down' }</span>
</summary>
<Show when={p.item.items}>
<menu>
<Items items={p.item.items as Array<Item>} indent={p.indent+10} />
<Items items={p.item.items as Array<Item>} indent={p.indent+1} />
</menu>
</Show>
</details>
@@ -80,7 +80,7 @@ export default function (props: Props): JSX.Element {

setSelected(p.item.value);
props.onChange(p.item.value, old);
}} style={{ 'margin-left': `${p.indent}` + 'px' }} classList={{
}} style={{ 'padding-left': `calc(${p.indent} * var(--item-space))` }} classList={{
'item': true,
'selected': selected() === p.item.value
}}>
@@ -106,6 +106,6 @@ export default function (props: Props): JSX.Element {
'c--list': true,
[`palette--${props.palette}`]: !!props.palette
}}>
<Items items={props.children} indent={0} />
<Items items={props.children} indent={1} />
</menu>;
}
Original file line number Diff line number Diff line change
@@ -6,17 +6,31 @@

@layer components {
.c--list {
@apply text-[var(--fg)] bg-[var(--bg)] flex flex-col p-2 gap-1 min-w-[200px] rounded-md;
counter-reset: indent;
/* 组件中引用此值 */
--item-space: theme('spacing.2');

@apply text-[var(--fg)] bg-[var(--bg)] flex flex-col gap-1 min-w-[200px] rounded-md;


>:first-child {
@apply rounded-t-md;
}

:last-child {
@apply rounded-b-md;
}

.item {
@apply p-2 hover:bg-[var(--bg-low)] cursor-pointer rounded-md;
@apply hover:bg-[var(--bg-low)] cursor-pointer;
padding: var(--item-space);
}

/* 带子项的 */
details {
summary {
@apply flex cursor-pointer mb-1 list-none px-2;
@apply flex cursor-pointer list-none;
padding-left: var(--item-space);
padding-right: var(--item-space);

.tail {
margin-left: auto;
@@ -35,7 +49,9 @@
}

.group {
@apply text-[var(--fg-low)] cursor-default px-2;
@apply text-[var(--fg-low)] cursor-default;
padding-left: var(--item-space);
padding-right: var(--item-space);
}

.item.selected {
44 changes: 44 additions & 0 deletions admin/src/components/tree/menu/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2024 caixw
//
// SPDX-License-Identifier: MIT

import { createSignal } from 'solid-js';

import { paletteSelector } from '@/components/base/demo';
import { Item } from '@/components/tree/item';
import { default as Menu } from './menu';

export default function() {
const [paletteS,palette] = paletteSelector('primary');
const items: Array<Item> = [
{type: 'item', value: 'v1', label: 'v1'},
{type: 'item', value: 'v2', label: 'v2'},
{type: 'item', value: 'v3', label: 'v3'},
{type: 'divider'},
{type: 'group', label: 'group', items: [
{type: 'item', value: 'v22', label: 'v22'},
{type: 'divider'},
{type: 'item', value: 'v23', label: 'v23', items:[
{type: 'item', value: 'v233', label: 'v233'},
{type: 'item', value: 'v234', label: 'v234', items: [
{type: 'item', value: 'v2341', label: 'v2341'},
{type: 'item', value: 'v2342', label: 'v2342'},
{type: 'divider'},
{type: 'item', value: 'v2343', label: 'v2343'},
]},
]},
]},
];

const [selected, setSelected] = createSignal<string>();
return <div class="flex flex-col gap-2">
{paletteS}

<div class="w-80 mt-4">
<Menu palette={palette()} onChange={(v,old)=>setSelected(v.toString()+' '+old?.toString())}>
{items}
</Menu>
<div>{ selected() }</div>
</div>
</div>;
}
7 changes: 7 additions & 0 deletions admin/src/components/tree/menu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2024 caixw
//
// SPDX-License-Identifier: MIT

export { default as Menu } from './menu';
export type { Props as MenuProps } from './menu';

110 changes: 110 additions & 0 deletions admin/src/components/tree/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: 2024 caixw
//
// SPDX-License-Identifier: MIT

import { createSignal, For, JSX, Match, mergeProps, Show, Switch } from 'solid-js';

import { BaseProps, renderElementProp } from '@/components/base';
import { Divider } from '@/components/divider';
import type { Item, Value } from '@/components/tree/item';

export interface Props extends BaseProps {
/**
* 子项
*/
children: Array<Item>;

/**
* 当选择项发生变化时触发的事件
*/
onChange?: { (selected: Value, old?: Value): void };

/**
* 右侧展开子菜单的图标,默认为 chevron_right
*/
expandIcon?: string
}

const defaultProps: Readonly<Partial<Props>> = {
expandIcon: 'chevron_right'
};

export default function (props: Props): JSX.Element {
props = mergeProps(defaultProps, props);

const [selected, setSelected] = createSignal<Value>();

const Items = (p: { items: Array<Item> }): JSX.Element => {
return <For each={p.items}>
{(item) => (
<Switch>
<Match when={item.type === 'divider'}>
<Divider />
</Match>
<Match when={item.type === 'group'}>
<Group item={item} />
</Match>
<Match when={item.type === 'item'}>
<I item={item} />
</Match>
</Switch>
)}
</For>;
};

// 渲染 type==item 的元素
const I = (p: { item: Item }) => {
if (p.item.type !== 'item') {
throw 'item.type 只能是 item';
}

return <Switch>
<Match when={p.item.items && p.item.items.length > 0}>
<li class="item">
{renderElementProp(p.item.label)}
<span class="tail material-symbols-outlined">{ props.expandIcon }</span>
<Show when={p.item.items}>
<menu class="c--menu hidden">
<Items items={p.item.items as Array<Item>} />
</menu>
</Show>
</li>
</Match>
<Match when={!p.item.items}>
<li onClick={()=>{
if (p.item.type !== 'item') { throw 'p.item.type 必须为 item'; }

const old = selected();
if (!props.onChange || old === p.item.value) { return; }

setSelected(p.item.value);
props.onChange(p.item.value, old);
}} classList={{
'item': true,
'selected': selected() === p.item.value
}}>
{renderElementProp(p.item.label)}
</li>
</Match>
</Switch>;
};

// 渲染 type==group 的元素
const Group = (p: { item: Item }): JSX.Element => {
if (p.item.type !== 'group') {
throw 'item.type 只能是 group';
}

return <>
<p class="group">{renderElementProp(p.item.label)}</p>
<Items items={p.item.items} />
</>;
};

return <menu role="menu" classList={{
'c--menu': true,
[`palette--${props.palette}`]: !!props.palette
}}>
<Items items={props.children} />
</menu>;
}
45 changes: 45 additions & 0 deletions admin/src/components/tree/menu/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2024 caixw
*
* SPDX-License-Identifier: MIT
*/

@layer components {
.c--menu {
@apply text-[var(--fg)] bg-[var(--bg)] flex flex-col gap-y-1 min-w-[200px] rounded-md;

:first-child {
@apply rounded-t-md;
}

:last-child {
@apply rounded-b-md;
}

.item {
@apply p-2 hover:bg-[var(--bg-low)] cursor-pointer relative flex;

.tail {
margin-left: auto
}

>.c--menu {
@apply absolute left-[100%] top-0;
}
}

.item:hover {
>.c--menu {
display: flex;
}
}

.group {
@apply text-[var(--fg-low)] cursor-default px-2;
}

.item.selected {
@apply bg-[var(--bg-high)] text-[var(--fg-high)];
}
}
}
5 changes: 4 additions & 1 deletion admin/src/demo.tsx
Original file line number Diff line number Diff line change
@@ -12,8 +12,11 @@ const maps: Array<[string, ReturnType<typeof lazy>]> = [
['/button', lazy(()=>import('@/components/button/demo'))],
['/dropdown', lazy(()=>import('@/components/dropdown/demo'))],
['/badge', lazy(()=>import('@/components/badge/demo'))],
['/list', lazy(()=>import('@/components/list/demo'))],
['/divider', lazy(()=>import('@/components/divider/demo'))],

['/tree-list', lazy(()=>import('@/components/tree/list/demo'))],
['/tree-menu', lazy(()=>import('@/components/tree/menu/demo'))],

['/form', lazy(()=>import('@/components/form/demo'))],
['/form-textfield', lazy(()=>import('@/components/form/textfield/demo'))],
['/form-textarea', lazy(()=>import('@/components/form/textarea/demo'))],
4 changes: 3 additions & 1 deletion admin/src/style.css
Original file line number Diff line number Diff line change
@@ -14,9 +14,11 @@
@import './components/drawer/style.css';
@import './components/dropdown/style.css';
@import './components/error/style.css';
@import './components/list/style.css';
@import './components/notify/style.css';

@import './components/tree/list/style.css';
@import './components/tree/menu/style.css';

@import './components/form/style.css';
@import './components/form/checkbox/style.css';
@import './components/form/choice/style.css';

0 comments on commit 2d8cff6

Please sign in to comment.