-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tree): add theming for tree component
RISDEV-4997
- Loading branch information
Showing
4 changed files
with
280 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
span[data-pc-section="nodelabel"]:has(a + span) { | ||
@apply gap-4; | ||
} | ||
|
||
span[data-pc-section="nodelabel"] a, | ||
span[data-pc-section="nodelabel"] span { | ||
@apply w-full focus:outline-none; | ||
} | ||
|
||
span[data-pc-section="nodelabel"] :first-child { | ||
@apply group-hover:underline; | ||
} | ||
|
||
span[data-pc-section="nodelabel"] span:last-child { | ||
@apply group-hover:no-underline; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { Meta, StoryObj } from "@storybook/vue3"; | ||
import Tree from "primevue/tree"; | ||
import { html } from "@/lib/tags.ts"; | ||
import { ref, onMounted } from "vue"; | ||
import ChevronDownIcon from "~icons/mdi/chevron-down"; | ||
import ChevronUpIcon from "~icons/mdi/chevron-up"; | ||
import PrimevueButton from "primevue/button"; | ||
import { vueRouter } from "storybook-vue3-router"; | ||
|
||
interface TreeNode { | ||
key: string; | ||
label: string; | ||
route?: string; | ||
secondaryLabel?: string; | ||
children?: TreeNode[]; | ||
} | ||
|
||
const meta: Meta<typeof Tree> = { | ||
component: Tree, | ||
tags: ["autodocs"], | ||
args: {}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Default: Story = { | ||
render: (args) => ({ | ||
components: { Tree, ChevronDownIcon, ChevronUpIcon, PrimevueButton }, | ||
setup() { | ||
const nodes = ref<TreeNode[]>([]); | ||
const expandedKeys = ref<Record<string, boolean>>({}); | ||
const isExpanded = ref<boolean>(false); | ||
const selectionKeys = ref<Record<string, boolean>>({}); | ||
|
||
onMounted(() => { | ||
nodes.value = [ | ||
{ | ||
key: "0", | ||
label: "Primary Text", | ||
secondaryLabel: "Secondary Text", | ||
route: "/", | ||
children: [ | ||
{ | ||
key: "0-0", | ||
label: "Child 1", | ||
secondaryLabel: "Secondary Text", | ||
children: [ | ||
{ | ||
key: "0-0-0", | ||
label: "Grandchild 1", | ||
secondaryLabel: "Secondary Text", | ||
route: "/grandchild-1", | ||
}, | ||
{ | ||
key: "0-0-1", | ||
label: "Grandchild 2", | ||
secondaryLabel: "Secondary Text", | ||
route: "/grandchild-2", | ||
children: [ | ||
{ | ||
key: "0-0-1-0", | ||
label: "Great Grandchild", | ||
route: "/great-grandchild", | ||
}, | ||
], | ||
}, | ||
{ | ||
key: "0-0-1-1", | ||
label: "Grandchild 3", | ||
secondaryLabel: "Secondary Text", | ||
children: [ | ||
{ | ||
key: "0-0-1-2", | ||
label: "Great Grandchild 2", | ||
route: "/great-grandchild-2", | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
key: "0-1", | ||
label: "Child 2", | ||
secondaryLabel: "Secondary Text", | ||
route: "/child-2", | ||
children: [ | ||
{ | ||
key: "0-1-0", | ||
label: "Grandchild 4", | ||
secondaryLabel: "Secondary Text", | ||
route: "/grandchild-4", | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
]; | ||
}); | ||
|
||
// Toggle expansion on click | ||
const toggleNode = (node: TreeNode) => { | ||
if (expandedKeys.value[node.key]) { | ||
delete expandedKeys.value[node.key]; | ||
} else { | ||
expandedKeys.value[node.key] = true; | ||
} | ||
expandedKeys.value = { ...expandedKeys.value }; | ||
}; | ||
|
||
// Expand all nodes | ||
const expandAll = () => { | ||
const expandNode = (node: TreeNode) => { | ||
expandedKeys.value[node.key] = true; | ||
if (node.children) { | ||
node.children.forEach(expandNode); | ||
} | ||
}; | ||
|
||
nodes.value.forEach(expandNode); | ||
expandedKeys.value = { ...expandedKeys.value }; | ||
}; | ||
|
||
// Collapse all nodes | ||
const collapseAll = () => { | ||
expandedKeys.value = {}; | ||
}; | ||
|
||
// Toggle expand/collapse for all nodes | ||
const toggleExpandCollapse = () => { | ||
if (isExpanded.value) { | ||
collapseAll(); | ||
} else { | ||
expandAll(); | ||
} | ||
isExpanded.value = !isExpanded.value; | ||
}; | ||
|
||
return { | ||
args, | ||
nodes, | ||
expandedKeys, | ||
selectionKeys, | ||
isExpanded, | ||
toggleExpandCollapse, | ||
toggleNode, | ||
}; | ||
}, | ||
template: html` | ||
<div class="card w-full"> | ||
<div | ||
class="mb-6 flex w-full cursor-pointer items-center justify-between gap-2" | ||
@click="toggleExpandCollapse" | ||
> | ||
<span>Inhaltsverzeichnis</span> | ||
<PrimevueButton text label="Alle Ebenen öffnen" v-if="!isExpanded"> | ||
<template #icon> | ||
<ChevronDownIcon /> | ||
</template> | ||
</PrimevueButton> | ||
<PrimevueButton text label="Alle Ebenen schließen" v-else> | ||
<template #icon> | ||
<ChevronUpIcon /> | ||
</template> | ||
</PrimevueButton> | ||
</div> | ||
<Tree | ||
v-model:expandedKeys="expandedKeys" | ||
v-model:selectionKeys="selectionKeys" | ||
:value="nodes" | ||
selectionMode="single" | ||
> | ||
<template #default="{ node, selected, expanded }"> | ||
<router-link | ||
v-if="node.route" | ||
:to="node.route" | ||
@click="toggleNode(node)" | ||
> | ||
{{ node.label }} | ||
</router-link> | ||
<span v-else class="w-full" @click="toggleNode(node)" | ||
>{{ node.label }}</span | ||
> | ||
<span class="ris-label2-regular" @click="toggleNode(node)" | ||
>{{ node.secondaryLabel }}</span | ||
> | ||
</template> | ||
<template #nodetoggleicon="{ expanded }"> | ||
<ChevronDownIcon v-if="!expanded" /> | ||
<ChevronUpIcon v-else /> | ||
</template> | ||
</Tree> | ||
</div> | ||
`, | ||
}), | ||
decorators: [ | ||
vueRouter(), // This will add basic router functionality to your story | ||
], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { TreePassThroughOptions } from "primevue/tree"; | ||
import { tw } from "@/lib/tags.ts"; | ||
import "./tree.css"; | ||
|
||
const tree: TreePassThroughOptions = { | ||
node: () => { | ||
const focus = tw`focus-visible:outline-none focus-visible:outline-4 focus-visible:outline-offset-4 focus-visible:outline-blue-800`; // Adding focus state with a red border | ||
return { | ||
class: { | ||
[focus]: true, | ||
}, | ||
}; | ||
}, | ||
nodeContent: ({ context }) => { | ||
const base = tw`group ris-label2-bold flex w-full border-l-4 border-transparent py-10 pl-10 pr-20 text-blue-800 hover:bg-gray-100`; | ||
const pointer = tw`cursor-pointer select-none`; | ||
const focusVisible = tw`focus-visible:outline-none focus-visible:outline-4 focus-visible:outline-offset-4 focus-visible:outline-blue-800`; // Adding focus state with a red border | ||
const selected = tw`border-l-blue-800 bg-gray-100`; | ||
const hoverActive = tw`hover:active:bg-blue-200`; | ||
|
||
return { | ||
class: { | ||
[base]: true, | ||
[selected]: context.selected, | ||
[pointer]: true, | ||
[focusVisible]: true, | ||
[hoverActive]: true, | ||
}, | ||
}; | ||
}, | ||
nodeToggleButton: ({ context }) => { | ||
const base = tw`inline-flex h-24 w-24 justify-center border-0 bg-transparent outline-none hover:text-black group-hover:text-black`; | ||
const invisible = tw`invisible`; | ||
|
||
return { | ||
class: { | ||
[base]: true, | ||
[invisible]: context.leaf, | ||
}, | ||
}; | ||
}, | ||
nodeChildren: () => { | ||
const base = tw`m-0 ml-14 mt-1 w-full list-none p-0 outline-none`; | ||
const focusVisible = tw`focus-visible:outline-none focus-visible:outline-4 focus-visible:outline-offset-4 focus-visible:outline-blue-800`; // Adding focus state with a red border | ||
return { | ||
class: { | ||
[base]: true, | ||
[focusVisible]: true, | ||
}, | ||
}; | ||
}, | ||
nodeLabel: () => { | ||
const base = tw`group flex w-full flex-col items-start outline-none group-hover:text-black`; | ||
return { | ||
class: { | ||
[base]: true, | ||
}, | ||
}; | ||
}, | ||
}; | ||
|
||
export default tree; |