Skip to content

Commit

Permalink
feat: collapse component added showClose prop and closeIcon slot (#214)
Browse files Browse the repository at this point in the history
* feat: collapse component added showClose prop and closeIcon slot

* chore: temp commit

* feat: added collapse wrapper to support accordion

* test: updated unit test

* docs: updated collapse document

* test: updated snap
  • Loading branch information
baiwusanyu-c committed Sep 5, 2023
1 parent 28b383e commit 488638c
Show file tree
Hide file tree
Showing 20 changed files with 349 additions and 56 deletions.
14 changes: 7 additions & 7 deletions components/Collapse/__test__/__snapshots__/collapse.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Test: KCollapse > props: content 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark k-collapse--title__show\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"k-collapse--content\\"><div class=\\"k-collapse--line\\"></div> foo</div></div>"`;
exports[`Test: KCollapse > props: content 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"\\" style=\\"opacity: 0;\\"><div class=\\"k-collapse--line\\"></div> foo</div></div>"`;
exports[`Test: KCollapse > props: show 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark k-collapse--title__show\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"k-collapse--content\\" style=\\"animation: __svelte_2259128677_0 300ms linear 0ms 1 both;\\"><div class=\\"k-collapse--line\\"></div> foo</div></div>"`;
exports[`Test: KCollapse > props: show 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"\\" style=\\"opacity: 0; animation: __svelte_2259128677_0 300ms linear 0ms 1 both;\\"><div class=\\"k-collapse--line\\"></div> foo</div></div>"`;
exports[`Test: KCollapse > props: showClose 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\">title-content </div> </div>"`;
exports[`Test: KCollapse > props: showClose 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\">title-content </div> </div>"`;
exports[`Test: KCollapse > props: title 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\">title-content <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> </div>"`;
exports[`Test: KCollapse > props: title 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\">title-content <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> </div>"`;
exports[`Test: KCollapse > slot: closeIcon 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"> <div slot=\\"closeIcon\\" class=\\"flex\\"><div>🍓 closeIcon</div></div></div> </div>"`;
exports[`Test: KCollapse > slot: closeIcon 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"> <div slot=\\"closeIcon\\" class=\\"flex\\"><div>🍓 closeIcon</div></div></div> </div>"`;
exports[`Test: KCollapse > slot: content 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark k-collapse--title__show\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"k-collapse--content\\"><div class=\\"k-collapse--line\\"></div> <div slot=\\"content\\" class=\\"flex\\"><div id=\\"custom_content\\">🍞 Bread! Bread! We want to bread!</div></div></div></div>"`;
exports[`Test: KCollapse > slot: content 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right rotate-90 k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> <div class=\\"\\" style=\\"opacity: 0;\\"><div class=\\"k-collapse--line\\"></div> <div slot=\\"content\\" class=\\"flex\\"><div id=\\"custom_content\\">🍞 Bread! Bread! We want to bread!</div></div></div></div>"`;
exports[`Test: KCollapse > slot: title 1`] = `"<div class=\\"k-collapse k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"><div slot=\\"title\\" class=\\"flex\\"><div id=\\"custom_title\\">🍓 Germinal</div></div> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> </div>"`;
exports[`Test: KCollapse > slot: title 1`] = `"<div class=\\"k-collapse--base\\"><div class=\\"k-collapse--title k-collapse--title__dark\\" aria-hidden=\\"true\\"><div slot=\\"title\\" class=\\"flex\\"><div id=\\"custom_title\\">🍓 Germinal</div></div> <div role=\\"\\" aria-hidden=\\"true\\" class=\\"k-icon--base k-icon--base__dark \\"><div class=\\"i-carbon-chevron-right k-icon-transition \\" style=\\"width: 24px; height: 24px;\\"></div></div></div> </div>"`;
6 changes: 5 additions & 1 deletion components/Collapse/__test__/collapse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ const initHost = () => {
};
beforeEach(() => {
initHost();
vi.useFakeTimers();
});
afterEach(() => {
host.remove();
vi.restoreAllMocks();
});

describe('Test: KCollapse', () => {
Expand Down Expand Up @@ -92,15 +94,17 @@ describe('Test: KCollapse', () => {
});
expect(instance).toBeTruthy();
expect(host.children[0].children.length).toBe(1);
const triggerEl = host.children[0].children[0];
const triggerEl = host.children[0].children[0].children[0];
triggerEl.dispatchEvent(new window.Event('click', { bubbles: true }));
await tick();
await vi.advanceTimersByTimeAsync(300);
expect(host.children[0].children.length).toBe(2);
expect(mockFn).toBeCalledTimes(1);
expect(show).toBeTruthy();
triggerEl.dispatchEvent(new window.Event('click', { bubbles: true }));
instance.$set({ show });
await tick();
await vi.advanceTimersByTimeAsync(300);
expect(mockFn).toBeCalledTimes(2);
expect(show).not.toBeTruthy();
});
Expand Down
108 changes: 92 additions & 16 deletions components/Collapse/src/index.svelte
Original file line number Diff line number Diff line change
@@ -1,35 +1,113 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { KIcon } from '@ikun-ui/icon';
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher, getContext, tick } from 'svelte';
import { clsx, type ClassValue } from 'clsx';
import { getPrefixCls } from '@ikun-ui/utils';
import { getPrefixCls, collapseWrapperKey } from '@ikun-ui/utils';
export let title = '';
export let content = '';
export let attrs = {};
export let cls: ClassValue = '';
export let show = false;
export let showClose = true;
export let uid: string = '';
const dispatch = createEventDispatcher();
let showInner = show;
const showContent = () => {
showInner = !showInner;
dispatch('change', showInner);
const showCur = !showInner;
if (showCur) {
showInner = !showInner;
setAnimateOpen(() => {
dispatch('change', showInner);
});
if (collapseContext) {
collapseContext.closeCollapse(uid);
}
} else {
setAnimateClose(() => {
showInner = !showInner;
dispatch('change', showInner);
});
}
};
const closeCollapse = () => {
setAnimateClose(() => {
showInner = false;
});
};
const collapseContext = getContext<{
setCollapseMap: (key: string, cb: () => void) => void;
closeCollapse: (key: string) => void;
}>(collapseWrapperKey);
if (collapseContext && uid) {
collapseContext.setCollapseMap(uid, closeCollapse);
}
$: if (show) {
showInner = true;
setAnimateOpen();
} else {
setAnimateClose(() => {
showInner = false;
});
}
let contentRef: HTMLElement | null = null;
let opacity = 0;
let contentCls = '';
const setAnimateOpen = async (cb?: () => void) => {
await tick();
if (contentRef) {
const h = contentRef.clientHeight;
contentRef.style.height = '0';
contentRef.style.transition = 'height 0.3s linear';
contentCls = cnamesContent;
await tick();
setTimeout(() => {
contentRef && (contentRef.style.height = `${h}px`);
opacity = 1;
cb && cb();
}, 0);
}
};
const setAnimateClose = (cb: () => void) => {
if (contentRef) {
contentRef && (contentRef.style.height = '0');
opacity = 0;
contentCls = '';
setTimeout(() => {
cb && cb();
}, 300);
}
};
$: if (show) showInner = true;
else showInner = false;
// class
const prefixCls = getPrefixCls('collapse');
$: clsInner = clsx(`${prefixCls}`, `${prefixCls}--base`, cls);
$: clsInner = clsx(
{
[`${prefixCls}--base`]: !collapseContext,
[`${prefixCls}--base--wrapper`]: collapseContext
},
cls
);
$: cnames = clsx(`${prefixCls}--title`, `${prefixCls}--title__dark`, {
[`${prefixCls}--title__show`]: showInner
[`${prefixCls}--title__active`]: showInner && collapseContext
});
$: cnamesLine = clsx(`${prefixCls}--line`);
$: cnamesContent = clsx(`${prefixCls}--content`);
$: cnamesTitleIcon = clsx({
'i-carbon-chevron-right': true,
['rotate-90']: showInner,
[`${prefixCls}--title__active`]: showInner && collapseContext
});
</script>

<div class={clsInner} {...attrs}>
Expand All @@ -39,18 +117,16 @@
</slot>
<slot name="closeIcon">
{#if showClose}
<KIcon icon="i-carbon-chevron-right {showInner ? 'rotate-90' : ''}" />
<KIcon icon={cnamesTitleIcon} />
{/if}
</slot>
</div>
{#if showInner}
<div
class={cnamesContent}
out:fly={{ y: -30, duration: 300 }}
in:fly={{ y: -30, duration: 300 }}
>
<div class={contentCls} bind:this={contentRef} style:opacity in:fly={{ y: -30, duration: 300 }}>
<div class={cnamesLine} />
<slot name="content">{content}</slot>
<slot name="content">
{content}
</slot>
</div>
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Test: KCollapseWrapper > props: cls 1`] = `"<div class=\\"k-collapse-wrapper k-collapse-wrapper--base k-collapse-wrapper--test\\"></div>"`;
30 changes: 30 additions & 0 deletions components/CollapseWrapper/__test__/collapse-wrapper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { afterEach, expect, test, describe, beforeEach } from 'vitest';
import KCollapseWrapper from '../src';

let host: HTMLElement;

const initHost = () => {
host = document.createElement('div');
host.setAttribute('id', 'host');
document.body.appendChild(host);
};
beforeEach(() => {
initHost();
});
afterEach(() => {
host.remove();
});

describe('Test: KCollapseWrapper', () => {
test('props: cls', async () => {
const instance = new KCollapseWrapper({
target: host,
props: {
cls: 'k-collapse-wrapper--test'
}
});
expect(instance).toBeTruthy();
expect((host as HTMLElement)!.innerHTML.includes('k-collapse-wrapper--test')).toBeTruthy();
expect(host.innerHTML).matchSnapshot();
});
});
42 changes: 42 additions & 0 deletions components/CollapseWrapper/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@ikun-ui/collapse-wrapper",
"version": "0.0.9-beta.5",
"type": "module",
"main": "./src/index.ts",
"types": "src/index.ts",
"keywords": [
"svelte",
"svelte3",
"web component",
"component",
"react",
"vue",
"svelte-kit",
"dx"
],
"scripts": {
"build": "npm run build:js && npm run build:svelte",
"build:js": "tsc -p . --outDir dist/ --rootDir src/",
"build:svelte": "svelte-strip strip src/ dist",
"publish:npm": "pnpm publish --no-git-checks --access public"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"module": "dist/index.js",
"svelte": "dist/index.js",
"types": "src/index.ts"
},
"dependencies": {
"@ikun-ui/icon": "workspace:*",
"@ikun-ui/utils": "workspace:*",
"baiwusanyu-utils": "^1.0.14",
"clsx": "^2.0.0"
},
"devDependencies": {
"@tsconfig/svelte": "^5.0.0",
"svelte-strip": "^2.0.0",
"tslib": "^2.6.1",
"typescript": "^5.1.6"
}
}
50 changes: 50 additions & 0 deletions components/CollapseWrapper/src/index.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
import { getPrefixCls, collapseWrapperKey } from '@ikun-ui/utils';
import { clsx } from 'clsx';
import { onMount, setContext } from 'svelte';
import type { collapseMapType, KCollapseWrapperProps } from './types';
export let cls: KCollapseWrapperProps['cls'] = '';
export let accordion: KCollapseWrapperProps['accordion'] = false;
export let attrs: KCollapseWrapperProps['attrs'] = {};
const collapseMap: collapseMapType = {};
const setCollapseMap = (key: string, cb: () => void) => {
collapseMap[key] = cb;
};
const closeCollapse = (key: string) => {
Object.keys(collapseMap).forEach((k) => {
if (k !== key) {
collapseMap[k]();
}
});
};
if (accordion) {
setContext(collapseWrapperKey, {
setCollapseMap,
closeCollapse
});
}
let wrapperRef: HTMLElement | null = null;
onMount(() => {
if (wrapperRef) {
let lastCollapse = [...wrapperRef.querySelectorAll('.k-collapse--base--wrapper')].pop();
lastCollapse && ((lastCollapse as HTMLElement).style.border = '0');
}
});
const prefixCls = getPrefixCls('collapse-wrapper');
$: cnames = clsx(
prefixCls,
{
[`${prefixCls}--base`]: true
},
cls
);
</script>

<div class={cnames} {...$$restProps} {...attrs} bind:this={wrapperRef}>
<slot />
</div>
5 changes: 5 additions & 0 deletions components/CollapseWrapper/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="./types" />
import CollapseWrapper from './index.svelte';
export { CollapseWrapper as KCollapseWrapper };

export default CollapseWrapper;
7 changes: 7 additions & 0 deletions components/CollapseWrapper/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="svelte" />
export type KCollapseWrapperProps = {
accordion: boolean;
cls: string;
attrs: Record<string, string>;
};
export type collapseMapType = Record<string, () => void>;
11 changes: 11 additions & 0 deletions components/CollapseWrapper/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",

"compilerOptions": {
"noImplicitAny": true,
"strict": true,
"declaration": true
},
"include": ["src/**/*.ts", "src/**/*.svelte"],
"exclude": ["node_modules/*", "**/*.spec.ts"]
}
1 change: 1 addition & 0 deletions components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from '@ikun-ui/button';
export * from '@ikun-ui/button-group';
export * from '@ikun-ui/checkbox';
export * from '@ikun-ui/collapse';
export * from '@ikun-ui/collapse-wrapper';
export * from '@ikun-ui/client-only';
export * from '@ikun-ui/drawer';
export * from '@ikun-ui/eye-dropper';
Expand Down
Loading

0 comments on commit 488638c

Please sign in to comment.