diff --git a/package-lock.json b/package-lock.json index c4b2fff5..231834d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1244,9 +1244,9 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", - "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.6.0", diff --git a/src/components/forms/index.ts b/src/components/forms/index.ts index f2384967..1ac46538 100644 --- a/src/components/forms/index.ts +++ b/src/components/forms/index.ts @@ -1 +1 @@ -export * from './radio-group/index.js'; +export * from './radio-button-group/index.js'; diff --git a/src/components/forms/radio-button-group/RadioButton.tsx b/src/components/forms/radio-button-group/RadioButton.tsx new file mode 100644 index 00000000..a81e155e --- /dev/null +++ b/src/components/forms/radio-button-group/RadioButton.tsx @@ -0,0 +1,77 @@ +/** @jsxImportSource @emotion/react */ +import { Radio, type RadioProps } from '@blueprintjs/core'; +import { css } from '@emotion/react'; + +import { enabledColor } from '../styles.js'; + +const buttonStyles = { + radioGroup: css({ + display: 'flex', + flexDirection: 'row', + width: 'fit-content', + ' & > *:first-of-type, & > *:first-of-type span': { + borderRadius: '6px 0 0 6px', + }, + ' & > *:last-of-type, & > *:last-of-type span': { + borderRightWidth: 1, + borderRadius: '0 6px 6px 0', + }, + }), + container: (disabled: boolean, large?: boolean) => + css({ + height: large ? '40px' : '30px', + border: '1px solid rgba(0, 0, 0, 0.25)', + borderRightWidth: 0, + position: 'relative', + paddingLeft: '0 !important', + '.bp5-control-indicator': { + display: 'none', + }, + '&:hover': { + color: enabledColor, + }, + 'input[type="radio"]:checked': { + '& ~ div': { + color: enabledColor, + }, + '& ~ span': { + boxSizing: 'border-box', + border: `1px solid ${enabledColor} !important`, + opacity: disabled ? 0.25 : 1, + }, + }, + span: { + position: 'absolute', + top: -1, + left: -1, + right: -1, + bottom: -1, + zIndex: 10, + borderWidth: '0 !important', + }, + }), + item: (disabled: boolean, large?: boolean) => + css({ + opacity: disabled ? 0.25 : 1, + padding: large ? '0px 15px' : '0px 7px', + width: '100%', + height: '100%', + cursor: 'pointer', + lineHeight: large ? '40px' : '30px', + }), +}; + +export function RadioButton(prop: RadioProps) { + const { label, large, disabled = false, ...radioProps } = prop; + return ( + +
{label}
+ +
+ ); +} diff --git a/src/components/forms/radio-button-group/RadioButtonGroup.tsx b/src/components/forms/radio-button-group/RadioButtonGroup.tsx new file mode 100644 index 00000000..6cd889b2 --- /dev/null +++ b/src/components/forms/radio-button-group/RadioButtonGroup.tsx @@ -0,0 +1,76 @@ +/** @jsxImportSource @emotion/react */ +import { RadioGroup, type RadioGroupProps } from '@blueprintjs/core'; +import { css } from '@emotion/react'; +import { Children, cloneElement } from 'react'; + +import { RadioButton } from './RadioButton.js'; + +export interface RadioButtonGroupProps extends RadioGroupProps { + large?: boolean; +} +const rootStyles = { + button: (large?: boolean) => + css({ + display: 'flex', + flexDirection: 'row', + width: 'fit-content', + ' & > *:first-of-type, & > *:first-of-type span': { + borderRadius: large ? '6px 0 0 6px' : '4px 0 0 4px', + }, + ' & > *:last-of-type, & > *:last-of-type span': { + borderRightWidth: 1, + borderRadius: large ? '0 6px 6px 0' : '0 4px 4px 0', + }, + }), +}; +export function RadioButtonGroup(props: RadioButtonGroupProps) { + const { + disabled: groupDisabled = false, + options = [], + name, + large, + selectedValue, + onChange, + children, + ...restProps + } = props; + + return ( + + {options?.map(({ value, label, disabled }) => { + const childProps = { + value, + label, + large, + name, + onChange, + checked: value === selectedValue, + disabled: groupDisabled || disabled, + }; + return ; + })} + {Children.map(children, (child) => { + if (!child || typeof child !== 'object' || !('type' in child)) { + return child; + } + if (child.type === RadioButton) { + return cloneElement(child, { + ...child.props, + large, + name, + onChange, + checked: child.props.value === selectedValue, + disabled: groupDisabled || child.props.disabled, + }); + } + return child; + })} + + ); +} diff --git a/src/components/forms/radio-button-group/index.ts b/src/components/forms/radio-button-group/index.ts new file mode 100644 index 00000000..d3ec6a3c --- /dev/null +++ b/src/components/forms/radio-button-group/index.ts @@ -0,0 +1,2 @@ +export * from './RadioButton.js'; +export * from './RadioButtonGroup.js'; diff --git a/src/components/forms/radio-group/ButtonRadioItem.tsx b/src/components/forms/radio-group/ButtonRadioItem.tsx deleted file mode 100644 index 489b7f70..00000000 --- a/src/components/forms/radio-group/ButtonRadioItem.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; -import * as RadioGroup from '@radix-ui/react-radio-group'; - -import { enabledColor, type InputVariant } from '../styles.js'; - -import type { RadioGroupProps, RadioOption } from './RadioGroup.js'; - -const buttonStyles = { - container: css({ - border: '1px solid rgba(0, 0, 0, 0.25)', - borderRightWidth: 0, - position: 'relative', - }), - item: (disabled: boolean, variant?: InputVariant) => - css({ - opacity: disabled ? 0.25 : 1, - padding: variant === 'default' ? '0px 15px' : '0px 7px', - width: '100%', - height: '100%', - cursor: 'pointer', - ':hover': { - '& > label': { - color: disabled ? '' : enabledColor, - }, - }, - }), - - indicator: css({ - position: 'absolute', - top: -1, - left: -1, - right: -1, - bottom: -1, - zIndex: 10, - border: '1px solid', - borderColor: enabledColor, - - '& ~ label': { - color: enabledColor, - }, - }), - - label: (variant?: InputVariant) => - css({ - cursor: 'pointer', - fontSize: variant === 'small' ? '1em' : '1.125em', - lineHeight: variant === 'default' ? '30px' : '22px', - }), -}; - -export function ButtonRadioItem( - prop: RadioOption & Pick, -) { - const { value, label, disabled = false, onSelect, variant, name } = prop; - return ( -
- { - if (!disabled) onSelect?.(prop); - }} - > - - - -
- ); -} diff --git a/src/components/forms/radio-group/RadioGroup.tsx b/src/components/forms/radio-group/RadioGroup.tsx deleted file mode 100644 index f78f0fc6..00000000 --- a/src/components/forms/radio-group/RadioGroup.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; -import * as RadioGroupRadix from '@radix-ui/react-radio-group'; -import type { ReactNode } from 'react'; - -import type { InputVariant } from '../styles.js'; - -import { ButtonRadioItem } from './ButtonRadioItem.js'; - -export interface RadioOption { - value: string; - label: ReactNode; - disabled?: boolean; -} -export interface RadioGroupProps { - selected?: RadioOption; - options?: RadioOption[]; - onSelect?: (option: RadioOption) => void; - name?: string; - disabled?: boolean; - variant?: InputVariant; - id?: string; -} -const rootStyles = { - basic: css({ - display: 'flex', - flexDirection: 'row', - width: 'fit-content', - }), - button: (variant: InputVariant) => - css({ - ' & > *:first-of-type, & > *:first-of-type span': { - borderRadius: variant === 'default' ? '6px 0 0 6px' : '4px 0 0 4px', - }, - ' & > *:last-of-type, & > *:last-of-type span': { - borderRightWidth: 1, - borderRadius: variant === 'default' ? '0 6px 6px 0' : '0 4px 4px 0', - }, - }), -}; -export function RadioGroup(props: RadioGroupProps) { - const { - id, - selected, - disabled: groupDisabled = false, - options = [], - onSelect, - name = '', - variant = 'default', - } = props; - return ( - - {options?.map(({ value, label, disabled }) => { - const childProps = { - value, - label, - disabled: groupDisabled || disabled, - onSelect, - variant, - name, - }; - return ; - })} - - ); -} diff --git a/src/components/forms/radio-group/index.ts b/src/components/forms/radio-group/index.ts deleted file mode 100644 index 49505d71..00000000 --- a/src/components/forms/radio-group/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './RadioGroup.js'; diff --git a/src/components/info-panel/InfoPanel.tsx b/src/components/info-panel/InfoPanel.tsx index 78eaa45a..8f18c3b2 100644 --- a/src/components/info-panel/InfoPanel.tsx +++ b/src/components/info-panel/InfoPanel.tsx @@ -1,7 +1,6 @@ /** @jsxImportSource @emotion/react */ -import { Icon, InputGroup } from '@blueprintjs/core'; +import { Collapse, InputGroup } from '@blueprintjs/core'; import { css } from '@emotion/react'; -import * as Collapsible from '@radix-ui/react-collapsible'; import { type CSSProperties, memo, @@ -10,6 +9,7 @@ import { useState, } from 'react'; +import { Button } from '../button/Button.js'; import { SelectedTotal } from '../selected-total/index.js'; import { createTableColumnHelper, Table } from '../table/index.js'; import * as ValueRenderers from '../value-renderers/index.js'; @@ -30,24 +30,6 @@ interface InfoPanelProps { const style = { content: css({ overflow: 'hidden', - "&[data-state='open']": { - animation: 'slideDown 300ms ease-out', - }, - '&[data-state="closed"]': { - animation: 'slideUp 300ms ease-out', - }, - '@keyframes slideDown': { - from: { - height: 0, - }, - to: { height: 'var(--radix-collapsible-content-height)' }, - }, - '@keyframes slideUp': { - from: { - height: 'var(--radix-collapsible-content-height)', - }, - to: { height: 0 }, - }, }), container: css({ padding: '5px 0 0 0', @@ -55,28 +37,27 @@ const style = { display: 'flex', flexDirection: 'column', }), - chevron: css({ - transition: 'all 0.3s ease-in-out', - }), - button: css({ - zIndex: 10, - position: 'sticky', - height: 30, - top: 0, - "&[data-state='open'] > span": { - rotate: '90deg', - }, - cursor: 'pointer', - borderBottom: '1px solid #f5f5f5', - backgroundColor: 'white', - display: 'flex', - alignItems: 'center', - padding: '5px 2px', - width: '100%', - ':hover': { - backgroundColor: '#f5f5f5', - }, - }), + button: (open: boolean) => + css({ + backgroundColor: 'white !important', + zIndex: 10, + position: 'sticky', + height: 30, + top: 0, + '& .bp5-icon': { + rotate: open ? '90deg' : '', + transition: 'all 0.3s ease-in-out', + }, + cursor: 'pointer', + borderBottom: '1px solid #f5f5f5', + display: 'flex', + alignItems: 'center', + padding: '5px 2px', + width: '100%', + ':hover': { + backgroundColor: '#f5f5f5 !important', + }, + }), }; interface InfoPanelDatum { @@ -210,6 +191,9 @@ interface InfoPanelContentProps { const InfoPanelContent = memo((props: InfoPanelContentProps) => { const { filteredData } = props; + const [isOpen, setIsOpen] = useState( + filteredData.map(({ description }) => description), + ); return (
{ }} > {filteredData.map(({ description, data }) => { + const open = isOpen.includes(description); return ( - - -
- - {description} -
-
- +
+ + { tableProps={{ style: { width: '100%' } }} compact /> - - + + ); })} diff --git a/stories/components/radio.stories.tsx b/stories/components/radio.stories.tsx index 37634b61..0a0cf21b 100644 --- a/stories/components/radio.stories.tsx +++ b/stories/components/radio.stories.tsx @@ -1,14 +1,11 @@ -import { - RadioGroup as BlueprintjsRadioGroup, - type RadioGroupProps as BlueprintjsRadioGroupProps, -} from '@blueprintjs/core'; +import { RadioGroup, type RadioGroupProps } from '@blueprintjs/core'; import styled from '@emotion/styled'; import { useState } from 'react'; import { - RadioGroup, - type RadioGroupProps, - type RadioOption, + RadioButton, + RadioButtonGroup, + type RadioButtonGroupProps, } from '../../src/components/index.js'; export default { @@ -29,22 +26,17 @@ const ExampleGroup = styled.div` gap: 20px; `; export function ControlBlueprint( - props: Omit< - BlueprintjsRadioGroupProps, - 'onChange' | 'selectedValue' | 'children' - >, + props: Omit, ) { - const [option, setOption] = useState(options[2]); + const [option, setOption] = useState(options[2].value); return ( - { const value = event.currentTarget.value; - setOption( - (option) => options.find((o) => o.value === value) || option, - ); + setOption(value); }} - selectedValue={option.value} + selectedValue={option} options={options} {...props} /> @@ -56,29 +48,57 @@ ControlBlueprint.args = { disabled: false, inline: false, }; -export function ControlButton( - props: Omit, + +export function ControlRadioButton( + props: Omit, ) { - const [option, setOption] = useState(options[2] as RadioOption); + const [option, setOption] = useState(options[2].value); return ( - { + const value = event.currentTarget.value; + setOption(value); + }} {...props} /> ); } -ControlButton.args = { - variant: 'default', +ControlRadioButton.args = { + large: false, disabled: false, }; -ControlButton.argTypes = { - variant: { - options: ['default', 'small'], - control: { type: 'radio' }, - }, +export function RadioButtonWithChildren( + props: Omit, +) { + const [option, setOption] = useState(options[2].value); + return ( + + { + const value = event.currentTarget.value; + setOption(value); + }} + {...props} + > + {options.map(({ value, label, disabled }) => ( + + ))} + + + ); +} +RadioButtonWithChildren.args = { + large: false, + disabled: false, };