diff --git a/admin/src/components/tree/item.spec.ts b/admin/src/components/tree/item.spec.ts new file mode 100644 index 00000000..ff5684ac --- /dev/null +++ b/admin/src/components/tree/item.spec.ts @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 caixw +// +// SPDX-License-Identifier: MIT + +import { expect, test } from 'vitest'; + +import { Item, findItems } from './item'; + +test('findItems', () => { + const items: Array = [ + { 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', label: 'v234', items: [ + { type: 'item', value: 'v2341', label: 'v2341' }, + { type: 'item', value: 'v2343', label: 'v2343' }, + ] + }, + ] + }, + ] + }, + ]; + + expect(findItems(items, 'v2')).toEqual([1]); + expect(findItems(items, 'v22')).toEqual([4,0]); + expect(findItems(items, 'v2343')).toEqual([4,2,1,1]); + expect(findItems(items, 'not-exists')).toBeUndefined(); + expect(findItems(items)).toBeUndefined(); +}); diff --git a/admin/src/components/tree/item.tsx b/admin/src/components/tree/item.tsx index 9c654ab1..3ecbb8fa 100644 --- a/admin/src/components/tree/item.tsx +++ b/admin/src/components/tree/item.tsx @@ -59,3 +59,37 @@ export type Item = { */ accesskey?: string; }; + +/** +* 从 items 中查找值为 value 的项 +* @param items 被查找对象 +* @param value 查找的对象 +* @returns 如果找到了,返回 value 在 items 的索引值,如果嵌套层,则返回每一次的索引。 +*/ +export function findItems(items: Array, value?: Value): Array|undefined { + if (value === undefined) { + return; + } + + for(const [index,item] of items.entries()) { + switch (item.type) { + case 'group': + if (item.items && item.items.length > 0) { + const indexes = findItems(item.items, value); + if (indexes && indexes.length > 0) { + return [index, ...indexes]; + } + } + continue; + case 'item': + if (item.items && item.items.length > 0) { + const indexes = findItems(item.items, value); + if (indexes && indexes.length > 0) { + return [index, ...indexes]; + } + } else if (item.value === value) { + return [index]; + } + } + } +} diff --git a/admin/src/components/tree/list/demo.tsx b/admin/src/components/tree/list/demo.tsx index 2cebe743..8b6ee041 100644 --- a/admin/src/components/tree/list/demo.tsx +++ b/admin/src/components/tree/list/demo.tsx @@ -71,8 +71,8 @@ export default function() {
-

不指定 onchange

- +

不指定 onchange,但是有默认值

+ {items}
diff --git a/admin/src/components/tree/list/list.tsx b/admin/src/components/tree/list/list.tsx index 0923ba9a..64fa742b 100644 --- a/admin/src/components/tree/list/list.tsx +++ b/admin/src/components/tree/list/list.tsx @@ -2,19 +2,31 @@ // // SPDX-License-Identifier: MIT -import { A } from '@solidjs/router'; +import { A, useLocation } from '@solidjs/router'; import { createSignal, For, JSX, Match, mergeProps, Show, Switch } from 'solid-js'; import { Dynamic } from 'solid-js/web'; import { Divider } from '@/components/divider'; import type { Props as ContainerProps } from '@/components/tree/container'; -import type { Item, Value } from '@/components/tree/item'; +import { findItems, type Item, type Value } from '@/components/tree/item'; export interface Props extends ContainerProps { /** - * 可点击的元素是否以 A 作为标签名 + * 设置选中项的初始值 + * + * NOTE: 该值为非响应属性。 + */ + selected?: Value; + + /** + * 可点击的元素是否以 {@link A} 作为标签名 * * 如果为 true,那为 {@link Item#value} 将作为链接的值。 + * + * NOTE: 如果此值为 true,且 {@link Props#selected} 为 undefined, + * 则会尝试从地址中获取相应的值。 + * + * NOTE: 该值为非响应属性。 */ anchor?: boolean; } @@ -23,23 +35,29 @@ const defaultProps: Readonly> = { selectedClass: 'selected' }; +/** + * 列表组件 + */ export default function (props: Props): JSX.Element { props = mergeProps(defaultProps, props); - const [selected, setSelected] = createSignal(); + const [selected, setSelected] = createSignal(props.selected ?? (props.anchor ? useLocation().pathname : undefined)); + const selectedIndexes = findItems(props.children, selected()); - const Items = (p: { items: Array, indent: number }): JSX.Element => { + const All = (p: { items: Array, indent: number, selectedIndex: number }): JSX.Element => { return - {(item) => ( + {(item, index) => ( - +

{(item as any).label}

+
- +
)} @@ -47,23 +65,23 @@ export default function (props: Props): JSX.Element { }; // 渲染 type==item 的元素 - const I = (p: { item: Item, indent: number }) => { + const Items = (p: { item: Item, indent: number, selectedIndex: number, isOpen: boolean }) => { if (p.item.type !== 'item') { throw 'item.type 只能是 item'; } - const [open, setOpen] = createSignal(false); + const [open] = createSignal(p.isOpen); return 0}> -
setOpen(!open())} open={open()}> +
{p.item.label} { open() ?'keyboard_arrow_up' : 'keyboard_arrow_down' } - } indent={p.indent+1} /> + } indent={p.indent+1} selectedIndex={p.selectedIndex+1} />
@@ -73,6 +91,13 @@ export default function (props: Props): JSX.Element { activeClass={props.selectedClass} href={props.anchor ? p.item.value?.toString() ?? '' : ''} accessKey={p.item.accesskey} + style={{ 'padding-left': `calc(${p.indent} * var(--item-space))` }} + classList={{ + 'item': true, + + // anchor 的类型定义在 activeClass 属性 + [props.anchor ? '' : props.selectedClass!]: !!props.selectedClass && selected() === p.item.value + }} onClick={()=>{ if (p.item.type !== 'item') { throw 'p.item.type 必须为 item'; } @@ -84,34 +109,18 @@ export default function (props: Props): JSX.Element { } setSelected(p.item.value); - }} style={{ 'padding-left': `calc(${p.indent} * var(--item-space))` }} classList={{ - 'item': true, - - // anchor 的类型定义在 activeClass 属性 - [props.anchor ? '' : props.selectedClass!]: !!props.selectedClass && selected() === p.item.value - }}> + }} + > {p.item.label} ; }; - // 渲染 type==group 的元素 - const Group = (p: { item: Item, indent: number }): JSX.Element => { - if (p.item.type !== 'group') { - throw 'item.type 只能是 group'; - } - - return <> -

{p.item.label}

- - ; - }; - return - + ; } diff --git a/admin/src/core/theme/breakpoints.spec.ts b/admin/src/core/theme/breakpoints.spec.ts index 8ae8840a..41b8cbf2 100644 --- a/admin/src/core/theme/breakpoints.spec.ts +++ b/admin/src/core/theme/breakpoints.spec.ts @@ -16,6 +16,6 @@ test('Breakpoints.compare', () => { }); test('breakpointsMedia', () => { - expect(breakpointsMedia.xs).toEqual(`(width >= ${breakpoints.xs}px)`); + expect(breakpointsMedia.xs).toEqual(`(width >= ${breakpoints.xs})`); expect(breakpointsMedia.lg).toEqual('(width >= 1024px)'); });