Skip to content

Commit

Permalink
feat(presence): new component (#219)
Browse files Browse the repository at this point in the history
* feat: presence init

* fix: pnpm lock

* fix: presence

* fix: typo

* delete: test.vue
  • Loading branch information
productdevbook committed Jul 19, 2023
1 parent 82fd8d3 commit 597113f
Show file tree
Hide file tree
Showing 15 changed files with 665 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@oku-ui/collapsible": "workspace:^",
"@oku-ui/label": "workspace:^",
"@oku-ui/popper": "workspace:^",
"@oku-ui/presence": "workspace:^",
"@oku-ui/primitive": "workspace:^",
"@oku-ui/progress": "workspace:^",
"@oku-ui/provide": "workspace:^",
Expand Down
11 changes: 11 additions & 0 deletions packages/components/presence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# `presence`

## Installation

```sh
$ pnpm add @oku-ui/presence
```

## Usage

soon docs
12 changes: 12 additions & 0 deletions packages/components/presence/build.config.ts
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,
})
49 changes: 49 additions & 0 deletions packages/components/presence/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@oku-ui/presence",
"type": "module",
"version": "0.1.0",
"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/presence"
},
"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"
}
}
3 changes: 3 additions & 0 deletions packages/components/presence/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
OkuPresence,
} from './presence'
117 changes: 117 additions & 0 deletions packages/components/presence/src/presence.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { describe, expect, it } from 'vitest'
import { type Component, ref } from 'vue'
import { mount } from '@vue/test-utils'
import { OkuPresence } from './presence'

describe('presence', async () => {
it('close content', async () => {
const component = {
components: {
OkuPresence,
},
template: `
<div>
<button @click="toggle">
toggle - {{ open }}
</button>
<OkuPresence :present="open">
<div>
content
</div>
</OkuPresence>
</div>
`,
setup() {
const open = ref(false)
const toggle = () => {
open.value = !open.value
}
return {
open,
toggle,
}
},
} as Component
const wrapper = mount(component, {})
expect(wrapper.html()).toContain(`<div><button> toggle - false</button>
<!---->
</div>`)
})

it('open content', async () => {
const component = {
components: {
OkuPresence,
},
template: `
<div>
<button @click="toggle">
toggle - {{ open }}
</button>
<OkuPresence :present="open">
<div>
content
</div>
</OkuPresence>
</div>
`,
setup() {
const open = ref(false)
const toggle = () => {
open.value = !open.value
}
return {
open,
toggle,
}
},
} as Component
const wrapper = mount(component, {})
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain(`<div><button> toggle - true</button>
<div present="true"> content </div>
</div>`)
})

it('open content', async () => {
const component = {
components: {
OkuPresence,
},
template: `
<div>
<button @click="toggle">
toggle - {{ open }}
</button>
<OkuPresence :present="open" class="text-white">
<div>
content
</div>
</OkuPresence>
</div>
`,
setup() {
const open = ref(false)
const toggle = () => {
open.value = !open.value
}
return {
open,
toggle,
}
},
} as Component
const wrapper = mount(component, {})
expect(wrapper.html()).toContain(`<div><button> toggle - false</button>
<!---->
</div>`)

await wrapper.find('button').trigger('click')

expect(wrapper.html()).toContain(`<div><button> toggle - true</button>
<div present="true" class="text-white"> content </div>
</div>`)
})

// TODO: add transition test
})
74 changes: 74 additions & 0 deletions packages/components/presence/src/presence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { Directive } from 'vue'
import { defineComponent, h, ref, toRefs, withDirectives } from 'vue'
import { syncRef } from '@oku-ui/use-composable'
import { usePresence } from './usePresence'

interface PresenceProps {
present: boolean
}

const NAME = 'OkuPresence'

const presence = defineComponent({
name: NAME,
inheritAttrs: false,
props: {
present: {
type: Boolean,
default: false,
},
},
setup(props, { slots, attrs }) {
const { present } = toRefs(props)
const elementRef = ref<HTMLElement>()

const element: Directive = {
created(el) {
const { isPresent } = usePresence(present, el)
syncRef(isPresent, elementRef, { direction: 'ltr' })
},
}

return () => {
const children = slots.default?.()

if (children?.length === 1) {
const [firstChild] = children || []

const directVNodeChildren = withDirectives(
h(
firstChild,
{
present: present.value,
...attrs,
},
),
[
[element],
])

return present.value ? directVNodeChildren : null
}
else {
throw new Error(
[
`Now you can only pass one child to \`${NAME}\`.`,
'',
'Note: All components accepting `Presence` expect only one direct child of valid VNode type.',
'You can apply a few solutions:',
[
'Provide a single child element so that we can forward the props onto that element.',
'Ensure the first child is an actual element instead of a raw text node or comment node.',
]
.map(line => ` - ${line}`)
.join('\n'),
].join('\n'),
)
}
}
},
})

const OkuPresence = presence as typeof presence & (new () => { $props: PresenceProps })

export { OkuPresence }
108 changes: 108 additions & 0 deletions packages/components/presence/src/stories/PresenceDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!-- eslint-disable no-console -->
<script setup lang="ts">
import { ref, watch } from 'vue'
import { OkuPresence } from '@oku-ui/presence'
export interface OkuPresenceProps {
template: '#1' | '#2' | '#3'
allshow?: boolean
}
withDefaults(defineProps<OkuPresenceProps>(), {
template: '#1',
})
const open = ref(false)
function toggle() {
open.value = !open.value
}
const element = ref<HTMLElement>()
watch(element, () => {
console.log('element', element.value)
})
function handleToggleVisibility() {
console.log(element.value, 'handleToggleVisibility')
const node = element.value
if (node) {
if (node.style.display === 'none')
node.style.display = 'block'
else
node.style.display = 'none'
}
}
</script>

<template>
<div class="cursor-default inline-block">
<div v-if="template === '#1' || allshow" class="flex flex-col">
<button @click="toggle">
toggle - {{ open }}
</button>
<OkuPresence ref="element" :present="open">
<div>
content
</div>
</OkuPresence>
</div>
<div v-if="template === '#2' || allshow" class="flex flex-col">
<button @click="toggle">
toggle - {{ open }}
</button>
<Transition name="bounce">
<OkuPresence :present="open">
<div>
content
</div>
</OkuPresence>
</Transition>
</div>
<div v-if="template === '#3' || allshow" class="flex flex-col">
<form class="flex space-x-4 mb-10">
<fieldset>
<legend>Mount</legend>
<button type="button" @click="toggle">
toggle
</button>
</fieldset>
<fieldset>
<legend>Visibility (triggers cancel event)</legend>
<button type="button" @click="handleToggleVisibility">
toggle
</button>
</fieldset>
</form>

<OkuPresence :present="open">
<div ref="element">
content
</div>
</OkuPresence>
</div>
</div>
</template>

<style scoped lang="postcss">
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
</style>
Loading

0 comments on commit 597113f

Please sign in to comment.