-
-
Notifications
You must be signed in to change notification settings - Fork 80
Changed various UI components. #1451
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
Changes from 37 commits
aa9c55e
a669fa9
68cbe9e
3cf4452
6538fbc
a762a4e
d691484
2eaba1d
cc9490a
32a3ee9
efc5d4a
62adaf2
29742de
51f4a98
49b1443
d5b958d
09bb024
3cc4b08
91860d2
3ea83f2
c9f3dbe
60cb43a
acc7d53
69d31a8
a898422
6f8074d
9233c48
917f349
bcef9be
f327cb2
a67e9d7
286ff6e
11cf9a6
ec790b2
ba2fc95
5c48803
44b0feb
467aee8
993d118
253f995
c18f8de
044dd2b
44e0a29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import PropTypes from 'prop-types'; | ||
| import React from 'react'; | ||
|
|
||
| import {Tooltip} from './Tooltip'; | ||
| import {usePreviousFocus} from '../../hooks/usePreviousFocus'; | ||
|
|
||
| export function ButtonGroupBeta({buttons = [], selectedName, onClick, hasTooltip = true}) { | ||
| return ( | ||
| <div className="flex"> | ||
| <ul className="flex items-center justify-evenly rounded-lg bg-grey-100 font-sans text-md font-normal text-white"> | ||
| {buttons.map(({label, name, Icon, dataTestId, ariaLabel}) => ( | ||
| <ButtonGroupIconButton | ||
| key={`${name}-${label}`} | ||
| ariaLabel={ariaLabel} | ||
| dataTestId={dataTestId} | ||
| hasTooltip={hasTooltip} | ||
| Icon={Icon} | ||
| label={label} | ||
| name={name} | ||
| selectedName={selectedName} | ||
| onClick={onClick} | ||
| /> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export function ButtonGroupIconButton({dataTestId, onClick, label, ariaLabel, name, selectedName, Icon, hasTooltip}) { | ||
| const isActive = name === selectedName; | ||
|
|
||
| const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); | ||
|
|
||
| return ( | ||
| <li className="mb-0"> | ||
| <button | ||
| aria-label={ariaLabel || label} | ||
| className={`group relative flex h-7 w-8 cursor-pointer items-center justify-center rounded-lg text-black dark:text-white dark:hover:bg-grey-900 ${isActive ? 'border border-grey-300 bg-white shadow-xs dark:bg-grey-900' : '' } ${Icon ? '' : 'text-[1.3rem] font-bold'}`} | ||
| data-testid={dataTestId} | ||
| type="button" | ||
| onClick={handleClick} | ||
| onMouseDown={handleMousedown} | ||
| > | ||
| {Icon ? <Icon className="size-4 stroke-2" /> : label} | ||
| {(Icon && label && hasTooltip) && <Tooltip label={label} />} | ||
| </button> | ||
|
Comment on lines
+46
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Potential accessibility issue with Icon-only buttons. When a button only has an icon (no visible text), ensure it always has an accessible name via {Icon ? <Icon className="size-4 stroke-2" /> : label}
{(Icon && label && hasTooltip) && <Tooltip label={label} />}
+ {(Icon && !label && !ariaLabel) && console.warn('ButtonGroupIconButton with Icon is missing both label and ariaLabel props')} |
||
| </li> | ||
| ); | ||
| } | ||
|
|
||
| ButtonGroupBeta.propTypes = { | ||
| selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']), | ||
| hasTooltip: PropTypes.bool, | ||
| onClick: PropTypes.func, | ||
| buttons: PropTypes.arrayOf(PropTypes.shape({ | ||
| label: PropTypes.string, | ||
| name: PropTypes.string, | ||
| Icon: PropTypes.func, | ||
| dataTestId: PropTypes.string, | ||
| ariaLabel: PropTypes.string | ||
| })) | ||
kevinansfield marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
kevinansfield marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import ImgFullIcon from '../../assets/icons/kg-img-full.svg?react'; | ||
| import ImgRegularIcon from '../../assets/icons/kg-img-regular.svg?react'; | ||
| import ImgWideIcon from '../../assets/icons/kg-img-wide.svg?react'; | ||
| import React from 'react'; | ||
| import {ButtonGroupBeta, ButtonGroupIconButton} from './ButtonGroupBeta'; | ||
|
|
||
| const story = { | ||
| title: 'Generic/Button group (beta)', | ||
| component: ButtonGroupBeta, | ||
| subcomponents: {ButtonGroupIconButton}, | ||
| parameters: { | ||
| status: { | ||
| type: 'functional' | ||
| } | ||
| }, | ||
| argTypes: { | ||
| selectedName: {control: 'select', options: ['regular', 'wide', 'full']} | ||
| } | ||
| }; | ||
| export default story; | ||
|
|
||
| const Template = (args) => { | ||
| return ( | ||
| <ButtonGroupBeta {...args} /> | ||
| ); | ||
| }; | ||
|
|
||
| export const CardWidth = Template.bind({}); | ||
| CardWidth.args = { | ||
| selectedName: 'regular', | ||
| buttons: [ | ||
| { | ||
| label: 'Regular', | ||
| name: 'regular', | ||
| Icon: ImgRegularIcon | ||
| }, | ||
| { | ||
| label: 'Wide', | ||
| name: 'wide', | ||
| Icon: ImgWideIcon | ||
| }, | ||
| { | ||
| label: 'Full', | ||
| name: 'full', | ||
| Icon: ImgFullIcon | ||
| } | ||
| ] | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import PlusIcon from '../../assets/icons/plus.svg?react'; | ||
| import React, {useState} from 'react'; | ||
| import {Tooltip} from './Tooltip'; | ||
| import {useClickOutside} from '../../hooks/useClickOutside'; | ||
| import {usePreviousFocus} from '../../hooks/usePreviousFocus'; | ||
|
|
||
| export function ColorOptionButtonsBeta({buttons = [], selectedName, onClick}) { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const componentRef = React.useRef(null); | ||
|
|
||
| const selectedButton = buttons.find(button => button.name === selectedName); | ||
|
|
||
| // Close the swatch popover when clicking outside of it | ||
| useClickOutside(isOpen, componentRef, () => setIsOpen(false)); | ||
|
|
||
| return ( | ||
| <div ref={componentRef} className="relative"> | ||
| <button | ||
| className={`relative size-6 cursor-pointer rounded-full ${selectedName ? 'p-[2px]' : 'border border-grey-200 dark:border-grey-800'}`} | ||
| data-testid="color-options-button" | ||
| type="button" | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| > | ||
| {selectedName && ( | ||
| <div className="absolute inset-0 rounded-full bg-clip-content p-[3px]" style={{ | ||
| background: 'conic-gradient(hsl(360,100%,50%),hsl(315,100%,50%),hsl(270,100%,50%),hsl(225,100%,50%),hsl(180,100%,50%),hsl(135,100%,50%),hsl(90,100%,50%),hsl(45,100%,50%),hsl(0,100%,50%))', | ||
| WebkitMask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)', | ||
| WebkitMaskComposite: 'xor', | ||
| mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)', | ||
| maskComposite: 'exclude' | ||
| }} /> | ||
| )} | ||
| <span | ||
| className={`${selectedButton?.color || ''} block size-full rounded-full border-2 border-white`} | ||
| ></span> | ||
| </button> | ||
|
|
||
| {/* Color options popover */} | ||
| {isOpen && ( | ||
| <div className="absolute -right-3 bottom-full z-10 mb-2 rounded-lg bg-white px-3 py-2 shadow" data-testid="color-options-popover"> | ||
| <div className="flex"> | ||
| <ul className="flex w-full items-center justify-between rounded-md font-sans text-md font-normal text-white"> | ||
| {buttons.map(({label, name, color}) => ( | ||
| name !== 'image' ? | ||
| <ColorButton | ||
| key={`${name}-${label}`} | ||
| color={color} | ||
| data-testid={`color-options-${name}-button`} | ||
| label={label} | ||
| name={name} | ||
| selectedName={selectedName} | ||
| onClick={(title) => { | ||
| onClick(title); | ||
| setIsOpen(false); | ||
| }} | ||
| /> | ||
| : | ||
| <li key='background-image' className={`mb-0 flex size-[3rem] cursor-pointer items-center justify-center rounded-full border-2 ${selectedName === name ? 'border-green' : 'border-transparent'}`} data-testid="background-image-color-button" type="button" onClick={() => onClick(name)}> | ||
| <span className="border-1 flex size-6 items-center justify-center rounded-full border border-black/5"> | ||
| <PlusIcon className="size-3 stroke-grey-700 stroke-2 dark:stroke-grey-500 dark:group-hover:stroke-grey-100" /> | ||
| </span> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export function ColorButton({onClick, label, name, color, selectedName}) { | ||
| const isActive = name === selectedName; | ||
|
|
||
| const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); | ||
| return ( | ||
| <li className="mb-0"> | ||
| <button | ||
| aria-label={label} | ||
| className={`group relative flex size-6 cursor-pointer items-center justify-center rounded-full border-2 ${isActive ? 'border-green' : 'border-transparent'}`} | ||
| data-test-id={`color-picker-${name}`} | ||
| type="button" | ||
| onClick={handleClick} | ||
| onMouseDown={handleMousedown} | ||
| > | ||
| <span | ||
| className={`${color} size-[1.8rem] rounded-full border`} | ||
| ></span> | ||
| <Tooltip label={label} /> | ||
| </button> | ||
| </li> | ||
| ); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.