@lumen-design/collapse 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Collapse.svelte +48 -0
- package/dist/Collapse.svelte.d.ts +11 -0
- package/dist/CollapseContext.d.ts +6 -0
- package/dist/CollapseContext.js +1 -0
- package/dist/CollapseItem.svelte +133 -0
- package/dist/CollapseItem.svelte.d.ts +11 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import { setContext } from 'svelte'
|
|
4
|
+
import { COLLAPSE_CONTEXT_KEY, type CollapseContext } from './CollapseContext'
|
|
5
|
+
|
|
6
|
+
interface CollapseProps {
|
|
7
|
+
modelValue?: string | string[]
|
|
8
|
+
accordion?: boolean
|
|
9
|
+
onchange?: (value: string | string[]) => void
|
|
10
|
+
children?: Snippet
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let { modelValue = $bindable([]), accordion = false, onchange, children, class: cls = '', ...attrs }: CollapseProps = $props()
|
|
15
|
+
|
|
16
|
+
const activeNames = $derived(Array.isArray(modelValue) ? modelValue : modelValue ? [modelValue] : [])
|
|
17
|
+
|
|
18
|
+
const toggle = (name: string): void => {
|
|
19
|
+
let newValue: string | string[]
|
|
20
|
+
if (accordion) {
|
|
21
|
+
newValue = activeNames.includes(name) ? '' : name
|
|
22
|
+
} else {
|
|
23
|
+
newValue = activeNames.includes(name) ? activeNames.filter((n) => n !== name) : [...activeNames, name]
|
|
24
|
+
}
|
|
25
|
+
modelValue = newValue
|
|
26
|
+
onchange?.(newValue)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const context: CollapseContext = {
|
|
30
|
+
get activeNames() {
|
|
31
|
+
return activeNames
|
|
32
|
+
},
|
|
33
|
+
get accordion() {
|
|
34
|
+
return accordion
|
|
35
|
+
},
|
|
36
|
+
toggle,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setContext(COLLAPSE_CONTEXT_KEY, context)
|
|
40
|
+
|
|
41
|
+
const classes = $derived(cls ? `lm-collapse ${cls}` : 'lm-collapse')
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class={classes} role="tablist" {...attrs}>
|
|
45
|
+
{#if children}
|
|
46
|
+
{@render children()}
|
|
47
|
+
{/if}
|
|
48
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface CollapseProps {
|
|
3
|
+
modelValue?: string | string[];
|
|
4
|
+
accordion?: boolean;
|
|
5
|
+
onchange?: (value: string | string[]) => void;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const Collapse: import("svelte").Component<CollapseProps, {}, "modelValue">;
|
|
10
|
+
type Collapse = ReturnType<typeof Collapse>;
|
|
11
|
+
export default Collapse;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const COLLAPSE_CONTEXT_KEY = Symbol('collapse');
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import { getContext, onMount } from 'svelte'
|
|
4
|
+
import { COLLAPSE_CONTEXT_KEY, type CollapseContext } from './CollapseContext'
|
|
5
|
+
import Icon from '@lumen-design/icon'
|
|
6
|
+
import { ChevronRight } from 'lucide'
|
|
7
|
+
|
|
8
|
+
interface CollapseItemProps {
|
|
9
|
+
name: string
|
|
10
|
+
title?: string | Snippet
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
children?: Snippet
|
|
13
|
+
class?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { name, title = '', disabled = false, children, class: cls = '', ...attrs }: CollapseItemProps = $props()
|
|
17
|
+
|
|
18
|
+
const collapse = getContext<CollapseContext>(COLLAPSE_CONTEXT_KEY)
|
|
19
|
+
|
|
20
|
+
const isActive = $derived(collapse?.activeNames.includes(name) ?? false)
|
|
21
|
+
|
|
22
|
+
let wrapEl: HTMLDivElement | null = null
|
|
23
|
+
let contentEl: HTMLDivElement | null = null
|
|
24
|
+
let wrapHeight = $state<string>('0px')
|
|
25
|
+
let reduceMotion = $state(false)
|
|
26
|
+
|
|
27
|
+
const setHeightInstant = (value: string): void => {
|
|
28
|
+
wrapHeight = value
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const animateTo = (value: string): void => {
|
|
32
|
+
requestAnimationFrame(() => {
|
|
33
|
+
wrapHeight = value
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const open = (): void => {
|
|
38
|
+
if (!wrapEl) return
|
|
39
|
+
|
|
40
|
+
const targetHeight = contentEl?.scrollHeight ?? wrapEl.scrollHeight
|
|
41
|
+
setHeightInstant('0px')
|
|
42
|
+
animateTo(`${targetHeight}px`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const close = (): void => {
|
|
46
|
+
if (!wrapEl) return
|
|
47
|
+
|
|
48
|
+
const currentHeight = wrapEl.getBoundingClientRect().height
|
|
49
|
+
setHeightInstant(`${currentHeight}px`)
|
|
50
|
+
animateTo('0px')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const handleWrapTransitionEnd = (e: TransitionEvent): void => {
|
|
54
|
+
if (e.target !== wrapEl) return
|
|
55
|
+
if (e.propertyName !== 'height') return
|
|
56
|
+
if (isActive) {
|
|
57
|
+
wrapHeight = 'auto'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onMount(() => {
|
|
62
|
+
if (typeof window !== 'undefined') {
|
|
63
|
+
reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
wrapHeight = isActive ? 'auto' : '0px'
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
let prevActive = $state<boolean>(false)
|
|
70
|
+
$effect(() => {
|
|
71
|
+
const activeNow = isActive
|
|
72
|
+
if (!wrapEl) {
|
|
73
|
+
prevActive = activeNow
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (reduceMotion) {
|
|
78
|
+
wrapHeight = activeNow ? 'auto' : '0px'
|
|
79
|
+
prevActive = activeNow
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (activeNow === prevActive) return
|
|
84
|
+
prevActive = activeNow
|
|
85
|
+
|
|
86
|
+
if (activeNow) open()
|
|
87
|
+
else close()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const handleClick = (): void => {
|
|
91
|
+
if (!disabled) collapse?.toggle(name)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleKeydown = (e: KeyboardEvent): void => {
|
|
95
|
+
if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
|
|
96
|
+
e.preventDefault()
|
|
97
|
+
collapse?.toggle(name)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const classes = $derived(`lm-collapse-item${isActive ? ' is-active' : ''}${disabled ? ' is-disabled' : ''}${cls ? ` ${cls}` : ''}`)
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<div class={classes} {...attrs}>
|
|
105
|
+
<div class="lm-collapse-item__header" role="tab" tabindex={disabled ? -1 : 0} aria-expanded={isActive} aria-disabled={disabled} onclick={handleClick} onkeydown={handleKeydown}>
|
|
106
|
+
<span class="lm-collapse-item__title">
|
|
107
|
+
{#if typeof title === 'string'}
|
|
108
|
+
{title}
|
|
109
|
+
{:else if title}
|
|
110
|
+
{@render title()}
|
|
111
|
+
{/if}
|
|
112
|
+
</span>
|
|
113
|
+
<span class="lm-collapse-item__arrow" class:is-active={isActive}>
|
|
114
|
+
<Icon icon={ChevronRight} size={16} />
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div
|
|
119
|
+
class="lm-collapse-item__wrap"
|
|
120
|
+
class:is-active={isActive}
|
|
121
|
+
role="tabpanel"
|
|
122
|
+
aria-hidden={!isActive}
|
|
123
|
+
bind:this={wrapEl}
|
|
124
|
+
style:height={wrapHeight}
|
|
125
|
+
ontransitionend={handleWrapTransitionEnd}
|
|
126
|
+
>
|
|
127
|
+
<div class="lm-collapse-item__content" bind:this={contentEl}>
|
|
128
|
+
{#if children}
|
|
129
|
+
{@render children()}
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface CollapseItemProps {
|
|
3
|
+
name: string;
|
|
4
|
+
title?: string | Snippet;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const CollapseItem: import("svelte").Component<CollapseItemProps, {}, "">;
|
|
10
|
+
type CollapseItem = ReturnType<typeof CollapseItem>;
|
|
11
|
+
export default CollapseItem;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
/** Collapse 属性 */
|
|
3
|
+
export interface CollapseProps {
|
|
4
|
+
/** 当前激活的面板 */
|
|
5
|
+
modelValue?: string | string[];
|
|
6
|
+
/** 是否手风琴模式 */
|
|
7
|
+
accordion?: boolean;
|
|
8
|
+
/** 变更事件 */
|
|
9
|
+
onchange?: (value: string | string[]) => void;
|
|
10
|
+
/** 子内容 */
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
/** 自定义类名 */
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
/** CollapseItem 属性 */
|
|
16
|
+
export interface CollapseItemProps {
|
|
17
|
+
/** 唯一标识 */
|
|
18
|
+
name: string;
|
|
19
|
+
/** 标题 */
|
|
20
|
+
title?: string | Snippet;
|
|
21
|
+
/** 是否禁用 */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** 子内容 */
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
/** 自定义类名 */
|
|
26
|
+
class?: string;
|
|
27
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lumen-design/collapse",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Collapse component for Lumen UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "svelte-package -i src -o dist --types",
|
|
21
|
+
"build:watch": "svelte-package -i src -o dist --types -w"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@lumen-design/icon": "0.0.2",
|
|
25
|
+
"lucide": "^0.563.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@sveltejs/package": "^2.5.7",
|
|
29
|
+
"svelte": "5.48.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"svelte": "^5.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|