Skip to content

Commit

Permalink
fix(NumberInput): review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
DaryaLari committed Sep 27, 2024
1 parent 13ec1b6 commit b7b173b
Show file tree
Hide file tree
Showing 35 changed files with 231 additions and 397 deletions.
3 changes: 0 additions & 3 deletions src/components/lab/NumberInput/NumberInput.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
@use '../../../../styles/mixins';
@use '../../variables';
@use '../../controls/mixins.scss' as control-mixins;
@use '../../controls/variables.scss' as control-variables;

$block: '.#{variables.$ns}number-input';

Expand Down
16 changes: 4 additions & 12 deletions src/components/lab/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {block} from '../../utils/cn';
import {NumericArrows} from './NumericArrows/NumericArrows';
import {
clampToNearestStepValue,
getInternalVariables,
getInternalState,
getParsedValue,
getPossibleNumberSubstring,
updateCursorPosition,
Expand Down Expand Up @@ -118,7 +118,7 @@ export const NumberInput = React.forwardRef<HTMLSpanElement, NumberInputProps>(f
const {
value: externalValue,
defaultValue: externalDefaultValue,
onChange,
onChange: handleChange,
onUpdate,
min: externalMin,
max: externalMax,
Expand All @@ -145,7 +145,7 @@ export const NumberInput = React.forwardRef<HTMLSpanElement, NumberInputProps>(f
value,
defaultValue,
shiftMultiplier,
} = getInternalVariables({
} = getInternalState({
min: externalMin,
max: externalMax,
step: externalStep,
Expand Down Expand Up @@ -234,6 +234,7 @@ export const NumberInput = React.forwardRef<HTMLSpanElement, NumberInputProps>(f

const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
setActive(false);
setStep(baseStep);
if (clamp) {
const clampedValue = clampToNearestStepValue({
value,
Expand All @@ -248,15 +249,6 @@ export const NumberInput = React.forwardRef<HTMLSpanElement, NumberInputProps>(f
onBlur?.(e);
};

const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const preparedStringValue = getPossibleNumberSubstring(e.target.value, allowDecimal);
updateCursorPosition(innerControlRef, e.target.value, preparedStringValue);
const {valid, value: parsedNumberValue} = getParsedValue(preparedStringValue);
if (valid && parsedNumberValue !== value) {
onChange?.(e);
}
};

const handleUpdate = (v: string) => {
setInputValue(v);
const preparedStringValue = getPossibleNumberSubstring(v, allowDecimal);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
@use '../../../../../styles/mixins';
@use '../../../variables';
@use '../../../controls/mixins.scss' as control-mixins;
@use '../../../controls/variables.scss' as control-variables;

$block: '.#{variables.$ns}numeric-arrows';

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
248 changes: 212 additions & 36 deletions src/components/lab/NumberInput/__stories__/NumberInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React from 'react';

import type {Meta, StoryFn, StoryObj} from '@storybook/react';
import {ArrowShapeUpToLine} from '@gravity-ui/icons';
import type {Meta, StoryObj} from '@storybook/react';

import {Showcase} from '../../../../demo/Showcase';
import {Button} from '../../../Button';
import {Checkbox} from '../../../Checkbox';
import {Icon} from '../../../Icon';
import {Text} from '../../../Text';
import {Flex} from '../../../layout';
import {NumberInput} from '../NumberInput';
import type {NumberInputProps} from '../NumberInput';

import {NumberInputShowcase} from './NumberInputShowcase';
import {NumberInputSizes} from './NumberInputSizes';
import {getNumericInputValidator} from '../utils';

export default {
title: 'Lab/NumberInput',
Expand Down Expand Up @@ -37,51 +42,222 @@ export default {
},
} as Meta;

const fixConsoleErrors = {
onKeyDown: () => {},
onKeyUp: () => {},
onKeyPress: () => {},
};
type Story = StoryObj<typeof NumberInput>;

const DefaultTemplate: StoryFn<NumberInputProps> = (args) => {
function StoryWithState(args: NumberInputProps) {
const [value, setValue] = React.useState(args.value ?? args.defaultValue ?? undefined);
return <NumberInput {...fixConsoleErrors} {...args} value={value} onUpdate={setValue} />;
};
export const Default = DefaultTemplate.bind({});
return <NumberInput {...args} value={value} onUpdate={setValue} />;
}

const ShowcaseTemplate: StoryFn<NumberInputProps> = (args: NumberInputProps) => (
<NumberInputShowcase {...args} />
);
export const Showcase = ShowcaseTemplate.bind({});
export const Default: Story = {
args: {},
render: StoryWithState,
};

export const Basic: StoryObj<typeof NumberInput> = {
export const Sizes: Story = {
args: {
...fixConsoleErrors,
...Default.args,
},
render: (args: NumberInputProps) => <NumberInputSizes {...args} />,
render: (args) => (
<Showcase>
<StoryWithState size="s" placeholder="size=s" {...args} />
<StoryWithState size="m" placeholder="size=m" {...args} />
<StoryWithState size="l" placeholder="size=l" {...args} />
<StoryWithState size="xl" placeholder="size=xl" {...args} />
</Showcase>
),
};

export const WithErrors: StoryObj<typeof NumberInput> = {
export const Errors: Story = {
args: {
...fixConsoleErrors,
...Default.args,
validationState: 'invalid',
errorPlacement: 'inside',
errorMessage: 'A validation error has occurred',
hasClear: true,
label: 'Label:',
},
render: (args: NumberInputProps) => <NumberInputSizes {...args} />,
render: (args) => (
<Showcase>
<StoryWithState placeholder="without message" {...args} />
<StoryWithState
placeholder="inside error placement"
errorPlacement="inside"
errorMessage="A validation error has occurred"
{...args}
/>
<StoryWithState
placeholder="outside error placement"
errorPlacement="outside"
errorMessage="A validation error has occurred"
{...args}
/>
</Showcase>
),
};

export const ViewClear: StoryObj<typeof NumberInput> = {
export const View: Story = {
args: {
...fixConsoleErrors,
view: 'clear',
validationState: 'invalid',
errorPlacement: 'inside',
errorMessage: 'A validation error has occurred',
hasClear: true,
label: 'Label:',
...Default.args,
},
render: (args) => (
<Showcase>
<StoryWithState placeholder="view=normal" view="normal" {...args} />
<StoryWithState placeholder="view=normal disabled" view="normal" disabled {...args} />
<StoryWithState placeholder="view=clear" view="clear" {...args} />
<StoryWithState placeholder="view=clear disabled" view="clear" disabled {...args} />
</Showcase>
),
};

export const Controls: Story = {
args: {
...Default.args,
},
render: (args) => (
<Showcase>
<StoryWithState placeholder="with controls" {...args} />
<StoryWithState placeholder="without controls" hasControls={false} {...args} />
<StoryWithState placeholder="has clear" value={123} hasClear {...args} />
</Showcase>
),
};

export const AdditionalContent: Story = {
args: {
...Default.args,
},
render: (args) => (
<Showcase>
<StoryWithState placeholder="without start content" startContent="$" {...args} />
<StoryWithState placeholder="without end content" endContent="$" {...args} />
<StoryWithState
placeholder="without button start content"
startContent={
<Button size={args.size ?? 'm'} view="flat" title="example button">
<Icon data={ArrowShapeUpToLine} />
</Button>
}
{...args}
/>
<StoryWithState
placeholder="without button end content"
endContent={
<Button size={args.size ?? 'm'} view="flat" title="example button">
<Icon data={ArrowShapeUpToLine} />
</Button>
}
{...args}
/>
</Showcase>
),
};

export const Step: Story = {
args: {
...Default.args,
},
render: (args) => (
<Showcase>
<StoryWithState placeholder="default [step=1]" {...args} />
<StoryWithState step={5} placeholder="step=5" {...args} />
<StoryWithState shiftMultiplier={50} placeholder="shiftMultiplier=50" {...args} />
<StoryWithState step={5} min={2} placeholder="step=5 min=2" {...args} />
<StoryWithState
step={1.25}
allowDecimal
placeholder="step=1.25 with allowDecimal"
{...args}
/>
</Showcase>
),
};

export const MinMax: Story = {
args: {
...Default.args,
},
render: (args) => (
<Showcase>
<StoryWithState min={-1000} max={1000} placeholder="min=-1000 max=1000" {...args} />
<StoryWithState
min={-10.25}
max={1000}
allowDecimal
placeholder="min=-10.25 max=1000 with allowDecimal"
{...args}
/>
</Showcase>
),
};

export const WithValidators: Story = {
args: {
...Default.args,
},
render: function WithValidatorsStory(args) {
const [value, setValue] = React.useState(args.value ?? args.defaultValue ?? undefined);
const [positiveOnly, setPositiveOnly] = React.useState(false);
const [withoutFraction, setWithoutFraction] = React.useState(true);

const validatorProps = {
positiveOnly,
withoutFraction,
min: -10000,
max: 10000,
};

const {pattern, validator} = getNumericInputValidator(validatorProps);
const validationProps = {
validationState: validator(value) ? ('invalid' as const) : undefined,
errorMessage: validator(value),
controlProps: {pattern},
};
console.log(value);
return (
<Flex gap={4}>
<Flex direction="column" gap={4}>
<NumberInput
{...args}
{...validationProps}
allowDecimal
step={0.1}
value={value}
onUpdate={setValue}
placeholder={`step=0.1 allowDecimal validator=${JSON.stringify(validatorProps)}`}
/>
<NumberInput
{...args}
{...validationProps}
allowDecimal
step={0.1}
value={value}
onUpdate={setValue}
controlProps={{pattern}}
placeholder={`with pattern, step=0.1 allowDecimal validator=${JSON.stringify(validatorProps)}`}
/>
</Flex>
<Flex direction="column" gap={4}>
<Checkbox checked={positiveOnly} onUpdate={setPositiveOnly}>
positiveOnly
</Checkbox>
<Checkbox checked={withoutFraction} onUpdate={setWithoutFraction}>
withoutFraction
</Checkbox>
</Flex>
</Flex>
);
},
};

export const TextHints: Story = {
args: {
...Default.args,
},
render: (args: NumberInputProps) => <NumberInputSizes {...args} />,
render: (args) => (
<Showcase>
<StoryWithState placeholder="with label" label="Label:" {...args} />
<StoryWithState
placeholder="with note"
note={<Text color="secondary">Additional</Text>}
{...args}
/>
</Showcase>
),
};

This file was deleted.

Loading

0 comments on commit b7b173b

Please sign in to comment.