-
Notifications
You must be signed in to change notification settings - Fork 69
Add line width setting for wizard line charts #3389
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
d252e7e
501c261
bf0ebcb
db7fa75
b25158f
326c313
c1b4b07
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 |
|---|---|---|
|
|
@@ -311,6 +311,8 @@ export type V15ColorsConfig = { | |
|
|
||
| export type V15ShapesConfig = { | ||
| mountedShapes?: Record<string, string>; | ||
| mountedShapesLineWidths?: Record<string, number>; | ||
| chartLineWidth?: number; | ||
|
||
| fieldGuid?: string; | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,47 @@ | ||
| import React from 'react'; | ||
|
|
||
| import {Button, TextInput} from '@gravity-ui/uikit'; | ||
| import {Minus, Plus} from '@gravity-ui/icons'; | ||
| import {Button, Icon, TextInput} from '@gravity-ui/uikit'; | ||
| import block from 'bem-cn-lite'; | ||
| import type {ValueOf} from 'shared'; | ||
|
|
||
| import './NumberInput.scss'; | ||
|
|
||
| const STEP_BUTTON_DIRECTION = { | ||
| Plus: '+', | ||
| Minus: '-', | ||
| } as const; | ||
|
|
||
| type StepButtonDirection = ValueOf<typeof STEP_BUTTON_DIRECTION>; | ||
|
|
||
| interface StepButtonProps { | ||
| direction: StepButtonDirection; | ||
| disabled: boolean; | ||
| onClick: () => void; | ||
| } | ||
|
|
||
| const DIRECTION_CONFIG: Record< | ||
| StepButtonDirection, | ||
| {icon: typeof Plus | typeof Minus; pin: 'brick-round' | 'round-brick'} | ||
| > = { | ||
| [STEP_BUTTON_DIRECTION.Plus]: {icon: Plus, pin: 'brick-round'}, | ||
| [STEP_BUTTON_DIRECTION.Minus]: {icon: Minus, pin: 'round-brick'}, | ||
| }; | ||
|
|
||
| const b = block('wizard-number-input'); | ||
|
||
|
|
||
| const StepButton: React.FC<StepButtonProps> = ({direction, disabled, onClick}) => { | ||
| const {icon, pin} = DIRECTION_CONFIG[direction]; | ||
|
|
||
| return ( | ||
| <div className={b('input-button')}> | ||
| <Button view="outlined" pin={pin} width="max" disabled={disabled} onClick={onClick}> | ||
| <Icon data={icon} /> | ||
| </Button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
||
|
|
||
| interface NumberInputProps { | ||
| value: number; | ||
| max?: number; | ||
|
|
@@ -13,53 +50,103 @@ interface NumberInputProps { | |
| qa?: string; | ||
| } | ||
|
|
||
| const b = block('wizard-number-input'); | ||
| const DEFAULT_VALUE = 0; | ||
|
|
||
| const NumberInput: React.FC<NumberInputProps> = ({ | ||
| value, | ||
| onChange, | ||
| max = Infinity, | ||
| min = -Infinity, | ||
| qa, | ||
| }) => { | ||
| const onBlur = React.useCallback(() => { | ||
| if (Number.isNaN(value)) { | ||
| onChange(DEFAULT_VALUE); | ||
| } | ||
| }, [onChange, value]); | ||
| const [internalValue, setInternalValue] = React.useState<string>(String(value)); | ||
|
|
||
| const onInput = React.useCallback((newValue) => onChange(parseInt(newValue, 10)), [onChange]); | ||
| React.useEffect(() => { | ||
| setInternalValue(String(value)); | ||
| }, [value]); | ||
|
|
||
| const onPlus = React.useCallback(() => { | ||
| const newValue = Number.isNaN(value) ? DEFAULT_VALUE : Math.min(value + 1, max); | ||
| onChange(newValue); | ||
| }, [max, onChange, value]); | ||
| const commitValue = React.useCallback( | ||
| (newValue: number) => { | ||
| setInternalValue(String(newValue)); | ||
| onChange(newValue); | ||
| }, | ||
| [onChange], | ||
| ); | ||
|
|
||
| const clampAndCommit = React.useCallback( | ||
| (delta = 0) => { | ||
| const parsed = parseInt(internalValue, 10); | ||
| const baseValue = Number.isNaN(parsed) ? value : parsed; | ||
| const newValue = baseValue + delta; | ||
|
|
||
| commitValue(Math.max(min, Math.min(max, newValue))); | ||
| }, | ||
| [internalValue, min, max, commitValue, value], | ||
| ); | ||
|
|
||
| const onBlur = React.useCallback( | ||
| (e: React.FocusEvent<HTMLInputElement>) => { | ||
| const relatedTarget = e.relatedTarget as HTMLElement | null; | ||
| if (relatedTarget?.closest(`.${b('input-button')}`)) { | ||
| return; | ||
| } | ||
|
|
||
| clampAndCommit(); | ||
| }, | ||
| [clampAndCommit], | ||
| ); | ||
|
|
||
| const onKeyDown = React.useCallback( | ||
| (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
| if (e.key === 'Enter') { | ||
| clampAndCommit(); | ||
| } | ||
| }, | ||
| [clampAndCommit], | ||
| ); | ||
|
|
||
| const onMinus = React.useCallback(() => { | ||
| const newValue = Number.isNaN(value) ? DEFAULT_VALUE : Math.max(value - 1, min); | ||
| onChange(newValue); | ||
| }, [min, onChange, value]); | ||
| const onInput = React.useCallback((newValue: string) => { | ||
| setInternalValue(newValue); | ||
| }, []); | ||
|
|
||
| const memorizedInputAttrs = React.useMemo(() => ({min, max}), [min, max]); | ||
| const onPlus = React.useCallback(() => clampAndCommit(1), [clampAndCommit]); | ||
|
|
||
| const onMinus = React.useCallback(() => clampAndCommit(-1), [clampAndCommit]); | ||
|
|
||
| const memorizedInputAttrs: React.InputHTMLAttributes<HTMLInputElement> = React.useMemo( | ||
| () => ({ | ||
| min: Number.isFinite(min) ? min : undefined, | ||
| max: Number.isFinite(max) ? max : undefined, | ||
| style: {textAlign: 'center'}, | ||
| }), | ||
| [min, max], | ||
| ); | ||
|
|
||
| const parsedInternal = parseInt(internalValue, 10); | ||
| const isMinusDisabled = !Number.isNaN(parsedInternal) && parsedInternal <= min; | ||
| const isPlusDisabled = !Number.isNaN(parsedInternal) && parsedInternal >= max; | ||
|
|
||
| return ( | ||
| <div className={b()}> | ||
| <Button pin="round-brick" onClick={onMinus}> | ||
| - | ||
| </Button> | ||
| <div className={b({})}> | ||
| <StepButton | ||
| direction={STEP_BUTTON_DIRECTION.Minus} | ||
| disabled={isMinusDisabled} | ||
| onClick={onMinus} | ||
| /> | ||
| <TextInput | ||
| qa={qa} | ||
| type="number" | ||
| pin="brick-brick" | ||
| controlProps={memorizedInputAttrs} | ||
| value={String(value)} | ||
| value={internalValue} | ||
| onBlur={onBlur} | ||
| onUpdate={onInput} | ||
| onKeyDown={onKeyDown} | ||
| className={b('text-input')} | ||
| /> | ||
| <StepButton | ||
| direction={STEP_BUTTON_DIRECTION.Plus} | ||
| disabled={isPlusDisabled} | ||
| onClick={onPlus} | ||
| /> | ||
| <Button pin="brick-round" onClick={onPlus}> | ||
| + | ||
| </Button> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import React from 'react'; | ||
|
|
||
| import {Flex, Text} from '@gravity-ui/uikit'; | ||
| import {i18n} from 'i18n'; | ||
| import NumberInput from 'ui/components/NumberFormatSettings/NumberInput/NumberInput'; | ||
| import { | ||
| LINE_WIDTH_AUTO_VALUE, | ||
| LINE_WIDTH_MAX_VALUE, | ||
| LINE_WIDTH_MIN_VALUE, | ||
| } from 'ui/units/wizard/constants/shapes'; | ||
|
|
||
| import {LineWidthSelect} from '../../../LineWidthSelect/LineWidthSelect'; | ||
|
|
||
| interface DialogLineWidthProps { | ||
| value: string; | ||
| onChange: (lineWidth: string) => void; | ||
| style?: React.CSSProperties; | ||
| allowDefault?: boolean; | ||
| } | ||
|
|
||
| export const DialogLineWidth = React.memo( | ||
| ({value, onChange, allowDefault, style}: DialogLineWidthProps) => { | ||
| const handleNumberInputChange = React.useCallback( | ||
| (nextValue: number) => { | ||
| return onChange(nextValue.toString()); | ||
| }, | ||
| [onChange], | ||
| ); | ||
|
|
||
| const isAutoValue = value === LINE_WIDTH_AUTO_VALUE; | ||
|
|
||
| const numberInput = React.useMemo(() => { | ||
| if (isAutoValue) { | ||
| return null; | ||
| } | ||
|
|
||
| const valueNumber = Number.parseInt(value, 10); | ||
|
|
||
| if (Number.isNaN(valueNumber)) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <Flex direction="row" alignItems="center" justifyContent="flex-end"> | ||
| <NumberInput | ||
| value={valueNumber} | ||
| min={LINE_WIDTH_MIN_VALUE} | ||
| max={LINE_WIDTH_MAX_VALUE} | ||
| onChange={handleNumberInputChange} | ||
| /> | ||
| </Flex> | ||
| ); | ||
| }, [handleNumberInputChange, value, isAutoValue]); | ||
|
|
||
| return ( | ||
| <Flex direction="column" gap={2} style={style}> | ||
| <Flex direction="row" alignItems="center" justifyContent="space-between"> | ||
| <Text variant="body-1">{i18n('wizard', 'label_dialog-line-width')}</Text> | ||
| <LineWidthSelect | ||
| allowDefault={allowDefault} | ||
| value={value} | ||
| onChange={onChange} | ||
| /> | ||
| </Flex> | ||
| {numberInput} | ||
| </Flex> | ||
| ); | ||
| }, | ||
| ); | ||
|
|
||
| DialogLineWidth.displayName = 'DialogLineWidth'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if title is an empty string (or null)? Is everything going to be okay?