Refactored some core UI

This commit is contained in:
Gregory Schier
2023-10-30 06:35:52 -07:00
parent c8e674d015
commit b392f0c00f
12 changed files with 175 additions and 134 deletions

View File

@@ -49,9 +49,9 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
() =>
classNames(
className,
'flex-shrink-0 outline-none whitespace-nowrap',
'focus-visible-or-class:ring',
'rounded-md flex items-center',
'whitespace-nowrap outline-none',
'flex-shrink-0 flex items-center',
'focus-visible-or-class:ring rounded-md',
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
colorStyles[color || 'default'],
justify === 'start' && 'justify-start',
@@ -70,7 +70,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
) : leftSlot ? (
<div className="mr-1">{leftSlot}</div>
) : null}
{children}
<div className="max-w-[15em] truncate">{children}</div>
{rightSlot && <div className="ml-1">{rightSlot}</div>}
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
</button>

View File

@@ -59,7 +59,7 @@ export function Dialog({
'dark:border border-highlight shadow shadow-black/10',
size === 'sm' && 'w-[25rem] max-h-[80vh]',
size === 'md' && 'w-[45rem] max-h-[80vh]',
size === 'full' && 'w-[calc(100vw-8em)] h-[calc(100vh-8em)]',
size === 'full' && 'w-[95vw] h-[calc(100vh-6em)]',
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
)}
>

View File

@@ -1,5 +1,4 @@
import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';
import { motion } from 'framer-motion';
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
import React, {
@@ -13,11 +12,11 @@ import React, {
useRef,
useState,
} from 'react';
import { useKey, useKeyPressEvent } from 'react-use';
import { Portal } from '../Portal';
import { useKey, useKeyPressEvent, useWindowSize } from 'react-use';
import { Button } from './Button';
import { Separator } from './Separator';
import { VStack } from './Stacks';
import { Overlay } from '../Overlay';
export type DropdownItemSeparator = {
type: 'separator';
@@ -65,7 +64,7 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
useImperativeHandle(ref, () => ({
...menuRef.current,
isOpen: open,
toggle (activeIndex?: number) {
toggle(activeIndex?: number) {
if (!open) this.open(activeIndex);
else setOpen(false);
},
@@ -107,10 +106,12 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
buttonRef.current?.setAttribute('aria-expanded', open.toString());
}, [open]);
const windowSize = useWindowSize();
const triggerRect = useMemo(() => {
windowSize; // Make TS happy with this dep
if (!open) return null;
return buttonRef.current?.getBoundingClientRect();
}, [open]);
}, [open, windowSize]);
return (
<>
@@ -267,61 +268,59 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
if (items.length === 0) return null;
return (
<Portal name="dropdown">
<FocusTrap>
<div>
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-50" onClick={onClose} />
<motion.div
tabIndex={0}
onKeyDown={handleMenuKeyDown}
initial={{ opacity: 0, y: -5, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
role="menu"
aria-orientation="vertical"
dir="ltr"
ref={containerRef}
style={containerStyles}
className={classNames(className, 'outline-none mt-1 pointer-events-auto fixed z-50')}
>
<span
aria-hidden
style={triangleStyles}
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
/>
{containerStyles && (
<VStack
space={0.5}
ref={initMenu}
style={menuStyles}
className={classNames(
className,
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
'border-gray-200 overflow-auto mb-1 mx-0.5',
)}
>
{items.map((item, i) => {
if (item.type === 'separator') {
return <Separator key={i} className="my-1.5" label={item.label} />;
}
if (item.hidden) {
return null;
}
return (
<MenuItem
focused={i === selectedIndex}
onFocus={handleFocus}
onSelect={handleSelect}
key={item.key}
item={item}
/>
);
})}
</VStack>
)}
</motion.div>
</div>
</FocusTrap>
</Portal>
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}>
<div>
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={onClose} />
<motion.div
tabIndex={0}
onKeyDown={handleMenuKeyDown}
initial={{ opacity: 0, y: -5, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
role="menu"
aria-orientation="vertical"
dir="ltr"
ref={containerRef}
style={containerStyles}
className={classNames(className, 'outline-none mt-1 pointer-events-auto fixed z-50')}
>
<span
aria-hidden
style={triangleStyles}
className="bg-gray-50 absolute rotate-45 border-gray-200 border-t border-l"
/>
{containerStyles && (
<VStack
space={0.5}
ref={initMenu}
style={menuStyles}
className={classNames(
className,
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
'border-gray-200 overflow-auto mb-1 mx-0.5',
)}
>
{items.map((item, i) => {
if (item.type === 'separator') {
return <Separator key={i} className="my-1.5" label={item.label} />;
}
if (item.hidden) {
return null;
}
return (
<MenuItem
focused={i === selectedIndex}
onFocus={handleFocus}
onSelect={handleSelect}
key={item.key}
item={item}
/>
);
})}
</VStack>
)}
</motion.div>
</div>
</Overlay>
);
});
@@ -359,6 +358,8 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
onFocus={handleFocus}
onClick={handleClick}
justify="start"
leftSlot={item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
rightSlot={item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
className={classNames(
className,
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
@@ -367,7 +368,6 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
)}
{...props}
>
{item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
<div
className={classNames(
// Add padding on right when no right slot, for some visual balance
@@ -376,7 +376,6 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
>
{item.label}
</div>
{item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
</Button>
);
}

View File

@@ -154,6 +154,8 @@ export const PairEditor = memo(function PairEditor({
'pb-2 grid overflow-auto max-h-full',
// Move over the width of the drag handle
'-ml-3',
// Pad to make room for the drag divider
'pt-0.5',
)}
>
{pairs.map((p, i) => {
@@ -171,8 +173,8 @@ export const PairEditor = memo(function PairEditor({
forceUpdateKey={forceUpdateKey}
nameAutocomplete={nameAutocomplete}
valueAutocomplete={valueAutocomplete}
namePlaceholder={namePlaceholder}
valuePlaceholder={valuePlaceholder}
namePlaceholder={isLast ? namePlaceholder : ''}
valuePlaceholder={isLast ? valuePlaceholder : ''}
nameValidate={nameValidate}
valueValidate={valueValidate}
showDelete={!isLast}

View File

@@ -102,14 +102,16 @@ export function Tabs({
size="sm"
onClick={isActive ? undefined : () => handleTabChange(t.value)}
className={btnClassName}
rightSlot={
<Icon
icon="triangleDown"
className={classNames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
/>
}
>
{option && 'shortLabel' in option
? option.shortLabel
: option?.label ?? 'Unknown'}
<Icon
icon="triangleDown"
className={classNames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
/>
</Button>
</RadioDropdown>
);