-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Pranay Kothapalli <[email protected]>
- Loading branch information
1 parent
c3aa440
commit cea0ec7
Showing
14 changed files
with
459 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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,37 @@ | ||
import React from "react"; | ||
import DisclosureRoot from "./fragments/DisclosureRoot"; | ||
import DisclosureItem from "./fragments/DisclosureItem"; | ||
import DisclosureTrigger from "./fragments/DisclosureTrigger"; | ||
import DisclosureContent from "./fragments/DisclosureContent"; | ||
|
||
export type DisclosureProps = { | ||
|
||
items:{title:string, content: React.ReactNode}[] | ||
} | ||
|
||
const Disclosure = ({items}:DisclosureProps) => { | ||
return( | ||
|
||
<DisclosureRoot> | ||
{items.map((item,index) => ( | ||
<DisclosureItem key={index} value={index}> | ||
<DisclosureTrigger> | ||
{item.title} | ||
</DisclosureTrigger> | ||
<DisclosureContent> | ||
{item.content} | ||
</DisclosureContent> | ||
</DisclosureItem> | ||
|
||
))} | ||
|
||
</DisclosureRoot> | ||
) | ||
} | ||
|
||
Disclosure.Root = DisclosureRoot; | ||
Disclosure.Item = DisclosureItem; | ||
Disclosure.Trigger = DisclosureTrigger; | ||
Disclosure.Content = DisclosureContent; | ||
|
||
export default Disclosure; |
13 changes: 13 additions & 0 deletions
13
src/components/ui/Disclosure/contexts/DisclosureContext.tsx
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 @@ | ||
import {createContext} from "react"; | ||
|
||
export type DisclosureContextType = { | ||
rootClass: string; | ||
activeItem: number | null; | ||
setActiveItem: (item: number | null) => void; | ||
focusItem: HTMLElement | null; | ||
setFocusItem: (node: HTMLElement | null) => void; | ||
disclosureRef: any; | ||
focusNextItem: () => void; | ||
focusPrevItem: () => void; | ||
} | ||
export const DisclosureContext = createContext<DisclosureContextType>({} as DisclosureContextType) |
11 changes: 11 additions & 0 deletions
11
src/components/ui/Disclosure/contexts/DisclosureItemContext.tsx
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,11 @@ | ||
import {createContext} from "react"; | ||
|
||
export type DisclosureItemContextType = { | ||
itemValue: number; | ||
setItemValue: (value: number) => void; | ||
handleBlurEvent: () => void; | ||
handleClickEvent: () => void; | ||
handleFocusEvent: () => void; | ||
} | ||
|
||
export const DisclosureItemContext = createContext<DisclosureItemContextType>({} as DisclosureItemContextType) |
28 changes: 28 additions & 0 deletions
28
src/components/ui/Disclosure/fragments/DisclosureContent.tsx
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,28 @@ | ||
import React, { useContext, useState } from "react"; | ||
import clsx from "clsx"; | ||
import { DisclosureContext } from "../contexts/DisclosureContext"; | ||
import { DisclosureItemContext } from "../contexts/DisclosureItemContext"; | ||
|
||
export type DisclosureContentProps = { | ||
children: React.ReactNode; | ||
className?: string; | ||
|
||
} | ||
|
||
const DisclosureContent = ({children, className=''}:DisclosureContentProps) => { | ||
|
||
const {activeItem,rootClass} = useContext(DisclosureContext) | ||
const {itemValue} = useContext(DisclosureItemContext) | ||
return( | ||
<div | ||
className={clsx(`${rootClass}-content`, className)} | ||
hidden={activeItem !== itemValue} | ||
role="region" | ||
aria-hidden={activeItem !== itemValue} | ||
> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
export default DisclosureContent |
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,85 @@ | ||
import React, { useContext, useEffect, useRef, useState, useId } from "react"; | ||
import { DisclosureContext } from "../contexts/DisclosureContext"; | ||
import { DisclosureItemContext } from "../contexts/DisclosureItemContext"; | ||
import { clsx } from "clsx"; | ||
|
||
export type DisclosureItemProps = { | ||
children: React.ReactNode; | ||
className?: string; | ||
value: number; | ||
} | ||
|
||
const DisclosureItem = ({children, className='', value}:DisclosureItemProps) => { | ||
|
||
const disclosureItemRef = useRef<HTMLDivElement>(null) | ||
const { activeItem, rootClass, focusItem } = useContext(DisclosureContext) | ||
|
||
const [itemValue, setItemValue] = useState<number>(value) | ||
const [isOpen, setIsOpen] = useState(false) | ||
|
||
useEffect(() => { | ||
setIsOpen(activeItem === itemValue) | ||
|
||
}, [activeItem, itemValue]); | ||
|
||
const id = useId() | ||
let shouldAddFocusDataAttribute = false; | ||
|
||
const focusItemId = focusItem?.id; | ||
if (focusItemId === `disclosure-data-item-${id}`) | ||
{ | ||
shouldAddFocusDataAttribute = true; | ||
} | ||
|
||
const focusCurrentItem = () => { | ||
const elem = disclosureItemRef?.current | ||
|
||
if (elem) { | ||
elem.setAttribute('data-rad-ui-focus-element', '') | ||
} | ||
|
||
} | ||
|
||
const handleBlurEvent = () => { | ||
const elem = disclosureItemRef?.current; | ||
|
||
if (elem) { | ||
elem.removeAttribute('data-rad-ui-focus-element') | ||
} | ||
} | ||
|
||
const handleClickEvent = () => { | ||
focusCurrentItem() | ||
} | ||
|
||
const handleFocusEvent = () => { | ||
focusCurrentItem() | ||
} | ||
return( | ||
<DisclosureItemContext.Provider | ||
value={{ | ||
itemValue, | ||
setItemValue, | ||
handleBlurEvent, | ||
handleClickEvent, | ||
handleFocusEvent | ||
}}> | ||
<div | ||
className={clsx(`${rootClass}-item`, className)} | ||
ref={disclosureItemRef} | ||
data-state={isOpen ? 'open' : 'closed'} | ||
id={`disclosure-data-item-${id}`} | ||
role="region" | ||
aria-labelledby={`disclosure-trigger-${id}`} | ||
aria-expanded={isOpen} | ||
data-rad-ui-batch-element | ||
{...shouldAddFocusDataAttribute ? {'data-rad-ui-focus-element': ''} : {}} | ||
> | ||
{children} | ||
|
||
</div> | ||
</DisclosureItemContext.Provider> | ||
) | ||
} | ||
|
||
export default DisclosureItem |
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,76 @@ | ||
import React, { useState, useRef } from "react"; | ||
import { customClassSwitcher } from "~/core"; | ||
import { clsx } from "clsx"; | ||
import { DisclosureContext } from "../contexts/DisclosureContext"; | ||
import { getAllBatchElements, getNextBatchItem, getPrevBatchItem } from "~/core/batches"; | ||
|
||
const COMPONENT_NAME = 'Disclosure'; | ||
|
||
export type DisclosureRootProps = { | ||
children: React.ReactNode; | ||
customRootClass?: string; | ||
defaultOpen?: number | null; | ||
'aria-label'?: string; | ||
|
||
} | ||
|
||
const DisclosureRoot = ({ children, customRootClass, 'aria-label': ariaLabel }:DisclosureRootProps) => { | ||
|
||
const disclosureRef = useRef(null) | ||
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME) | ||
|
||
const [activeItem, setActiveItem] = useState<number | null>(null); | ||
const [focusItem, setFocusItem] = useState<HTMLElement | null>(null); | ||
|
||
const focusNextItem = () => { | ||
const batches = getAllBatchElements(disclosureRef.current!) | ||
const nextItem = getNextBatchItem(batches) | ||
setFocusItem(nextItem as HTMLElement) | ||
|
||
if (nextItem){ | ||
const button = nextItem.querySelector('button') | ||
button?.focus() | ||
} | ||
} | ||
|
||
const focusPrevItem = () => { | ||
const batches = getAllBatchElements(disclosureRef?.current!) | ||
const prevItem = getPrevBatchItem(batches) | ||
setFocusItem(prevItem as HTMLElement) | ||
|
||
if (prevItem){ | ||
const button = prevItem.querySelector('button') | ||
button?.focus() | ||
} | ||
} | ||
|
||
return( | ||
|
||
<DisclosureContext.Provider | ||
value={{ | ||
rootClass, | ||
activeItem, | ||
setActiveItem, | ||
disclosureRef, | ||
focusNextItem, | ||
focusPrevItem, | ||
focusItem, | ||
setFocusItem | ||
|
||
}}> | ||
|
||
<div | ||
className={clsx(`${rootClass}-root`)} | ||
ref={disclosureRef} | ||
role="region" | ||
aria-label={ariaLabel} | ||
data-testid='disclosure-root' | ||
> | ||
|
||
{children} | ||
</div> | ||
</DisclosureContext.Provider> | ||
) | ||
} | ||
|
||
export default DisclosureRoot |
56 changes: 56 additions & 0 deletions
56
src/components/ui/Disclosure/fragments/DisclosureTrigger.tsx
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,56 @@ | ||
import React, { useContext } from "react" | ||
import clsx from "clsx"; | ||
import { DisclosureContext } from "../contexts/DisclosureContext"; | ||
import { DisclosureItemContext } from "../contexts/DisclosureItemContext"; | ||
|
||
export type DisclosureTriggerProps = { | ||
children: React.ReactNode; | ||
className?: string; | ||
} | ||
|
||
|
||
const DisclosureTrigger = ({ children, className }:DisclosureTriggerProps) => { | ||
|
||
const { activeItem, setActiveItem, rootClass, focusNextItem, focusPrevItem } = useContext(DisclosureContext) | ||
const { itemValue, handleBlurEvent, handleClickEvent, handleFocusEvent} = useContext(DisclosureItemContext) | ||
|
||
const handleDisclosure = () => { | ||
|
||
setActiveItem(activeItem === itemValue ? null : itemValue) | ||
handleClickEvent(); | ||
} | ||
|
||
const onFocusHandler = () => { | ||
handleFocusEvent() | ||
} | ||
|
||
return( | ||
|
||
<button | ||
type='button' | ||
className={clsx(`${rootClass}-trigger`, className)} | ||
onClick={handleDisclosure} | ||
onBlur={handleBlurEvent} | ||
onFocus={onFocusHandler} | ||
onKeyDown={(e) => { | ||
if (e.key === 'ArrowDown') { | ||
e.preventDefault() | ||
focusNextItem() | ||
} | ||
|
||
if (e.key === 'ArrowUp') { | ||
e.preventDefault() | ||
focusPrevItem() | ||
} | ||
}} | ||
aria-expanded={activeItem === itemValue} | ||
aria-haspopup='true' | ||
> | ||
|
||
{children} | ||
</button> | ||
|
||
) | ||
} | ||
|
||
export default DisclosureTrigger |
Oops, something went wrong.