Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit 1cb3773

Browse files
Merge pull request #180 from TylerAPfledderer/feat/create-use-disclosure
Create `useDisclosure` composable
2 parents fe21223 + 1094799 commit 1cb3773

File tree

6 files changed

+275
-0
lines changed

6 files changed

+275
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<template>
2+
<c-button @click="open">Open Drawer</c-button>
3+
<c-drawer placement="right" v-model="isOpen">
4+
<c-drawer-overlay />
5+
<c-drawer-content>
6+
<c-drawer-header borderBottomWidth="1px">Basic Drawer</c-drawer-header>
7+
<c-drawer-close-button @click="close" />
8+
<c-drawer-body>
9+
<p>Some contents...</p>
10+
<p>Some contents...</p>
11+
<p>Some contents...</p>
12+
</c-drawer-body>
13+
</c-drawer-content>
14+
</c-drawer>
15+
</template>
16+
<script lang="ts" setup>
17+
import { useDisclosure } from "../src/use-disclosure"
18+
19+
const { open, close, isOpen } = useDisclosure()
20+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<c-v-stack gap="4">
3+
<c-button v-bind="buttonProps">This is a button</c-button>
4+
<c-center h="4">
5+
<c-box v-bind="disclosureProps">Toggled On with Props!</c-box>
6+
</c-center>
7+
</c-v-stack>
8+
</template>
9+
<script setup lang="ts">
10+
import { useDisclosure } from "../src/use-disclosure"
11+
12+
const { buttonProps, disclosureProps } = useDisclosure()
13+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<template>
2+
<c-v-stack gap="4">
3+
<c-button @click="toggle">This is a button</c-button>
4+
<c-center h="4">
5+
<c-box :hidden="!isOpen">Toggled On with onToggle!</c-box>
6+
</c-center>
7+
</c-v-stack>
8+
</template>
9+
<script setup lang="ts">
10+
import { useDisclosure } from "../src/use-disclosure"
11+
12+
const { toggle, isOpen } = useDisclosure()
13+
</script>

packages/vue-composables/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from "./use-event-listener"
33
export * from "./use-window-event"
44
export * from "./use-element-stack"
55
export * from "./use-clipboard"
6+
export * from "./use-disclosure"
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import {
2+
computed,
3+
ComputedRef,
4+
HTMLAttributes,
5+
Ref,
6+
ref,
7+
watchEffect,
8+
} from "vue"
9+
import { useId } from "./use-id"
10+
11+
export interface UseDisclosureProps {
12+
/**
13+
* Defines open state from outside dynamic state being passed in.
14+
*
15+
* Overrides `defaultIsOpen` prop.
16+
*/
17+
isOpen?: boolean
18+
/**
19+
* Default state on render. Overriden by `isOpen` prop dynamically
20+
* if outside state should pass in a truthy value
21+
*/
22+
defaultIsOpen?: boolean
23+
/**
24+
* Additional actions to run when the targeted element is closed.
25+
*/
26+
onClose?(): void
27+
/**
28+
* Additional actions to run when the targeted element is opened.
29+
*/
30+
onOpen?(): void
31+
/**
32+
* Custom id to connect the toggle with the targeted element for accessibility.
33+
*
34+
* @default `disclosure-<uid>`
35+
*/
36+
id?: string
37+
}
38+
39+
type ReturnUseDisclosureType = {
40+
/**
41+
* Returns current state
42+
*
43+
* @default false
44+
*/
45+
isOpen: Ref<boolean>
46+
/**
47+
* Actions run when opening targeted element.
48+
*
49+
* If target element is uncontrolled, then it includes toggle open.
50+
*/
51+
open: () => void
52+
/**
53+
* Actions run when closing targeted element.
54+
*
55+
* If target element is uncontrolled, then it includes toggle closed.
56+
*/
57+
close: () => void
58+
/**
59+
* Actions run when toggling open and closed.
60+
*/
61+
toggle: () => void
62+
/**
63+
* Check if external functionality controls the state of the targeted element
64+
*/
65+
isControlled: boolean
66+
/**
67+
* Computed object of Accessibility attributes and toggling event for the toggling element.
68+
*
69+
* `NOTE:` Pass this to the v-bind of the element.
70+
*
71+
* i.e. `v-bind='buttonProps'`
72+
*/
73+
buttonProps: ComputedRef<{
74+
"aria-expanded": HTMLAttributes["aria-expanded"]
75+
"aria-controls": HTMLAttributes["aria-controls"]
76+
onClick: HTMLAttributes["onClick"]
77+
}>
78+
/**
79+
* Computed object of Accessibility attributes to show/hide targeted element and for aria controls.
80+
*
81+
* `NOTE:` Pass this to the v-bind of the element.
82+
*
83+
* i.e. `v-bind='disclosureProps'`
84+
*/
85+
disclosureProps: ComputedRef<{
86+
hidden: HTMLAttributes["hidden"]
87+
id: HTMLAttributes["id"]
88+
}>
89+
}
90+
91+
/**
92+
* Handles common open, close, or toggle scenarios.
93+
*
94+
* It can be used to control feedback components such as `Modal`, `AlertDialog`, `Drawer`, etc.
95+
*/
96+
export function useDisclosure(
97+
props: UseDisclosureProps = {}
98+
): ReturnUseDisclosureType {
99+
const {
100+
isOpen: isOpenProp,
101+
onClose: handleClose,
102+
onOpen: handleOpen,
103+
id: idProp,
104+
defaultIsOpen,
105+
} = props
106+
107+
const isOpenState = ref(defaultIsOpen || false)
108+
109+
const isOpen: ReturnUseDisclosureType["isOpen"] = ref(
110+
isOpenProp !== undefined ? isOpenProp : isOpenState.value
111+
)
112+
113+
const isControlled = isOpenProp !== undefined
114+
115+
const uid = useId()
116+
const id = computed(() => idProp ?? `disclosure-${uid.value}`)
117+
118+
const close = () => {
119+
if (!isControlled) {
120+
isOpenState.value = false
121+
}
122+
handleClose?.()
123+
}
124+
125+
const open = () => {
126+
if (!isControlled) {
127+
isOpenState.value = true
128+
}
129+
handleOpen?.()
130+
}
131+
132+
const toggle = () => (isOpen.value ? close() : open())
133+
134+
const buttonProps: ReturnUseDisclosureType["buttonProps"] = computed(() => ({
135+
"aria-expanded": isOpen.value,
136+
"aria-controls": id.value,
137+
onClick() {
138+
toggle()
139+
},
140+
}))
141+
142+
const disclosureProps: ReturnUseDisclosureType["disclosureProps"] = computed(
143+
() => ({
144+
hidden: !isOpen.value,
145+
id: id.value,
146+
})
147+
)
148+
149+
watchEffect(() => {
150+
isOpen.value = isOpenState.value
151+
})
152+
153+
return {
154+
isOpen,
155+
open,
156+
close,
157+
toggle,
158+
isControlled,
159+
buttonProps,
160+
disclosureProps,
161+
}
162+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import UseDisclosureDrawer from "../examples/use-disclosure-drawer.vue"
2+
import UseDisclosureProps from "../examples/use-disclosure-props.vue"
3+
import UseDisclosureToggle from "../examples/use-disclosure-toggle.vue"
4+
5+
describe("useDisclosure with button/disclosure props", () => {
6+
it("is accessbile", () => {
7+
cy.mount(UseDisclosureProps).checkA11y()
8+
})
9+
10+
it("renders the button props correctly", () => {
11+
cy.mount(UseDisclosureProps)
12+
13+
const button = cy.get("button")
14+
15+
button
16+
.should("have.attr", "aria-expanded", "false")
17+
.and("have.attr", "aria-controls", "disclosure-2")
18+
})
19+
20+
it("toggles the disclosure", () => {
21+
cy.mount(UseDisclosureProps)
22+
23+
cy.get("button").should("have.attr", "aria-expanded", "false")
24+
cy.get("#disclosure-3").should("be.hidden")
25+
26+
cy.get("button")
27+
.invoke("click")
28+
.should("have.attr", "aria-expanded", "true")
29+
30+
cy.get("#disclosure-3").should("not.be.hidden")
31+
32+
cy.get("button")
33+
.invoke("click")
34+
.should("have.attr", "aria-expanded", "false")
35+
cy.get("#disclosure-3").should("be.hidden")
36+
})
37+
})
38+
39+
describe("useDisclosure with onToggle", () => {
40+
it("toggles the text", () => {
41+
cy.mount(UseDisclosureToggle)
42+
43+
cy.contains("div", "Toggled On with onToggle!").should("be.hidden")
44+
45+
cy.get("button").contains("This is a button").invoke("click")
46+
47+
cy.contains("div", "Toggled On with onToggle!").should("not.be.hidden")
48+
})
49+
})
50+
51+
describe("useDisclosure with drawer", () => {
52+
it("toggles drawer", () => {
53+
cy.mount(UseDisclosureDrawer)
54+
55+
cy.get(".chakra-modal__content-container").should("not.exist")
56+
57+
cy.contains("button", "Open Drawer").invoke("click")
58+
59+
cy.get(".chakra-modal__content-container")
60+
.should("exist")
61+
.get(".chakra-modal__close-button")
62+
.invoke("click")
63+
64+
cy.get(".chakra-modal__content-container").should("not.exist")
65+
})
66+
})

0 commit comments

Comments
 (0)