Skip to content

Commit

Permalink
feat(toggle): new component (#111)
Browse files Browse the repository at this point in the history
* feat: (Oku toggle)

* chore: (cleanups)

* [autofix.ci] apply automated fixes

* feat: use-composable add

* nuxt playground in add component

* readme update
  • Loading branch information
faithfulojebiyi committed May 17, 2023
1 parent 495cebd commit 659fa23
Show file tree
Hide file tree
Showing 17 changed files with 440 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Enter the component you want most in the components, leave the emojis and follow
- [ ] [Switch](https://github.com/oku-ui/primitives/issues/22)
- [ ] [Tabs](https://github.com/oku-ui/primitives/issues/23)
- [ ] [Toast](https://github.com/oku-ui/primitives/issues/24)
- [ ] [Toggle](https://github.com/oku-ui/primitives/issues/25)
- [x] [Toggle](https://github.com/oku-ui/primitives/issues/25)
- [ ] [Toggle Group](https://github.com/oku-ui/primitives/issues/26)
- [ ] [Toolbar](https://github.com/oku-ui/primitives/issues/27)
- [ ] [Tooltip](https://github.com/oku-ui/primitives/issues/28)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@oku-ui/label": "workspace:^",
"@oku-ui/progress": "workspace:^",
"@oku-ui/separator": "workspace:^",
"@oku-ui/toggle": "workspace:^",
"@storybook/addon-essentials": "^7.0.10",
"@storybook/addon-interactions": "^7.0.10",
"@storybook/addon-links": "^7.0.10",
Expand Down
10 changes: 10 additions & 0 deletions packages/components/toggle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `@oku-ui/toggle`

## Installation

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

## Usage
...
12 changes: 12 additions & 0 deletions packages/components/toggle/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,
})
44 changes: 44 additions & 0 deletions packages/components/toggle/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@oku-ui/toggle",
"type": "module",
"version": "0.0.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/toggle"
},
"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"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch"
},
"peerDependencies": {
"vue": "^3.3.1"
},
"dependencies": {
"@oku-ui/primitive": "workspace:^",
"@oku-ui/provide": "workspace:^",
"@oku-ui/use-composable": "workspace:^",
"@oku-ui/utils": "workspace:^"
},
"devDependencies": {
"tsconfig": "workspace:^"
}
}
9 changes: 9 additions & 0 deletions packages/components/toggle/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export {
OkuToggle,
} from './toggle'

export type {
ToggleProps,
ToggleElement,
ToggleRef,
} from './toggle'
93 changes: 93 additions & 0 deletions packages/components/toggle/src/stories/ToggleDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<script setup lang="ts">
import type { ToggleProps } from '@oku-ui/toggle'
import { OkuToggle } from '@oku-ui/toggle'
import { ref } from 'vue'
export interface IToggleProps extends ToggleProps {
template?: '#1' | '#2' | '#3'
allshow?: boolean
}
withDefaults(defineProps<IToggleProps>(), {
})
const pressed = ref(true)
function setPressed(value: boolean) {
pressed.value = value
}
</script>

<template>
<div>
<div v-if="template === '#1' || allshow" class="w-[300px]">
<div>
<h1>Oku Default toggle</h1>
<div class="max-w-xl mx-auto h-full items-center justify-center">
<OkuToggle
id="toggle"
aria-label="Toggle italic"
class="w-10 h-10 border-2 rounded text-black flex items-center justify-center data-[state=on]:border-green-500 data-[state=off]:border-gray-900 hover:bg-gray-400"
>
I
</OkuToggle>
</div>
</div>
<div>
<h1>Oku toggle controlled (active)</h1>
<div class="max-w-xl mx-auto h-full items-center justify-center">
<OkuToggle
id="toggle"
:pressed="pressed"
:on-pressed-change="setPressed"
aria-label="Toggle italic"
class="w-10 h-10 border-2 rounded text-black flex items-center justify-center data-[state=on]:border-green-500 data-[state=off]:border-gray-900 hover:bg-gray-400"
>
{{ pressed ? 'on' : 'off' }}
</OkuToggle>
</div>
</div>
<div>
<h1>Oku toggle controlled ative true</h1>
<div class="max-w-xl mx-auto h-full items-center justify-center">
<OkuToggle
id="toggle"
:pressed="true"
aria-label="Toggle italic"
class="w-10 h-10 border-2 rounded text-black flex items-center justify-center data-[state=on]:border-green-500 data-[state=off]:border-gray-900 hover:bg-gray-400"
>
t
</OkuToggle>
</div>
</div>
<div>
<h1>Oku toggle controlled ative false</h1>
<div class="max-w-xl mx-auto h-full items-center justify-center">
<OkuToggle
id="toggle"
:pressed="false"
aria-label="Toggle italic"
class="w-10 h-10 border-2 rounded text-black flex items-center justify-center data-[state=on]:border-green-500 data-[state=off]:border-gray-900 hover:bg-gray-400"
>
f
</OkuToggle>
</div>
</div>
<div>
<h1>Oku toggle controlled disabled</h1>
<div class="max-w-xl mx-auto h-full items-center justify-center">
<OkuToggle
id="toggle"
:disabled="true"
:pressed="false"
aria-label="Toggle italic"
class="w-10 h-10 border-2 rounded flex items-center justify-center data-[state=on]:border-green-500 hover:bg-gray-400 data-[disabled]:border-gray-100"
>
f
</OkuToggle>
</div>
</div>
</div>
</div>
</template>
58 changes: 58 additions & 0 deletions packages/components/toggle/src/stories/toggle.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/vue3'

import type { IToggleProps } from './ToggleDemo.vue'

import OkuToggleComponent from './ToggleDemo.vue'

interface StoryProps extends IToggleProps {

}

const meta = {
title: 'Components/Toggle',
args: {
template: '#1',
},
component: OkuToggleComponent,
tags: ['autodocs'],
} satisfies Meta<typeof OkuToggleComponent> & {
args: StoryProps
}

export default meta
type Story = StoryObj<typeof meta> & {
args: StoryProps
}

export const Styled: Story = {
args: {
template: '#1',
allshow: true,
},

render: (args: any) => ({
components: { OkuToggleComponent },
setup() {
return { args }
},
template: `
<OkuToggleComponent v-bind="args" />
`,
}),
}

export const Template1: Story = {
args: {
template: '#2',
},

render: (args: any) => ({
components: { OkuToggleComponent },
setup() {
return { args }
},
template: `
<OkuToggleComponent v-bind="args" />
`,
}),
}
38 changes: 38 additions & 0 deletions packages/components/toggle/src/toggle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest'
import { mount, shallowMount } from '@vue/test-utils'
import { OkuToggle } from './toggle'

describe('OkuToggle', () => {
const wrapper = mount(OkuToggle)

it('renders correctly', () => {
expect(wrapper.html()).toBe('<button type="button" aria-pressed="false" data-state="off"></button>')
})

it('Active state', () => {
const wrapper = shallowMount(OkuToggle, {
propsData: {
defaultPressed: true,
},
})
expect(wrapper.attributes('aria-pressed')).toBe('true')
})

it('Inactive state', () => {
const wrapper = shallowMount(OkuToggle, {
propsData: {
defaultPressed: false,
},
})
expect(wrapper.attributes('aria-pressed')).toBe('false')
})

it('Toggle state', async () => {
const wrapper = mount(OkuToggle, {
})
await wrapper.trigger('click')
expect(wrapper.attributes('aria-pressed')).toBe('true')
await wrapper.trigger('click')
expect(wrapper.attributes('aria-pressed')).toBe('false')
})
})
97 changes: 97 additions & 0 deletions packages/components/toggle/src/toggle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { PropType, Ref } from 'vue'
import { defineComponent, h, toRefs } from 'vue'
import { Primitive } from '@oku-ui/primitive'
import type { ElementType, MergeProps, PrimitiveProps, RefElement } from '@oku-ui/primitive'
import { composeEventHandlers } from '@oku-ui/utils'
import { useControllableRef, useRef } from '@oku-ui/use-composable'

const TOGGLE_NAME = 'Toggle'

type ToggleElement = ElementType<'button'>

interface ToggleProps extends PrimitiveProps {
/**
* The controlled state of the toggle.
*/
pressed?: boolean
/**
* The state of the toggle when initially rendered. Use `defaultPressed`
* if you do not need to control the state of the toggle.
* @defaultValue false
*/
defaultPressed?: boolean
/**
* The callback that fires when the state of the toggle changes.
*/
onPressedChange?: (pressed: boolean) => void
}

const Toggle = defineComponent({
name: TOGGLE_NAME,
inheritAttrs: false,
props: {
pressed: {
type: Boolean,
default: undefined,
},
defaultPressed: {
type: Boolean,
default: false,
},
onPressedChange: Function as PropType<(pressed: boolean) => void>,
},

setup(props, { attrs, expose, slots }) {
const { onPressedChange } = props
const { pressed: pressedProp, defaultPressed } = toRefs(props)
const { _ref: toogleRef, refEl: toogleRefEl } = useRef<ToggleElement>()

expose({
innerRef: toogleRefEl,
})

const { state } = useControllableRef({
prop: pressedProp.value,
onChange: onPressedChange,
defaultProp: defaultPressed.value,
})

const { disabled, ...toggleProps } = attrs as ToggleElement

const originalReturn = () => h(
Primitive.button, {
'type': 'button',
'aria-pressed': state.value,
'data-state': state.value ? 'on' : 'off',
'data-disabled': disabled ? '' : undefined,
...toggleProps,
'ref': toogleRef,
'onClick': composeEventHandlers(toggleProps.onClick, () => {
if (!disabled)
state.value = !state.value
}),
},
slots.default && slots.default?.(),
)

return originalReturn as unknown as {
innerRef: Ref<ToggleElement>
}
},
})

type _ToggleProps = MergeProps<ToggleProps, ToggleElement>

type ToggleRef = RefElement<typeof Toggle>

const OkuToggle = Toggle as typeof Toggle & (new () => { $props: _ToggleProps })

export {
OkuToggle,
}

export type {
ToggleProps,
ToggleElement,
ToggleRef,
}
10 changes: 10 additions & 0 deletions packages/components/toggle/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "tsconfig/node16.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": [
"src"
]
}
Loading

0 comments on commit 659fa23

Please sign in to comment.