Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle Primitive, Toggle, ToggleGroup Refactor #576

Merged
merged 17 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
kotAPI marked this conversation as resolved.
Show resolved Hide resolved
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');
}
kotAPI marked this conversation as resolved.
Show resolved Hide resolved

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;
};
kotAPI marked this conversation as resolved.
Show resolved Hide resolved

export const ToggleContext = createContext<ToggleContextType>({});
kotAPI marked this conversation as resolved.
Show resolved Hide resolved
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);
kotAPI marked this conversation as resolved.
Show resolved Hide resolved

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;
};

kotAPI marked this conversation as resolved.
Show resolved Hide resolved
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;
};
kotAPI marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

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

This file was deleted.

Loading
Loading