Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d3ad059
Add table.readonly to argType typing
valentinpalkovic Mar 20, 2024
2f66985
Support readonly mode for Boolean input
valentinpalkovic Mar 20, 2024
d1bbb95
Support readonly mode for Color input
valentinpalkovic Mar 20, 2024
fb3e179
Support readonly mode for Date input
valentinpalkovic Mar 20, 2024
613da90
Support readonly mode for Files input
valentinpalkovic Mar 20, 2024
6fc5915
Support readonly mode for Number input
valentinpalkovic Mar 20, 2024
2267fa4
Support readonly mode for Object input
valentinpalkovic Mar 20, 2024
a57b40e
Support readonly mode for Range input
valentinpalkovic Mar 20, 2024
d5c8563
Support readonly mode for Text input
valentinpalkovic Mar 20, 2024
18f7fad
Support readonly mode for Checkbox input
valentinpalkovic Mar 20, 2024
3e89607
Support readonly mode for Radio input
valentinpalkovic Mar 20, 2024
a4489a0
Support readonly mode for Select input
valentinpalkovic Mar 20, 2024
91b2abc
Refactor OptionsControl to include argType in normalized props
valentinpalkovic Mar 20, 2024
757ddf5
Update OptionsConfig interface in types.ts
valentinpalkovic Mar 20, 2024
4c188e6
Add a new Example Story to showcase the button component with readonl…
valentinpalkovic Mar 20, 2024
c209329
Add documentation for argTypes.<input>.table.readonly
valentinpalkovic Mar 20, 2024
baac183
Set onlyChanged:false in chronatic.config.json
valentinpalkovic Mar 20, 2024
098adc7
Set onlyChanged:true in chronatic.config.json
valentinpalkovic Mar 20, 2024
a42f217
Improve typing for ArgType.table
valentinpalkovic Mar 21, 2024
f13ed7f
Fix readonly assignment in control components
valentinpalkovic Mar 21, 2024
0dd73ea
Fix styling issue in RawInput component
valentinpalkovic Mar 21, 2024
5ac51f6
Remove deprecated parameters from ButtonReadonly.stories.tsx
valentinpalkovic Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions code/ui/blocks/src/components/ArgsTable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ export interface ArgType {
description?: string;
defaultValue?: any;
if?: Conditional;
table?: {
category?: string;
disable?: boolean;
subcategory?: string;
defaultValue?: {
summary: string;
detail?: string;
};
type?: {
summary: string;
detail?: string;
};
readonly?: boolean;
Comment thread
valentinpalkovic marked this conversation as resolved.
[key: string]: any;
};
[key: string]: any;
}

Expand Down
35 changes: 28 additions & 7 deletions code/ui/blocks/src/controls/Boolean.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { within, fireEvent, waitFor, expect } from '@storybook/test';
import { within, fireEvent, waitFor, expect, fn } from '@storybook/test';
import { addons } from '@storybook/preview-api';
import { RESET_STORY_ARGS, STORY_ARGS_UPDATED } from '@storybook/core-events';
import { BooleanControl } from './Boolean';
Expand All @@ -14,31 +14,36 @@ const meta = {
info: 'This is info for the Boolean control stories',
jsx: { useBooleanShorthandSyntax: false },
},
} as Meta<typeof BooleanControl>;
args: {
onChange: fn(),
},
} satisfies Meta<typeof BooleanControl>;

export default meta;

export const True: StoryObj<typeof BooleanControl> = {
type Story = StoryObj<typeof meta>;

export const True: Story = {
args: {
value: true,
name: 'True',
},
};
export const False: StoryObj<typeof BooleanControl> = {
export const False: Story = {
args: {
value: false,
name: 'False',
},
};

export const Undefined: StoryObj<typeof BooleanControl> = {
export const Undefined: Story = {
args: {
value: undefined,
name: 'Undefined',
},
};

export const Toggling: StoryObj<typeof BooleanControl> = {
export const Toggling: Story = {
args: {
value: undefined,
name: 'Toggling',
Expand Down Expand Up @@ -78,7 +83,7 @@ export const Toggling: StoryObj<typeof BooleanControl> = {
},
};

export const TogglingInDocs: StoryObj<typeof BooleanControl> = {
export const TogglingInDocs: Story = {
...Toggling,
args: {
name: 'Toggling In Docs',
Expand All @@ -89,3 +94,19 @@ export const TogglingInDocs: StoryObj<typeof BooleanControl> = {
},
},
};

export const Readonly: Story = {
args: {
name: 'readonly',
value: true,
argType: { table: { readonly: true } },
},
};

export const ReadonlyAndUndefined: Story = {
args: {
name: 'readonly-and-undefined',
value: undefined,
argType: { table: { readonly: true } },
},
};
21 changes: 19 additions & 2 deletions code/ui/blocks/src/controls/Boolean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ const Label = styled.label(({ theme }) => ({
background: theme.boolean.background,
borderRadius: '3em',
padding: 1,
'&[aria-disabled="true"]': {
opacity: 0.5,

input: {
cursor: 'not-allowed',
},
},

input: {
appearance: 'none',
Expand Down Expand Up @@ -98,15 +105,24 @@ export type BooleanProps = ControlProps<BooleanValue> & BooleanConfig;
* <BooleanControl name="isTrue" value={value} onChange={handleValueChange}/>
* ```
*/
export const BooleanControl: FC<BooleanProps> = ({ name, value, onChange, onBlur, onFocus }) => {
export const BooleanControl: FC<BooleanProps> = ({
name,
value,
onChange,
onBlur,
onFocus,
argType,
}) => {
const onSetFalse = useCallback(() => onChange(false), [onChange]);
const readonly = !!argType?.table?.readonly;
if (value === undefined) {
return (
<Button
variant="outline"
size="medium"
id={getControlSetterButtonId(name)}
onClick={onSetFalse}
disabled={readonly}
>
Set boolean
</Button>
Expand All @@ -117,13 +133,14 @@ export const BooleanControl: FC<BooleanProps> = ({ name, value, onChange, onBlur
const parsedValue = typeof value === 'string' ? parse(value) : value;

return (
<Label htmlFor={controlId} aria-label={name}>
<Label aria-disabled={readonly} htmlFor={controlId} aria-label={name}>
<input
id={controlId}
type="checkbox"
onChange={(e) => onChange(e.target.checked)}
checked={parsedValue}
role="switch"
disabled={readonly}
{...{ name, onBlur, onFocus }}
/>
<span aria-hidden="true">False</span>
Expand Down
26 changes: 19 additions & 7 deletions code/ui/blocks/src/controls/Color.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { ColorControl } from './Color';

export default {
const meta = {
component: ColorControl,
parameters: { withRawArg: 'value', controls: { include: ['value', 'startOpen'] } },
tags: ['autodocs'],
Expand All @@ -12,22 +13,26 @@ export default {
},
},
},
args: { name: 'color' },
} as Meta<typeof ColorControl>;
args: { name: 'color', onChange: fn() },
} satisfies Meta<typeof ColorControl>;

export const Basic: StoryObj<typeof ColorControl> = {
export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
args: {
value: '#ff00ff',
},
};

export const Undefined: StoryObj<typeof ColorControl> = {
export const Undefined: Story = {
args: {
value: undefined,
},
};

export const WithPresetColors: StoryObj<typeof ColorControl> = {
export const WithPresetColors: Story = {
args: {
value: '#00ffff',
presetColors: [
Expand Down Expand Up @@ -58,8 +63,15 @@ export const WithPresetColors: StoryObj<typeof ColorControl> = {
},
};

export const StartOpen: StoryObj<typeof ColorControl> = {
export const StartOpen: Story = {
args: {
startOpen: true,
},
};

export const Readonly: Story = {
args: {
value: '#ff00ff',
argType: { table: { readonly: true } },
},
};
23 changes: 17 additions & 6 deletions code/ui/blocks/src/controls/Color.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import { MarkupIcon } from '@storybook/icons';
const Wrapper = styled.div({
position: 'relative',
maxWidth: 250,
'&[aria-readonly="true"]': {
opacity: 0.5,
},
});

const PickerTooltip = styled(WithTooltip)({
position: 'absolute',
zIndex: 1,
top: 4,
left: 4,
'[aria-readonly=true] &': {
cursor: 'not-allowed',
},
});

const TooltipContent = styled.div({
Expand Down Expand Up @@ -50,7 +56,7 @@ const Swatches = styled.div({
width: 200,
});

const SwatchColor = styled.div<{ active: boolean }>(({ theme, active }) => ({
const SwatchColor = styled.div<{ active?: boolean }>(({ theme, active }) => ({
width: 16,
height: 16,
boxShadow: active
Expand All @@ -61,13 +67,13 @@ const SwatchColor = styled.div<{ active: boolean }>(({ theme, active }) => ({

const swatchBackground = `url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><path d="M8 0h8v8H8zM0 8h8v8H0z"/></svg>')`;

type SwatchProps = { value: string; active?: boolean; onClick?: () => void; style?: object };
const Swatch = ({ value, active, onClick, style, ...props }: SwatchProps) => {
type SwatchProps = { value: string } & React.ComponentProps<typeof SwatchColor>;
const Swatch = ({ value, style, ...props }: SwatchProps) => {
const backgroundImage = `linear-gradient(${value}, ${value}), ${swatchBackground}, linear-gradient(#fff, #fff)`;
return <SwatchColor {...props} {...{ active, onClick }} style={{ ...style, backgroundImage }} />;
return <SwatchColor {...props} style={{ ...style, backgroundImage }} />;
};

const Input = styled(Form.Input)(({ theme }) => ({
const Input = styled(Form.Input)(({ theme, readOnly }) => ({
Comment thread
valentinpalkovic marked this conversation as resolved.
width: '100%',
paddingLeft: 30,
paddingRight: 30,
Expand Down Expand Up @@ -309,6 +315,7 @@ export const ColorControl: FC<ColorControlProps> = ({
onBlur,
presetColors,
startOpen = false,
argType,
}) => {
const throttledOnChange = useCallback(throttle(onChange, 200), [onChange]);
const { value, realValue, updateValue, color, colorSpace, cycleColorSpace } = useColorInput(
Expand All @@ -318,10 +325,13 @@ export const ColorControl: FC<ColorControlProps> = ({
const { presets, addPreset } = usePresets(presetColors, color, colorSpace);
const Picker = ColorPicker[colorSpace];

const readonly = !!argType?.table?.readonly;

return (
<Wrapper>
<Wrapper aria-readonly={readonly}>
<PickerTooltip
startOpen={startOpen}
trigger={readonly ? [null] : undefined}
closeOnOutsideClick
onVisibleChange={() => addPreset(color)}
tooltip={
Expand Down Expand Up @@ -357,6 +367,7 @@ export const ColorControl: FC<ColorControlProps> = ({
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => updateValue(e.target.value)}
onFocus={(e: FocusEvent<HTMLInputElement>) => e.target.select()}
readOnly={readonly}
placeholder="Choose color..."
/>
{value ? <ToggleIcon onClick={cycleColorSpace} /> : null}
Expand Down
23 changes: 18 additions & 5 deletions code/ui/blocks/src/controls/Date.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { DateControl } from './Date';

export default {
const meta = {
component: DateControl,
tags: ['autodocs'],
parameters: { withRawArg: 'value', controls: { include: ['value'] } },
Expand All @@ -11,12 +12,24 @@ export default {
control: { type: 'date' },
},
},
args: { name: 'date' },
} as Meta<typeof DateControl>;
args: { name: 'date', onChange: fn() },
} satisfies Meta<typeof DateControl>;

export const Basic: StoryObj<typeof DateControl> = {
export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
args: { value: new Date('2020-10-20T09:30:02') },
};
export const Undefined: StoryObj<typeof DateControl> = {

export const Undefined: Story = {
args: { value: undefined },
};

export const Readonly: Story = {
args: {
value: new Date('2020-10-20T09:30:02'),
argType: { table: { readonly: true } },
},
};
14 changes: 11 additions & 3 deletions code/ui/blocks/src/controls/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const formatTime = (value: Date | number) => {
return `${hours}:${minutes}`;
};

const FormInput = styled(Form.Input)(({ readOnly }) => ({
opacity: readOnly ? 0.5 : 1,
}));

const FlexSpaced = styled.div(({ theme }) => ({
flex: 1,
display: 'flex',
Expand All @@ -61,10 +65,12 @@ const FlexSpaced = styled.div(({ theme }) => ({
}));

export type DateProps = ControlProps<DateValue> & DateConfig;
export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onBlur }) => {
export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onBlur, argType }) => {
const [valid, setValid] = useState(true);
const dateRef = useRef<HTMLInputElement>();
const timeRef = useRef<HTMLInputElement>();
const readonly = !!argType?.table?.readonly;

useEffect(() => {
if (valid !== false) {
if (dateRef && dateRef.current) {
Expand Down Expand Up @@ -99,21 +105,23 @@ export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onB

return (
<FlexSpaced>
<Form.Input
<FormInput
type="date"
max="9999-12-31" // I do this because of a rendering bug in chrome
ref={dateRef as RefObject<HTMLInputElement>}
id={`${controlId}-date`}
name={`${controlId}-date`}
readOnly={readonly}
onChange={onDateChange}
{...{ onFocus, onBlur }}
/>
<Form.Input
<FormInput
type="time"
id={`${controlId}-time`}
name={`${controlId}-time`}
ref={timeRef as RefObject<HTMLInputElement>}
onChange={onTimeChange}
readOnly={readonly}
{...{ onFocus, onBlur }}
/>
{!valid ? <div>invalid</div> : null}
Expand Down
Loading