-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(slot): new component * fix: ts-lint * fix: ts-lint * refactor: slot * chore: fix readme * refactor: slot * refactor: slot * chore: add more stories
- Loading branch information
1 parent
ddf7077
commit 247d535
Showing
13 changed files
with
641 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,13 @@ | ||
# `slot` | ||
|
||
<span><a href="https://www.npmjs.com/package/@oku-ui/slot "><img src="https://img.shields.io/npm/v/@oku-ui/slot?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/slot"> <img src="https://img.shields.io/npm/dm/@oku-ui/slot?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/slot"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> </span> | ||
|
||
## Installation | ||
|
||
```sh | ||
$ pnpm add @oku-ui/slot | ||
``` | ||
|
||
## Usage | ||
|
||
soon docs |
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,12 @@ | ||
import { defineBuildConfig } from 'unbuild' | ||
|
||
export default defineBuildConfig({ | ||
entries: [ | ||
{ | ||
builder: 'mkdist', | ||
input: './src/', | ||
pattern: ['**/!(*.test|*.stories).ts'], | ||
}, | ||
], | ||
declaration: true, | ||
}) |
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,49 @@ | ||
{ | ||
"name": "@oku-ui/slot", | ||
"type": "module", | ||
"version": "0.2.3", | ||
"license": "MIT", | ||
"source": "src/index.ts", | ||
"funding": "https://github.com/sponsors/productdevbook", | ||
"homepage": "https://oku-ui.com/primitives", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/oku-ui/primitives.git", | ||
"directory": "packages/components/slot" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/oku-ui/primitives/issues" | ||
}, | ||
"exports": { | ||
".": { | ||
"types": "./dist/index.d.ts", | ||
"import": "./dist/index.mjs" | ||
} | ||
}, | ||
"module": "./dist/index.mjs", | ||
"types": "./dist/index.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"engines": { | ||
"node": ">=18" | ||
}, | ||
"scripts": { | ||
"build": "tsup", | ||
"dev": "tsup --watch" | ||
}, | ||
"peerDependencies": { | ||
"vue": "^3.3.0" | ||
}, | ||
"dependencies": { | ||
"@oku-ui/primitive": "latest", | ||
"@oku-ui/use-composable": "latest", | ||
"@oku-ui/utils": "latest" | ||
}, | ||
"devDependencies": { | ||
"tsconfig": "workspace:^" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
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,4 @@ | ||
export { | ||
OkuSlot, | ||
OkuSlottable, | ||
} from './slot' |
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,223 @@ | ||
import { describe, expect, it } from 'vitest' | ||
import { mount } from '@vue/test-utils' | ||
import { h, ref } from 'vue' | ||
import type { Component } from 'vue' | ||
import { OkuSlot, OkuSlottable } from './slot' | ||
|
||
const ButtonTest: Component = { | ||
components: { | ||
OkuSlot, | ||
OkuSlottable, | ||
}, | ||
inheritAttrs: false, | ||
props: { | ||
asChild: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
}, | ||
setup(props, { slots }) { | ||
return () => { | ||
const Tag: any = props.asChild ? OkuSlot : 'button' | ||
return h( | ||
Tag, | ||
null, | ||
[ | ||
slots.iconLeft && slots.iconLeft(), | ||
h(OkuSlottable, {}, { | ||
default: () => slots.default && slots.default(), | ||
}), | ||
slots.iconRight && slots.iconRight(), | ||
], | ||
) | ||
} | ||
}, | ||
|
||
} | ||
|
||
describe('given a Button with Slottable', () => { | ||
describe('without asChild', () => { | ||
it('should render a button with icon on the left/right', () => { | ||
const _component: Component = { | ||
components: { | ||
ButtonTest, | ||
}, | ||
setup() { | ||
return () => h(ButtonTest, null, { | ||
iconLeft: () => h('span', {}, 'left'), | ||
iconRight: () => h('span', {}, 'right'), | ||
default: () => [ | ||
'Button ', | ||
h('em', {}, 'text'), | ||
], | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button><span>left</span>Button <em>text</em><span>right</span></button>') | ||
}) | ||
}) | ||
|
||
describe('with asChild', () => { | ||
it('should render a button with icon on the left/right', () => { | ||
const _component: Component = { | ||
components: { | ||
ButtonTest, | ||
}, | ||
setup() { | ||
return () => h(ButtonTest, { | ||
asChild: true, | ||
}, | ||
{ | ||
iconLeft: () => h('span', {}, 'left'), | ||
iconRight: () => h('span', {}, 'right'), | ||
default: () => h('a', { | ||
href: 'https://oku-ui.com', | ||
}, { | ||
default: () => [ | ||
'Button ', | ||
h('em', {}, 'text'), | ||
], | ||
}), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<a href="https://oku-ui.com"><span>left</span>Button <em>text</em><span>right</span></a>') | ||
}) | ||
}) | ||
}) | ||
|
||
describe('given a slotted Trigger', () => { | ||
it('with onClick on itself', async () => { | ||
const _component: Component = { | ||
components: { | ||
OkuSlot, | ||
}, | ||
setup() { | ||
const click = ref(false) | ||
const onClick = () => { | ||
click.value = true | ||
} | ||
return () => h(OkuSlot, { | ||
onClick, | ||
}, { | ||
default: () => h('button', { | ||
type: 'button', | ||
class: 't', | ||
}, click.value ? 'Clicked' : 'Click me'), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button type="button" class="t">Click me</button>') | ||
await wrapper.trigger('click') | ||
expect(wrapper.html()).equal('<button type="button" class="t">Clicked</button>') | ||
}) | ||
|
||
it('with onClick on the child', async () => { | ||
const _component: Component = { | ||
components: { | ||
OkuSlot, | ||
}, | ||
setup() { | ||
const click = ref(false) | ||
const onClick = () => { | ||
click.value = true | ||
} | ||
return () => h(OkuSlot, {}, { | ||
default: () => h('button', { | ||
type: 'button', | ||
onClick, | ||
}, click.value ? 'Clicked' : 'Click me'), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button type="button">Click me</button>') | ||
await wrapper.trigger('click') | ||
expect(wrapper.html()).equal('<button type="button">Clicked</button>') | ||
}) | ||
|
||
it('with onClick on the child and on itself', async () => { | ||
const _component: Component = { | ||
components: { | ||
OkuSlot, | ||
}, | ||
setup() { | ||
const click = ref(false) | ||
const onClick = () => { | ||
click.value = true | ||
} | ||
return () => h(OkuSlot, { | ||
onClick, | ||
}, { | ||
default: () => h('button', { | ||
type: 'button', | ||
onClick, | ||
}, click.value ? 'Clicked' : 'Click me'), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button type="button">Click me</button>') | ||
await wrapper.trigger('click') | ||
expect(wrapper.html()).equal('<button type="button">Clicked</button>') | ||
}) | ||
|
||
it('with onClick on itself AND undefined onClick on the child', async () => { | ||
const _component: Component = { | ||
components: { | ||
OkuSlot, | ||
}, | ||
setup() { | ||
const click = ref(false) | ||
const onClick = () => { | ||
click.value = true | ||
} | ||
return () => h(OkuSlot, { | ||
onClick, | ||
}, { | ||
default: () => h('button', { | ||
type: 'button', | ||
}, click.value ? 'Clicked' : 'Click me'), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button type="button">Click me</button>') | ||
await wrapper.trigger('click') | ||
expect(wrapper.html()).equal('<button type="button">Clicked</button>') | ||
}) | ||
|
||
it('with undefined onClick on itself AND onClick on the child', async () => { | ||
const _component: Component = { | ||
components: { | ||
OkuSlot, | ||
}, | ||
setup() { | ||
const click = ref(false) | ||
const onClick = () => { | ||
click.value = true | ||
} | ||
return () => h(OkuSlot, {}, { | ||
default: () => h('button', { | ||
type: 'button', | ||
onClick, | ||
}, click.value ? 'Clicked' : 'Click me'), | ||
}) | ||
}, | ||
} | ||
|
||
const wrapper = mount(_component) | ||
expect(wrapper.html()).equal('<button type="button">Click me</button>') | ||
await wrapper.trigger('click') | ||
expect(wrapper.html()).equal('<button type="button">Clicked</button>') | ||
}) | ||
}) |
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,55 @@ | ||
import { cloneVNode, createVNode, defineComponent, mergeProps } from 'vue' | ||
import { useComposeRefs, useForwardRef } from '@oku-ui/use-composable' | ||
import { isSlottable } from './utils' | ||
|
||
const NAME = 'OkuSlot' | ||
|
||
const OkuSlot = defineComponent({ | ||
name: NAME, | ||
inheritAttrs: false, | ||
setup(props, { attrs, slots }) { | ||
const forwarded = useForwardRef() | ||
const composedRefs = useComposeRefs(forwarded) | ||
|
||
return () => { | ||
const defaultSlot = slots.default?.() | ||
const slottable = defaultSlot?.find(isSlottable) | ||
|
||
if (slottable && defaultSlot?.length) { | ||
// TODO: default TS type problem | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-expect-error | ||
const newParentElement = slottable.children?.default?.()[0] | ||
|
||
const newChildren = defaultSlot.map((child) => { | ||
if (child === slottable) | ||
return newParentElement.children | ||
|
||
else return child | ||
}) | ||
|
||
return createVNode(newParentElement.type, { | ||
...mergeProps(attrs, props, newParentElement.props), ref: composedRefs, | ||
}, { | ||
default: () => newChildren, | ||
}) | ||
} | ||
else if (slots.default) { | ||
return cloneVNode(slots.default?.()[0], { ...mergeProps(attrs, props), ref: composedRefs }, true) | ||
} | ||
else { | ||
return null | ||
} | ||
} | ||
}, | ||
}) | ||
|
||
const OkuSlottable = defineComponent({ | ||
name: 'OkuSlottable', | ||
inheritAttrs: false, | ||
setup(_, { slots }) { | ||
return slots.default || (() => null) // Ensure it returns a function even if the default slot is not provided | ||
}, | ||
}) | ||
|
||
export { OkuSlot, OkuSlottable } |
Oops, something went wrong.