From ca7e0c142537732c65c08993ede78aaa70d38fd3 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Wed, 23 Jul 2025 16:55:18 +0700 Subject: [PATCH 01/11] wip --- src/runtime/components/Tree.vue | 155 ++++++++++++++++++++------------ src/theme/tree.ts | 18 ++-- 2 files changed, 109 insertions(+), 64 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index 9677f59b90..021e765a92 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -25,11 +25,11 @@ export type TreeItem = { onToggle?(e: Event): void onSelect?(e?: Event): void class?: any - ui?: Pick + ui?: Pick [key: string]: any } -export interface TreeProps = 'value', M extends boolean = false> extends Pick, 'expanded' | 'defaultExpanded' | 'selectionBehavior' | 'propagateSelect' | 'disabled' | 'bubbleSelect'> { +export interface TreeProps = 'value', M extends boolean = false> extends Pick, 'expanded' | 'defaultExpanded' | 'selectionBehavior' | 'propagateSelect' | 'disabled' | 'bubbleSelect'> { /** * The element or component this component should render as. * @defaultValue 'ul' @@ -52,7 +52,7 @@ export interface TreeProps + labelKey?: keyof T /** * The icon displayed on the right side of a parent node. * @defaultValue appConfig.ui.icons.chevronDown @@ -71,7 +71,7 @@ export interface TreeProps /** The value of the Tree when initially rendered. Use when you do not need to control the state of the Tree. */ @@ -82,13 +82,13 @@ export interface TreeProps | undefined, M extends boolean> = Omit & GetModelValueEmits +export type TreeEmits | undefined, M extends boolean> = Omit & GetModelValueEmits -type SlotProps = (props: { item: T, index: number, level: number, expanded: boolean, selected: boolean }) => any +type SlotProps = (props: { item: NestedItem, index: number, level: number, expanded: boolean, selected: boolean }) => any export type TreeSlots< - A extends TreeItem[] = TreeItem[], - T extends NestedItem = NestedItem + A extends TreeItem = TreeItem, + T extends NestedItem = NestedItem > = { 'item': SlotProps 'item-leading': SlotProps @@ -98,10 +98,10 @@ export type TreeSlots< - - + + diff --git a/src/theme/tree.ts b/src/theme/tree.ts index 2a07534cbb..d4b4a358e5 100644 --- a/src/theme/tree.ts +++ b/src/theme/tree.ts @@ -4,8 +4,7 @@ export default (options: Required) => ({ slots: { root: 'relative isolate', item: '', - listWithChildren: 'ms-4.5 border-s border-default', - itemWithChildren: 'ps-1.5 -ms-px', + itemWithChildren: 'ps-[calc(var(--level)*calc(var(--indent)+0.25em))]', link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', linkLabel: 'truncate', @@ -25,27 +24,32 @@ export default (options: Required) => ({ xs: { link: 'px-2 py-1 text-xs gap-1', linkLeadingIcon: 'size-4', - linkTrailingIcon: 'size-4' + linkTrailingIcon: 'size-4', + lineOffset: '6px' }, sm: { link: 'px-2.5 py-1.5 text-xs gap-1.5', linkLeadingIcon: 'size-4', - linkTrailingIcon: 'size-4' + linkTrailingIcon: 'size-4', + lineOffset: '6px' }, md: { link: 'px-2.5 py-1.5 text-sm gap-1.5', linkLeadingIcon: 'size-5', - linkTrailingIcon: 'size-5' + linkTrailingIcon: 'size-5', + lineOffset: '8px' }, lg: { link: 'px-3 py-2 text-sm gap-2', linkLeadingIcon: 'size-5', - linkTrailingIcon: 'size-5' + linkTrailingIcon: 'size-5', + lineOffset: '8px' }, xl: { link: 'px-3 py-2 text-base gap-2', linkLeadingIcon: 'size-6', - linkTrailingIcon: 'size-6' + linkTrailingIcon: 'size-6', + lineOffset: '10px' } }, selected: { From 82f48ba93f6d5f47f48700028e0a7a498f6676c0 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 24 Jul 2025 00:44:46 +0700 Subject: [PATCH 02/11] fix(Tree): use flattenItems instead recursive component --- src/runtime/components/Tree.vue | 6 ++---- src/theme/tree.ts | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index 021e765a92..eb47f4982a 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -155,8 +155,6 @@ const indent = computed(() => { } }) -const lineOffset = computed(() => ui.value.lineOffset || '8px') - const defaultExpanded = computed(() => props.defaultExpanded ?? props.items?.flatMap(item => getDefaultOpenedItems(item as NestedItem)) ) @@ -180,8 +178,8 @@ const defaultExpanded = computed(() => :class="item.level > 0 ? [ui.itemWithChildren({ class: [props.ui?.itemWithChildren, item.value.ui?.itemWithChildren] }), 'tree--indent'] : ui.item({ class: [props.ui?.item, item.value.ui?.item] })" :style="{ '--level': item.level - 1, - '--indent': indent, - '--line-offset': lineOffset + '--line-offset': ui.lineOffset(), + '--indent': indent }" > ) => ({ itemWithChildren: 'ps-[calc(var(--level)*calc(var(--indent)+0.25em))]', link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', + lineOffset: '', linkLabel: 'truncate', linkTrailing: 'ms-auto inline-flex gap-1.5 items-center', linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180' From 4f08d62101bcd065d5f0f12e853eab9565d3aee2 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 24 Jul 2025 00:54:50 +0700 Subject: [PATCH 03/11] up --- src/runtime/components/Tree.vue | 17 +---------------- src/theme/tree.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index eb47f4982a..48f131b2e5 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -140,21 +140,6 @@ function getDefaultOpenedItems(item: T): string[] { return [currentItem, ...childItems].filter(Boolean) as string[] } -const indent = computed(() => { - switch (props.size) { - case 'xl': - case 'lg': - return '24px' - case 'md': - case 'sm': - return '20px' - case 'xs': - return '16px' - default: - return '20px' - } -}) - const defaultExpanded = computed(() => props.defaultExpanded ?? props.items?.flatMap(item => getDefaultOpenedItems(item as NestedItem)) ) @@ -179,7 +164,7 @@ const defaultExpanded = computed(() => :style="{ '--level': item.level - 1, '--line-offset': ui.lineOffset(), - '--indent': indent + '--indent': ui.indent() }" > ) => ({ link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', lineOffset: '', + indent: '', linkLabel: 'truncate', linkTrailing: 'ms-auto inline-flex gap-1.5 items-center', linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180' @@ -26,31 +27,36 @@ export default (options: Required) => ({ link: 'px-2 py-1 text-xs gap-1', linkLeadingIcon: 'size-4', linkTrailingIcon: 'size-4', - lineOffset: '6px' + lineOffset: '6px', + indent: '16px' }, sm: { link: 'px-2.5 py-1.5 text-xs gap-1.5', linkLeadingIcon: 'size-4', linkTrailingIcon: 'size-4', - lineOffset: '6px' + lineOffset: '6px', + indent: '20px' }, md: { link: 'px-2.5 py-1.5 text-sm gap-1.5', linkLeadingIcon: 'size-5', linkTrailingIcon: 'size-5', - lineOffset: '8px' + lineOffset: '8px', + indent: '20px' }, lg: { link: 'px-3 py-2 text-sm gap-2', linkLeadingIcon: 'size-5', linkTrailingIcon: 'size-5', - lineOffset: '8px' + lineOffset: '8px', + indent: '24px' }, xl: { link: 'px-3 py-2 text-base gap-2', linkLeadingIcon: 'size-6', linkTrailingIcon: 'size-6', - lineOffset: '10px' + lineOffset: '10px', + indent: '24px' } }, selected: { From 5a12dd70a6b17ddc5b1f3bfe1592dc621d111175 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 24 Jul 2025 01:07:42 +0700 Subject: [PATCH 04/11] up --- src/runtime/components/Tree.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index 48f131b2e5..d5f31b18aa 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -225,8 +225,8 @@ const defaultExpanded = computed(() => to right, transparent, transparent calc(50% - 0.5px), - #e2e8f0 calc(50% - 0.5px), - #e2e8f0 calc(50% + 0.5px), + var(--ui-border) calc(50% - 0.5px), + var(--ui-border) calc(50% + 0.5px), transparent calc(50% + 0.5px) ); From 1fd67ff81969de35d89585c09aaed2a9ca788e06 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 24 Jul 2025 01:59:37 +0700 Subject: [PATCH 05/11] up --- src/runtime/components/Tree.vue | 10 +++------- src/theme/tree.ts | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index d5f31b18aa..fd31fafd21 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -159,8 +159,8 @@ const defaultExpanded = computed(() =>
  • -.tree-item { - position: relative; -} - -.tree-item::before { +.tree--item::before { content: ''; position: absolute; top: 0; diff --git a/src/theme/tree.ts b/src/theme/tree.ts index 64ee4af73e..4008145e48 100644 --- a/src/theme/tree.ts +++ b/src/theme/tree.ts @@ -3,8 +3,8 @@ import type { ModuleOptions } from '../module' export default (options: Required) => ({ slots: { root: 'relative isolate', - item: '', - itemWithChildren: 'ps-[calc(var(--level)*calc(var(--indent)+0.25em))]', + item: 'relative', + itemWithChildren: 'relative ps-[calc(var(--level)*calc(var(--indent)+0.25em))]', link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', lineOffset: '', From 40f287cb77aa588acc653a37aae2ab30610dee87 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 24 Jul 2025 21:26:17 +0700 Subject: [PATCH 06/11] refactor(Tree): move line coonector css to tailwind --- src/runtime/components/Tree.vue | 26 +------------------------- src/theme/tree.ts | 1 + 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index fd31fafd21..fcd72071cd 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -159,8 +159,7 @@ const defaultExpanded = computed(() =>
  • -.tree--item::before { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: var(--line-offset); - pointer-events: none; - width: calc(var(--level) * (var(--indent) + 0.25em)); - background-image: repeating-linear-gradient( - to right, - transparent, - transparent calc(50% - 0.5px), - var(--ui-border) calc(50% - 0.5px), - var(--ui-border) calc(50% + 0.5px), - transparent calc(50% + 0.5px) - ); - - background-size: calc(var(--indent) + 0.25em) 100%; - -} - diff --git a/src/theme/tree.ts b/src/theme/tree.ts index 4008145e48..c9243f6454 100644 --- a/src/theme/tree.ts +++ b/src/theme/tree.ts @@ -7,6 +7,7 @@ export default (options: Required) => ({ itemWithChildren: 'relative ps-[calc(var(--level)*calc(var(--indent)+0.25em))]', link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', + connector: 'relative before:absolute before:content-[\'\'] before:top-0 before:bottom-0 before:left-[var(--line-offset)] before:pointer-events-none before:w-[calc(var(--level)*(var(--indent)+0.25em))] before:bg-[repeating-linear-gradient(to_right,transparent,transparent_calc(50%-0.5px),var(--ui-border)_calc(50%-0.5px),var(--ui-border)_calc(50%+0.5px),transparent_calc(50%+0.5px))] before:bg-[size:calc(var(--indent)+0.25em)_100%]', lineOffset: '', indent: '', linkLabel: 'truncate', From e5bc674c1fb18417b2747efe5e2c7bd66037396b Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Thu, 31 Jul 2025 15:16:21 +0700 Subject: [PATCH 07/11] test(Tree): fix snapshot --- .../__snapshots__/Tree-vue.spec.ts.snap | 562 ++++++------------ .../__snapshots__/Tree.spec.ts.snap | 562 ++++++------------ 2 files changed, 384 insertions(+), 740 deletions(-) diff --git a/test/components/__snapshots__/Tree-vue.spec.ts.snap b/test/components/__snapshots__/Tree-vue.spec.ts.snap index a8da856c18..043939e0dd 100644 --- a/test/components/__snapshots__/Tree-vue.spec.ts.snap +++ b/test/components/__snapshots__/Tree-vue.spec.ts.snap @@ -2,520 +2,342 @@ exports[`Tree > renders with as correctly 1`] = ` "
    -
  • - -
  • -
  • - -
  • -
  • - -
  • +
  • +
  • +
  • " `; exports[`Tree > renders with class correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with collapsedIcon correctly 1`] = ` "
      -
    • - -
    • +
    " `; exports[`Tree > renders with default slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defaultExpanded correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defaultValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defautExpanded item correctly 1`] = ` "
      -
    • -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -
    • +
    • +
    • +
    • +
    " `; exports[`Tree > renders with disabled correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with disabled item correctly 1`] = ` "
      -
    • - -
    • +
    " `; exports[`Tree > renders with dynamic slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with expanded correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with expandedIcon correctly 1`] = ` "
      -
    • -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -
    • +
    • +
    • +
    • +
    " `; exports[`Tree > renders with item slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with item-leading slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with item-trailing slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with items correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with labelKey correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with modelValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple and defaultValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple and modelValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with neutral color correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size lg correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size md correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size sm correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size xl correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size xs correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with trailingIcon correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with ui correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with valueKey correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; diff --git a/test/components/__snapshots__/Tree.spec.ts.snap b/test/components/__snapshots__/Tree.spec.ts.snap index d02a6dd671..ec4a63a321 100644 --- a/test/components/__snapshots__/Tree.spec.ts.snap +++ b/test/components/__snapshots__/Tree.spec.ts.snap @@ -2,520 +2,342 @@ exports[`Tree > renders with as correctly 1`] = ` "
    -
  • - -
  • -
  • - -
  • -
  • - -
  • +
  • +
  • +
  • " `; exports[`Tree > renders with class correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with collapsedIcon correctly 1`] = ` "
      -
    • - -
    • +
    " `; exports[`Tree > renders with default slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defaultExpanded correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defaultValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with defautExpanded item correctly 1`] = ` "
      -
    • -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -
    • +
    • +
    • +
    • +
    " `; exports[`Tree > renders with disabled correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with disabled item correctly 1`] = ` "
      -
    • - -
    • +
    " `; exports[`Tree > renders with dynamic slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with expanded correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with expandedIcon correctly 1`] = ` "
      -
    • -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -
    • +
    • +
    • +
    • +
    " `; exports[`Tree > renders with item slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with item-leading slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with item-trailing slot correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with items correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with labelKey correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with modelValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple and defaultValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple and modelValue correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with multiple correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with neutral color correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size lg correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size md correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size sm correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size xl correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with size xs correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with trailingIcon correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with ui correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; exports[`Tree > renders with valueKey correctly 1`] = ` "
      -
    • - -
    • -
    • - -
    • -
    • - -
    • +
    • +
    • +
    " `; From 66bd8f4560565e06886fac987d2c483707a136e1 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Sun, 10 Aug 2025 01:52:10 +0700 Subject: [PATCH 08/11] test: update snapshot --- .../__snapshots__/Tree-vue.spec.ts.snap | 16 +++++++--------- test/components/__snapshots__/Tree.spec.ts.snap | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/test/components/__snapshots__/Tree-vue.spec.ts.snap b/test/components/__snapshots__/Tree-vue.spec.ts.snap index 1d225b69e0..71e2c7c438 100644 --- a/test/components/__snapshots__/Tree-vue.spec.ts.snap +++ b/test/components/__snapshots__/Tree-vue.spec.ts.snap @@ -164,15 +164,13 @@ exports[`Tree > renders with item-trailing slot correctly 1`] = ` exports[`Tree > renders with item-wrapper slot correctly 1`] = ` "
      -
    • - -
    • -
    • wrapper slot - -
    • -
    • wrapper slot - -
    • +
    • +
    • +
    " `; diff --git a/test/components/__snapshots__/Tree.spec.ts.snap b/test/components/__snapshots__/Tree.spec.ts.snap index 62c65a0ebb..1cfaad6a45 100644 --- a/test/components/__snapshots__/Tree.spec.ts.snap +++ b/test/components/__snapshots__/Tree.spec.ts.snap @@ -164,15 +164,13 @@ exports[`Tree > renders with item-trailing slot correctly 1`] = ` exports[`Tree > renders with item-wrapper slot correctly 1`] = ` "
      -
    • - -
    • -
    • wrapper slot - -
    • -
    • wrapper slot - -
    • +
    • +
    • +
    " `; From 93b451c2635d24237e36259b4e80fbc70ed2658d Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Sun, 10 Aug 2025 17:42:41 +0700 Subject: [PATCH 09/11] up --- src/runtime/components/Tree.vue | 56 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index 502929aff4..27b583a8e2 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -174,35 +174,37 @@ const defaultExpanded = computed(() => @toggle="item.value.onToggle" @select="item.value.onSelect" > - + + + {{ getItemLabel(item.value) }} + + + + + + + + + + + +
    From 720499672dabaf9a8312607da0a51ede17307db9 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Sun, 10 Aug 2025 17:50:11 +0700 Subject: [PATCH 10/11] test: update snapshot --- test/components/__snapshots__/Tree-vue.spec.ts.snap | 8 ++------ test/components/__snapshots__/Tree.spec.ts.snap | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/test/components/__snapshots__/Tree-vue.spec.ts.snap b/test/components/__snapshots__/Tree-vue.spec.ts.snap index 71e2c7c438..f49cf7784c 100644 --- a/test/components/__snapshots__/Tree-vue.spec.ts.snap +++ b/test/components/__snapshots__/Tree-vue.spec.ts.snap @@ -165,12 +165,8 @@ exports[`Tree > renders with item-trailing slot correctly 1`] = ` exports[`Tree > renders with item-wrapper slot correctly 1`] = ` "
    • -
    • -
    • +
    • wrapper slot
    • +
    • wrapper slot
    " `; diff --git a/test/components/__snapshots__/Tree.spec.ts.snap b/test/components/__snapshots__/Tree.spec.ts.snap index 1cfaad6a45..35f36274b8 100644 --- a/test/components/__snapshots__/Tree.spec.ts.snap +++ b/test/components/__snapshots__/Tree.spec.ts.snap @@ -165,12 +165,8 @@ exports[`Tree > renders with item-trailing slot correctly 1`] = ` exports[`Tree > renders with item-wrapper slot correctly 1`] = ` "
    • -
    • -
    • +
    • wrapper slot
    • +
    • wrapper slot
    " `; From 2c1b1cb6b4e0f95898ea0d114ca4ae4d683c8763 Mon Sep 17 00:00:00 2001 From: rdjanuar Date: Wed, 13 Aug 2025 07:47:08 +0700 Subject: [PATCH 11/11] up --- src/runtime/components/Tree.vue | 39 +++++++++++++++++++++++++++++++-- src/theme/tree.ts | 25 +++++++-------------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/runtime/components/Tree.vue b/src/runtime/components/Tree.vue index 27b583a8e2..94f59c9187 100644 --- a/src/runtime/components/Tree.vue +++ b/src/runtime/components/Tree.vue @@ -79,6 +79,8 @@ export interface TreeProps> + indent?: Partial> ui?: Tree['slots'] } @@ -126,6 +128,39 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tree || {}) size: props.size })) +const lineOffset = computed(() => { + switch (props.size) { + case 'xs': + return props.lineOffset?.xs || '6px' + case 'sm': + return props.lineOffset?.sm || '6px' + case 'md': + return props.lineOffset?.md || '8px' + case 'lg': + return props.lineOffset?.lg || '8px' + case 'xl': + return props.lineOffset?.xl || '10px' + default: + return props.lineOffset?.md || '8px' + } +}) +const indent = computed(() => { + switch (props.size) { + case 'xs': + return props.indent?.xs || '16px' + case 'sm': + return props.indent?.sm || '20px' + case 'md': + return props.indent?.md || '20px' + case 'lg': + return props.indent?.lg || '24px' + case 'xl': + return props.indent?.xl || '24px' + default: + return props.indent?.md || '20px' + } +}) + function getItemLabel(item: T): string { return get(item, props.labelKey as string) } @@ -163,8 +198,8 @@ const defaultExpanded = computed(() => :class="[ui.connector(), item.level > 0 ? [ui.itemWithChildren({ class: [props.ui?.itemWithChildren, item.value.ui?.itemWithChildren] })] : ui.item({ class: [props.ui?.item, item.value.ui?.item] })]" :style="{ '--level': item.level - 1, - '--line-offset': ui.lineOffset(), - '--indent': ui.indent() + '--line-offset': lineOffset, + '--indent': indent }" > ) => ({ link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2', linkLeadingIcon: 'shrink-0', connector: 'relative before:absolute before:content-[\'\'] before:top-0 before:bottom-0 before:left-[var(--line-offset)] before:pointer-events-none before:w-[calc(var(--level)*(var(--indent)+0.25em))] before:bg-[repeating-linear-gradient(to_right,transparent,transparent_calc(50%-0.5px),var(--ui-border)_calc(50%-0.5px),var(--ui-border)_calc(50%+0.5px),transparent_calc(50%+0.5px))] before:bg-[size:calc(var(--indent)+0.25em)_100%]', - lineOffset: '', - indent: '', linkLabel: 'truncate', linkTrailing: 'ms-auto inline-flex gap-1.5 items-center', linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180' @@ -27,37 +25,30 @@ export default (options: Required) => ({ xs: { link: 'px-2 py-1 text-xs gap-1', linkLeadingIcon: 'size-4', - linkTrailingIcon: 'size-4', - lineOffset: '6px', - indent: '16px' + linkTrailingIcon: 'size-4' }, sm: { link: 'px-2.5 py-1.5 text-xs gap-1.5', linkLeadingIcon: 'size-4', - linkTrailingIcon: 'size-4', - lineOffset: '6px', - indent: '20px' + linkTrailingIcon: 'size-4' }, md: { link: 'px-2.5 py-1.5 text-sm gap-1.5', linkLeadingIcon: 'size-5', - linkTrailingIcon: 'size-5', - lineOffset: '8px', - indent: '20px' + linkTrailingIcon: 'size-5' + }, lg: { link: 'px-3 py-2 text-sm gap-2', linkLeadingIcon: 'size-5', - linkTrailingIcon: 'size-5', - lineOffset: '8px', - indent: '24px' + linkTrailingIcon: 'size-5' + }, xl: { link: 'px-3 py-2 text-base gap-2', linkLeadingIcon: 'size-6', - linkTrailingIcon: 'size-6', - lineOffset: '10px', - indent: '24px' + linkTrailingIcon: 'size-6' + } }, selected: {