diff options
author | bobzel <zzzman@gmail.com> | 2025-03-07 13:46:59 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-03-07 13:46:59 -0500 |
commit | 82ba2c85e22fb809f1a5fba827c73555db0e4cd9 (patch) | |
tree | 602f887026e7fdf3ef68f80fef55ee45baef9daf /packages/components/src | |
parent | 72b264479ea5c039dbba806b6bbd941729b9073a (diff) |
fixed columnWidth settings for sidebar. fixed color picker and other dropdowns to toggle when button is clicked. fixed dash field views to collapse properly. fixed rtf to honor marks that were set before text box is created and typing starts.
Diffstat (limited to 'packages/components/src')
3 files changed, 315 insertions, 362 deletions
diff --git a/packages/components/src/components/ColorPicker/ColorPicker.tsx b/packages/components/src/components/ColorPicker/ColorPicker.tsx index 51b820a37..632b470f0 100644 --- a/packages/components/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/components/src/components/ColorPicker/ColorPicker.tsx @@ -1,204 +1,156 @@ -import React, { useState } from 'react' -import { GithubPicker, ChromePicker, BlockPicker, SliderPicker, SketchPicker } from 'react-color' -import { IGlobalProps, Size, Type , getFormLabelSize } from '../../global' -import { Button } from '../Button' -import { IconButton } from '../IconButton' -import { Popup, PopupTrigger } from '../Popup' -import './ColorPicker.scss' -import { Dropdown, DropdownType } from '../Dropdown' +import React, { useRef, useState } from 'react'; +import { GithubPicker, ChromePicker, BlockPicker, SliderPicker, SketchPicker } from 'react-color'; +import { IGlobalProps, Size, Type, getFormLabelSize } from '../../global'; +import { Button } from '../Button'; +import { IconButton } from '../IconButton'; +import { Popup, PopupTrigger } from '../Popup'; +import './ColorPicker.scss'; +import { Dropdown, DropdownType } from '../Dropdown'; -export const ColorPickerArray= ["Classic", "Chrome", "GitHub", "Block", "Slider"] -export type ColorPickerType= typeof ColorPickerArray[number]; +export const ColorPickerArray = ['Classic', 'Chrome', 'GitHub', 'Block', 'Slider']; +export type ColorPickerType = (typeof ColorPickerArray)[number]; export interface IColorPickerProps extends IGlobalProps { - text?: string - icon?: JSX.Element | string - colorPickerType?: ColorPickerType - defaultPickerType?: ColorPickerType - selectedColor?: string - setSelectedColor: (color: any) => unknown - setFinalColor: (color:any) => unknown + text?: string; + icon?: JSX.Element | string; + colorPickerType?: ColorPickerType; + defaultPickerType?: ColorPickerType; + selectedColor?: string; + setSelectedColor: (color: any) => unknown; + setFinalColor: (color: any) => unknown; } export const ColorPicker = (props: IColorPickerProps) => { - const [selectedColorLoc, setSelectedColorLoc] = useState(); - const { defaultPickerType, text, colorPickerType, fillWidth, formLabelPlacement, size = Size.SMALL, type = Type.TERT, icon, selectedColor = selectedColorLoc, setSelectedColor = setSelectedColorLoc, setFinalColor = setSelectedColorLoc, tooltip, color='black', formLabel } = props - const [isOpen, setOpen] = useState<boolean>(false) - const [pickerSelectorOpen, setPickerSelectorOpen] = useState<boolean>(false); - const decimalToHexString = (number: number) => { - if (number < 0) { - number = 0xffffffff + number + 1; - } - return (number < 16 ? '0' : '') + number.toString(16).toUpperCase(); -} - const colorString = (color: any ) => { - return color.hex === 'transparent' ? color.hex: color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff'); - } - const onChange = (color: any) => { - setSelectedColor(colorString(color) as any); - } - const onChangeComplete = (color: any) => { - setFinalColor(colorString(color) as any); - } - const [picker, setPicker] = useState<string>(defaultPickerType ?? "Classic") + const [selectedColorLoc, setSelectedColorLoc] = useState(); + const { + defaultPickerType, + text, + colorPickerType, + fillWidth, + formLabelPlacement, + size = Size.SMALL, + type = Type.TERT, + icon, + selectedColor = selectedColorLoc, + setSelectedColor = setSelectedColorLoc, + setFinalColor = setSelectedColorLoc, + tooltip, + color = 'black', + formLabel, + } = props; + const [isOpen, setOpen] = useState<boolean>(false); + const [pickerSelectorOpen, setPickerSelectorOpen] = useState<boolean>(false); + const decimalToHexString = (number: number) => { + if (number < 0) { + number = 0xffffffff + number + 1; + } + return (number < 16 ? '0' : '') + number.toString(16).toUpperCase(); + }; + const colorString = (color: any) => { + return color.hex === 'transparent' ? color.hex : color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff'); + }; + const onChange = (color: any) => { + setSelectedColor(colorString(color) as any); + }; + const onChangeComplete = (color: any) => { + setFinalColor(colorString(color) as any); + }; + const [picker, setPicker] = useState<string>(defaultPickerType ?? 'Classic'); - const getToggle = () => { - if (icon && !text) { - return ( - <IconButton - active={isOpen} - tooltip={tooltip} - type={type} - color={color} - size={size} - icon={icon} - colorPicker={selectedColor} - fillWidth={fillWidth} - /> - ) - } else if (text) { - return ( - <Button - active={isOpen} - tooltip={tooltip} - size={size} - type={type} - color={color} - text={text} - icon={icon} - align={'flex-start'} - iconPlacement={'left'} - colorPicker={selectedColor} - fillWidth={fillWidth} - /> - ) - } else { - return ( - <IconButton - active={isOpen} - tooltip={tooltip} - type={type} - color={color} - size={size} - icon={icon} - colorPicker={selectedColor} - fillWidth={fillWidth} - /> - ) - } - } + const toggleRef = useRef<HTMLDivElement | null>(null); + const getToggle = () => ( + <div ref={toggleRef}> + {icon && !text ? ( + <IconButton active={isOpen} tooltip={tooltip} type={type} color={color} size={size} icon={icon} colorPicker={selectedColor} fillWidth={fillWidth} /> + ) : text ? ( + <Button active={isOpen} tooltip={tooltip} size={size} type={type} color={color} text={text} icon={icon} align={'flex-start'} iconPlacement={'left'} colorPicker={selectedColor} fillWidth={fillWidth} /> + ) : ( + <IconButton active={isOpen} tooltip={tooltip} type={type} color={color} size={size} icon={icon} colorPicker={selectedColor} fillWidth={fillWidth} /> + )} + </div> + ); - const getColorPicker = (pickerType: ColorPickerType):JSX.Element => { - const colorPalette = ["FFFFFF", "#F9F6F2", "#E2E2E2", "#D1D1D1", "#737576", "#4b4a4d", "#222021", - '#EB9694', '#FAD0C3', '#FEF3BD', '#C1E1C5', '#BEDADC', '#C4DEF6', '#BED3F3', '#D4C4FB', "transparent" - ] - switch(pickerType) { - case "Block": - return ( - <BlockPicker - color={selectedColor} - triangle={'hide'} - colors={colorPalette} - onChange={onChange} - onChangeComplete={onChangeComplete} - /> - ); - case "Classic": - return (<SketchPicker - onChange={onChange} - onChangeComplete={onChangeComplete} - presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={selectedColor} - />); - case "Chrome": - default: - return ( - <ChromePicker - color={selectedColor} - onChange={onChange} - onChangeComplete={onChangeComplete} - /> - ); - case "GitHub": - return ( - <GithubPicker - color={selectedColor} - colors={colorPalette} - triangle={'hide'} - onChange={onChange} - onChangeComplete={onChangeComplete} - /> - ); - case "Slider": - return ( - <div style={{width: 200, height: 50}}> - <SliderPicker - color={selectedColor} - onChange={onChange} - onChangeComplete={onChangeComplete} - /> - </div> - ); - } - } - const openChanged = (isOpen:boolean) => setPickerSelectorOpen(isOpen); + const getColorPicker = (pickerType: ColorPickerType): JSX.Element => { + const colorPalette = ['FFFFFF', '#F9F6F2', '#E2E2E2', '#D1D1D1', '#737576', '#4b4a4d', '#222021', '#EB9694', '#FAD0C3', '#FEF3BD', '#C1E1C5', '#BEDADC', '#C4DEF6', '#BED3F3', '#D4C4FB', 'transparent']; + switch (pickerType) { + case 'Classic': return ( + <SketchPicker + onChange={onChange} + onChangeComplete={onChangeComplete} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} + color={selectedColor} + /> + ); + case 'Chrome': + default: return <ChromePicker color={selectedColor} onChange={onChange} onChangeComplete={onChangeComplete} />; + case 'GitHub': return <GithubPicker color={selectedColor} colors={colorPalette} triangle={'hide'} onChange={onChange} onChangeComplete={onChangeComplete} />; + case 'Block': return <BlockPicker color={selectedColor} triangle={'hide'} colors={colorPalette} onChange={onChange} onChangeComplete={onChangeComplete} />; + case 'Slider': return ( + <div style={{ width: 200, height: 50 }}> + <SliderPicker color={selectedColor} onChange={onChange} onChangeComplete={onChangeComplete} /> + </div> + ); + } // prettier-ignore + }; + const openChanged = (isOpen: boolean) => setPickerSelectorOpen(isOpen); - const getPopup = ():JSX.Element => { - if (colorPickerType){ - return getColorPicker(colorPickerType) - } else { - // Todo: this would be much easier if the selectedColor was a Color, not a string. - const newColor = (selectedColor === 'transparent' ? 'white': selectedColor?.startsWith("#") ? selectedColor.substring(0,7): - selectedColor?.startsWith('rgba') ? selectedColor?.replace(/,[0-9]*\)/,"1)") : selectedColor); - return <div style={{height: 'fit-content'}}> - <Dropdown - items={ - ColorPickerArray.map((item) => { - return { - text: item, - val: item, - } - }) - } - activeChanged={openChanged} - placement={'right'} - color={newColor} - type={Type.PRIM} - dropdownType={DropdownType.SELECT} - selectedVal={picker} - setSelectedVal={(val) => setPicker(val as string)} - fillWidth - /> - {getColorPicker(picker)} - </div> - } - } + const getPopup = (): JSX.Element => { + if (colorPickerType) { + return getColorPicker(colorPickerType); + } else { + // Todo: this would be much easier if the selectedColor was a Color, not a string. + const newColor = selectedColor === 'transparent' ? 'white' : selectedColor?.startsWith('#') ? selectedColor.substring(0, 7) : selectedColor?.startsWith('rgba') ? selectedColor?.replace(/,[0-9]*\)/, '1)') : selectedColor; + return ( + <div style={{ height: 'fit-content' }}> + <Dropdown + items={ColorPickerArray.map(item => { + return { + text: item, + val: item, + }; + })} + activeChanged={openChanged} + placement={'right'} + color={newColor} + type={Type.PRIM} + dropdownType={DropdownType.SELECT} + selectedVal={picker} + setSelectedVal={val => setPicker(val as string)} + fillWidth + /> + {getColorPicker(picker)} + </div> + ); + } + }; - const popupContainsPt = (x:number, y:number) => pickerSelectorOpen; + const popupContainsPt = (x: number, y: number) => { + const rect = toggleRef.current?.getBoundingClientRect(); + return rect && rect.left < x && rect.top < y && rect.right > x && rect.bottom > y; + }; - const colorPicker: JSX.Element = - ( - <Popup - toggle={getToggle()} - trigger={PopupTrigger.CLICK} - isOpen={isOpen} - setOpen={setOpen} - tooltip={tooltip} - size={size} - color={selectedColor} - popup={getPopup()} - popupContainsPt={popupContainsPt} // this should prohbably test to see if the click pt is actually within the picker selector list popup. - /> - ) + const colorPicker: JSX.Element = ( + <Popup + toggle={getToggle()} + trigger={PopupTrigger.CLICK} + isOpen={isOpen} + setOpen={setOpen} + tooltip={tooltip} + size={size} + color={selectedColor} + popup={getPopup()} + popupContainsPt={popupContainsPt} // this should prohbably test to see if the click pt is actually within the picker selector list popup. + /> + ); - return ( - formLabel ? - <div className={`form-wrapper ${formLabelPlacement}`} -style={{ width: fillWidth ? '100%' : undefined}}> - <div className={'formLabel'} style={{fontSize: getFormLabelSize(size)}}>{formLabel}</div> - {colorPicker} - </div> - : - colorPicker - ) -} + return formLabel ? ( + <div className={`form-wrapper ${formLabelPlacement}`} style={{ width: fillWidth ? '100%' : undefined }}> + <div className={'formLabel'} style={{ fontSize: getFormLabelSize(size) }}> + {formLabel} + </div> + {colorPicker} + </div> + ) : ( + colorPicker + ); +}; diff --git a/packages/components/src/components/IconButton/IconButton.tsx b/packages/components/src/components/IconButton/IconButton.tsx index 7d273e23f..954a0ae44 100644 --- a/packages/components/src/components/IconButton/IconButton.tsx +++ b/packages/components/src/components/IconButton/IconButton.tsx @@ -1,157 +1,153 @@ -import { Tooltip } from '@mui/material' -import React from 'react' -import { Colors, Size, Type, getFontSize, getHeight, isDark, getFormLabelSize } from '../../global' -import { IButtonProps } from '../Button' -import './IconButton.scss' +import { Tooltip } from '@mui/material'; +import React from 'react'; +import { Colors, Size, Type, getFontSize, getHeight, isDark, getFormLabelSize } from '../../global'; +import { IButtonProps } from '../Button'; +import './IconButton.scss'; export interface IIconButtonProps extends IButtonProps {} export const IconButton = (props: IButtonProps) => { - const { - active, - icon, - onClick, - onDoubleClick, - onPointerDown, - inactive, - type = Type.PRIM, - color = Colors.MEDIUM_BLUE, - background, - label, - height, - size = Size.SMALL, - style, - tooltip, - tooltipPlacement = 'top', - colorPicker, - formLabel, - formLabelPlacement, - hideLabel, - fillWidth - } = props + const { + active, + icon, + onClick, + onDoubleClick, + onPointerDown, + inactive, + type = Type.PRIM, + color = Colors.MEDIUM_BLUE, + background, + label, + height, + size = Size.SMALL, + style, + tooltip, + tooltipPlacement = 'top', + colorPicker, + formLabel, + formLabelPlacement, + hideLabel, + fillWidth, + } = props; - /** - * Pointer down - * @param e - */ - const handlePointerDown = (e: React.PointerEvent) => { - - if (!inactive && onPointerDown) { - e.stopPropagation(); - e.preventDefault(); - onPointerDown(e) - } - } + /** + * Pointer down + * @param e + */ + const handlePointerDown = (e: React.PointerEvent) => { + if (!inactive && onPointerDown) { + e.stopPropagation(); + e.preventDefault(); + onPointerDown(e); + } + }; - /** - * In the event that there is a single click - * @param e - */ - const handleClick = (e: React.MouseEvent) => { - if (!inactive && onClick) { - e.stopPropagation(); - e.preventDefault(); - onClick(e) - } - } + /** + * In the event that there is a single click + * @param e + */ + const handleClick = (e: React.MouseEvent) => { + if (!inactive && onClick) { + e.stopPropagation(); + e.preventDefault(); + onClick(e); + } + }; - /** - * Double click - * @param e - */ - const handleDoubleClick = (e: React.MouseEvent) => { - if (!inactive && onDoubleClick){ - e.stopPropagation(); - e.preventDefault(); - onDoubleClick(e) - } - } + /** + * Double click + * @param e + */ + const handleDoubleClick = (e: React.MouseEvent) => { + if (!inactive && onDoubleClick) { + e.stopPropagation(); + e.preventDefault(); + onDoubleClick(e); + } + }; - const getBorderColor = (): Colors | string | undefined => { - switch(type){ - case Type.PRIM: - return undefined; - case Type.SEC: - return color; - case Type.TERT: - if (colorPicker) return colorPicker; - if (active) return color; - else return color; - } - } + const getBorderColor = (): Colors | string | undefined => { + switch (type) { + case Type.PRIM: + return undefined; + case Type.SEC: + return color; + case Type.TERT: + if (colorPicker) return colorPicker; + if (active) return color; + else return color; + } + }; - const getColor = (): Colors | string | undefined => { - if (color && background) return color; - switch(type){ - case Type.PRIM: - return color; - case Type.SEC: - return color; - case Type.TERT: - if (colorPicker) { - if (isDark(colorPicker)) return Colors.WHITE; - else return Colors.BLACK + const getColor = (): Colors | string | undefined => { + if (color && background) return color; + switch (type) { + case Type.PRIM: + return color; + case Type.SEC: + return color; + case Type.TERT: + if (colorPicker) { + if (isDark(colorPicker)) return Colors.WHITE; + else return Colors.BLACK; + } + if (isDark(color)) return Colors.WHITE; + else return Colors.BLACK; } - if (isDark(color)) return Colors.WHITE; - else return Colors.BLACK - } - } + }; - const getBackground = (): Colors | string | undefined => { - if(background) return background; - switch(type){ - case Type.PRIM: - return color; - case Type.SEC: - return color; - case Type.TERT: - if (colorPicker) return colorPicker - else return color - } - } + const getBackground = (): Colors | string | undefined => { + if (background) return background; + switch (type) { + case Type.PRIM: + return color; + case Type.SEC: + return color; + case Type.TERT: + if (colorPicker) return colorPicker; + else return color; + } + }; - const defaultProperties: React.CSSProperties = { - height: getHeight(height, size), - width: fillWidth ? '100%' : getHeight(height, size), - minWidth: getHeight(height, size), - fontWeight: 500, - fontSize: getFontSize(size, true), - borderColor: getBorderColor(), - color: getColor() - } + const defaultProperties: React.CSSProperties = { + height: getHeight(height, size), + width: fillWidth ? '100%' : getHeight(height, size), + minWidth: getHeight(height, size), + fontWeight: 500, + fontSize: getFontSize(size, true), + borderColor: getBorderColor(), + color: getColor(), + }; - const backgroundProperties: React.CSSProperties = { - background: getBackground() - } + const backgroundProperties: React.CSSProperties = { + background: getBackground(), + }; - const iconButton: JSX.Element = ( - <Tooltip disableInteractive={true} arrow={true} placement={tooltipPlacement} title={tooltip}> - <div - className={`iconButton-container ${type} ${inactive && 'inactive'}`} - onClick={handleClick} - onDoubleClick={handleDoubleClick} - onPointerDown={handlePointerDown} - style={{...defaultProperties, ...style}} - tabIndex={-1} - > - <div className="iconButton-content"> - {icon} - {colorPicker && type !== (Type.TERT) && <div className={`color`} style={{background: colorPicker, outlineColor: defaultProperties.color}}/>} - {label && !hideLabel && <div className={'iconButton-label'} style={{color: defaultProperties.color}}>{label}</div>} - </div> - <div className={`background ${active && 'active'} ${inactive && 'inactive'}`} style={backgroundProperties}/> - </div> - </Tooltip> - ) + const iconButton: JSX.Element = ( + <Tooltip disableInteractive={true} arrow={true} placement={tooltipPlacement} title={tooltip}> + <div className={`iconButton-container ${type} ${inactive && 'inactive'}`} onClick={handleClick} onDoubleClick={handleDoubleClick} onPointerDown={handlePointerDown} style={{ ...defaultProperties, ...style }} tabIndex={-1}> + <div className="iconButton-content"> + {icon} + {colorPicker && type !== Type.TERT && <div className={`color`} style={{ background: colorPicker, outlineColor: defaultProperties.color }} />} + {label && !hideLabel && ( + <div className={'iconButton-label'} style={{ color: defaultProperties.color }}> + {label} + </div> + )} + </div> + <div className={`background ${active && 'active'} ${inactive && 'inactive'}`} style={backgroundProperties} /> + </div> + </Tooltip> + ); - return ( - formLabel ? - <div className={`form-wrapper ${formLabelPlacement}`} -style={{ width: fillWidth ? '100%' : undefined}}> - <div className={'formLabel'} style={{fontSize: getFormLabelSize(size)}}>{formLabel}</div> - {iconButton} - </div> - : - iconButton - ) -} + return formLabel ? ( + <div className={`form-wrapper ${formLabelPlacement}`} style={{ width: fillWidth ? '100%' : undefined }}> + <div className={'formLabel'} style={{ fontSize: getFormLabelSize(size) }}> + {formLabel} + </div> + {iconButton} + </div> + ) : ( + iconButton + ); +}; diff --git a/packages/components/src/components/Popup/Popup.tsx b/packages/components/src/components/Popup/Popup.tsx index f0920b723..5a58fee29 100644 --- a/packages/components/src/components/Popup/Popup.tsx +++ b/packages/components/src/components/Popup/Popup.tsx @@ -63,12 +63,19 @@ export const Popup = (props: IPopupProps) => { const triggerRef = useRef(null); const popperRef = useRef<HTMLDivElement | null>(null); + const toggleRef = useRef<HTMLDivElement | null>(null); let timeout = setTimeout(() => {}); const handlePointerAwayDown = (e: PointerEvent) => { const rect = popperRef.current?.getBoundingClientRect(); - if (rect && !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) && !popupContainsPt?.(e.clientX, e.clientY)) { + const rect2 = toggleRef.current?.getBoundingClientRect(); + if ( + (!rect2 || !(rect2.left < e.clientX && rect2.top < e.clientY && rect2.right > e.clientX && rect2.bottom > e.clientY)) && + rect && + !(rect.left < e.clientX && rect.top < e.clientY && rect.right > e.clientX && rect.bottom > e.clientY) && + !popupContainsPt?.(e.clientX, e.clientY) + ) { e.preventDefault(); setOpen(false); e.stopPropagation(); @@ -79,9 +86,7 @@ export const Popup = (props: IPopupProps) => { if (isOpen) { window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); window.addEventListener('pointerdown', handlePointerAwayDown, { capture: true }); - return () => { - window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); - }; + return () => window.removeEventListener('pointerdown', handlePointerAwayDown, { capture: true }); } }, [isOpen, popupContainsPt]); @@ -104,31 +109,31 @@ export const Popup = (props: IPopupProps) => { timeout = setTimeout(() => setOpen(false), 1000); } }}> - {toggle ? ( - toggle - ) : ( - <Toggle - tooltip={tooltip} - size={size} - type={type} - color={color} - background={props.isToggle ? undefined : background} - toggleType={ToggleType.BUTTON} - icon={icon} - iconPlacement={iconPlacement} - text={text} - label={props.label} - toggleStatus={isOpen || props.toggleStatus} - onClick={() => { - if (trigger === PopupTrigger.CLICK) { - if (!props.isToggle || props.toggleStatus) { - setOpen(!isOpen); + {toggle ?? ( + <div ref={toggleRef}> + <Toggle + tooltip={tooltip} + size={size} + type={type} + color={color} + background={props.isToggle ? undefined : background} + toggleType={ToggleType.BUTTON} + icon={icon} + iconPlacement={iconPlacement} + text={text} + label={props.label} + toggleStatus={isOpen || props.toggleStatus} + onClick={() => { + if (trigger === PopupTrigger.CLICK) { + if (!props.isToggle || props.toggleStatus) { + setOpen(!isOpen); + } + props.toggleFunc?.(); } - props.toggleFunc?.(); - } - }} - fillWidth={fillWidth} - /> + }} + fillWidth={fillWidth} + /> + </div> )} </div> <Popper open={isOpen} style={{ zIndex: 20000 }} anchorEl={triggerRef.current} placement={placement} modifiers={[]}> |