Skip to content

Commit

Permalink
Toggle Primitive, Toggle, ToggleGroup Refactor (#576)
Browse files Browse the repository at this point in the history
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
kotAPI and coderabbitai[bot] authored Nov 26, 2024
1 parent a3b6ce6 commit f9a21a0
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 100 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"./themes/default.css": "./dist/themes/default.css",
"./themes/tailwind-presets/default.js": "./dist/themes/tailwind-presets/default.js",
"./Accordion": "./dist/components/Accordion.js",
"./AlertDialog": "./dist/components/AlertDialog.js",
"./Avatar": "./dist/components/Avatar.js",
"./AvatarGroup": "./dist/components/AvatarGroup.js",
"./Badge": "./dist/components/Badge.js",
"./BlockQuote": "./dist/components/BlockQuote.js",
"./Button": "./dist/components/Button.js",
Expand All @@ -22,6 +24,7 @@
"./Link": "./dist/components/Link.js",
"./Progress": "./dist/components/Progress.js",
"./Quote": "./dist/components/Quote.js",
"./RadioGroup": "./dist/components/RadioGroup.js",
"./Separator": "./dist/components/Separator.js",
"./Skeleton": "./dist/components/Skeleton.js",
"./Strong": "./dist/components/Strong.js",
Expand Down
1 change: 0 additions & 1 deletion src/components/ui/Accordion/fragments/AccordionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ const AccordionItem: React.FC<AccordionItemProps> = ({ children, value, classNam
data-state={isOpen ? 'open' : 'closed'}
data-rad-ui-batch-element
{...shouldAddFocusDataAttribute ? { 'data-rad-ui-focus-element': '' } : {}}

>
{children}
</div>
Expand Down
28 changes: 16 additions & 12 deletions src/components/ui/Toggle/Toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React, { useState } from 'react';

import { customClassSwitcher } from '~/core';

import ButtonPrimitive from '~/core/primitives/Button';
import TogglePrimitive from '~/core/primitives/Toggle';

const COMPONENT_NAME = 'Toggle';

export type ToggleProps = {
defaultPressed? : boolean | false ;
pressed : boolean;
defaultPressed?: boolean;
pressed: boolean;
customRootClass? : string;
disabled? : boolean;
children? : React.ReactNode;
Expand All @@ -18,18 +18,21 @@ export type ToggleProps = {
};

const Toggle: React.FC<ToggleProps> = ({
defaultPressed,
defaultPressed = false,
customRootClass = '',
children,
className = '',
pressed,
pressed = false,
onChange,
...props
}) => {
if (typeof pressed !== 'boolean') {
throw new Error('Toggle: pressed prop must be a boolean');
}

const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);

const [isPressed, setIsPressed] = useState(pressed || defaultPressed);
const [isPressed, setIsPressed] = useState(pressed);

const handlePressed = () => {
const updatedPressed = !isPressed;
Expand All @@ -38,15 +41,16 @@ const Toggle: React.FC<ToggleProps> = ({
};

return (

<ButtonPrimitive
className={`${rootClass}`} onClick ={handlePressed}

<TogglePrimitive
className={`${rootClass}`}
pressed={isPressed}
onPressedChange={handlePressed}
data-state={isPressed ? 'on' : 'off'}
type='button'
data-disabled={props.disabled ? '' : undefined}
aria-pressed={pressed} {...props}>
{...props}>
{children}
</ButtonPrimitive>
</TogglePrimitive>
);
};

Expand Down
8 changes: 7 additions & 1 deletion src/components/ui/ToggleGroup/contexts/toggleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { createContext } from 'react';

export const ToggleContext = createContext({});
export type ToggleContextType = {
type: 'single' | 'multiple';
activeToggles: any[];
setActiveToggles: (toggles: any[]) => void;
};

export const ToggleContext = createContext<ToggleContextType>({});
39 changes: 30 additions & 9 deletions src/components/ui/ToggleGroup/fragments/ToggleGroupRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
import React, { useState } from 'react';
import React, { useState, useRef } from 'react';

import { customClassSwitcher } from '~/core';
import { getAllBatchElements, getNextBatchItem, getPrevBatchItem } from '~/core/batches';

import { ToggleContext } from '../contexts/toggleContext';

const ToggleGroupRoot = ({ type = 'multiple', className = '', customRootClass = '', componentName = '', value = null, children }:any) => {
const ToggleGroupRoot = ({ type = 'multiple', className = '', loop = true, customRootClass = '', componentName = '', value = null, children }:any) => {
const rootClass = customClassSwitcher(customRootClass, componentName);

const toggleGroupRef = useRef(null);
// value can be either a string or an array of strings
// if its null, then no toggles are active

const [activeToggles, setActiveToggles] = useState(value || []);

const nextItem = () => {
const batches = getAllBatchElements(toggleGroupRef?.current);
const nextItem = getNextBatchItem(batches, loop);
if (nextItem) {
nextItem?.focus();
}
};

const previousItem = () => {
const batches = getAllBatchElements(toggleGroupRef?.current);
const prevItem = getPrevBatchItem(batches, loop);
if (prevItem) {
prevItem?.focus();
}
};

const sendValues = {
nextItem,
previousItem,
activeToggles,
setActiveToggles,
type
};

return (
<div className={`${rootClass} ${className}`} role="group">
<div className={`${rootClass} ${className}`} role="group" ref={toggleGroupRef}>
<ToggleContext.Provider
value={{
activeToggles,
setActiveToggles,
type
}}>
value={sendValues}>
{children}
</ToggleContext.Provider>
</div>
Expand Down
73 changes: 54 additions & 19 deletions src/components/ui/ToggleGroup/fragments/ToggleItem.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';

import { ToggleContext } from '../contexts/toggleContext';
import ButtonPrimitive from '~/core/primitives/Button';
import TogglePrimitive from '~/core/primitives/Toggle';

const ToggleItem = ({ children, value = null, ...props }:any) => {
const toggleContext = useContext(ToggleContext);
export type ToggleItemProps = {
children: React.ReactNode;
value: any;
props: any;
};

const ToggleItem = ({ children, value = null, ...props }:ToggleItemProps) => {
const { type, activeToggles, setActiveToggles, nextItem, previousItem } = useContext(ToggleContext);
const isActive = activeToggles?.includes(value);

const [isFocused, setIsFocused] = useState(false);

const type = toggleContext?.type;
const ariaProps:any = {};
const dataProps:any = {};


const isActive = toggleContext?.activeToggles?.includes(value);
const handleFocus = () => {
setIsFocused(true);
};

const handleBlur = () => {
setIsFocused(false);
};

const handleToggleSelect = () => {
let activeToggleArray = toggleContext?.activeToggles || [];
let activeToggleArray = activeToggles || [];

// For Single Case
if (type === 'single') {
if (isActive) {

toggleContext?.setActiveToggles([]);
setActiveToggles([]);
return;
} else {
toggleContext?.setActiveToggles([value]);
setActiveToggles([value]);
return;
}
}
Expand All @@ -35,21 +49,42 @@ const ToggleItem = ({ children, value = null, ...props }:any) => {
}
}

toggleContext?.setActiveToggles(activeToggleArray);
setActiveToggles(activeToggleArray);
};

const handleKeyDown = (e:any) => {
if (e.key === 'ArrowRight') {
// prevent scrolling when pressing arrow keys
e.preventDefault();
nextItem();
}
if (e.key === 'ArrowLeft') {
// prevent scrolling when pressing arrow keys
e.preventDefault();
previousItem();
}
};

if (isActive) {
props['aria-pressed'] = 'true';
ariaProps['aria-pressed'] = 'true';
dataProps['data-active'] = 'true';
} else {
props['aria-pressed'] = 'false';
ariaProps['aria-pressed'] = 'false';
dataProps['data-active'] = 'false';
}

return <ButtonPrimitive
className={`${isActive ? 'bg-blue-600' : ''}`} onClick={() => {
handleToggleSelect();
}}
return <TogglePrimitive
onClick={handleToggleSelect}
onFocus={handleFocus}
onBlur={handleBlur}
{...ariaProps}
{...dataProps}
data-rad-ui-batch-element
onKeyDown={handleKeyDown}
{...isFocused ? { 'data-rad-ui-focus-element': '' } : {}}
{...props}
>{children}</ButtonPrimitive>;

>{children}</TogglePrimitive>;
};

export default ToggleItem;
20 changes: 15 additions & 5 deletions src/core/batches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,36 @@ export const getActiveBatchItem = (batches: NodeList) => {
return activeItem as HTMLElement | null;
};

export const getNextBatchItem = (batches: NodeList): Element => {
export const getNextBatchItem = (batches: NodeList, loop = false): Element => {
const activeItem = getActiveBatchItem(batches) as HTMLElement | null;
// get the next item, return it if it is not the last item
// Try to get the next sibling element
const nextItem = activeItem?.nextElementSibling;

if (nextItem) {
return nextItem;
}

// if it is the last item, return the last item
if (loop) {
// When at the end and looping is enabled, return the first item
return batches[0] as HTMLElement;
}

// When at the end and looping is disabled, stay on the last item
return batches[batches.length - 1] as HTMLElement;
};

export const getPrevBatchItem = (batches: NodeList) => {
export const getPrevBatchItem = (batches: NodeList, loop = false) => {
const activeItem = getActiveBatchItem(batches) as HTMLElement | null;
// get the next item, return it if it is not the last item
const prevItem = activeItem?.previousElementSibling;
if (prevItem) {
return prevItem;
}

// if it is the last item, return the last item
if (loop) {
// if it is the last item, return the last item
return batches[batches.length - 1] as HTMLElement;
}
// if it is the last item, return the first item
return batches[0] as HTMLElement;
};

This file was deleted.

24 changes: 0 additions & 24 deletions src/core/primitives/Toggle/fragments/TogglePrimitiveRoot.tsx

This file was deleted.

Loading

0 comments on commit f9a21a0

Please sign in to comment.