@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.
@@ -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,6 @@
1
+ export declare const COLLAPSE_CONTEXT_KEY: unique symbol;
2
+ export interface CollapseContext {
3
+ activeNames: string[];
4
+ accordion: boolean;
5
+ toggle: (name: string) => void;
6
+ }
@@ -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;
@@ -0,0 +1,5 @@
1
+ import Collapse from './Collapse.svelte';
2
+ import CollapseItem from './CollapseItem.svelte';
3
+ export { Collapse, CollapseItem };
4
+ export type { CollapseProps, CollapseItemProps } from './types';
5
+ export default Collapse;
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import Collapse from './Collapse.svelte';
2
+ import CollapseItem from './CollapseItem.svelte';
3
+ export { Collapse, CollapseItem };
4
+ export default Collapse;
@@ -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
+ }