Skip to content
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

Add readonly option #14077

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion docs/essentials/controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Here is the full list of available controls you can use:
| | color | color picker input that assumes strings are color values | - |
| | date | date picker input | - |

If you need to customize a control to use a enum data type in your story, for instance the `inline-radio` you can do it like so:
If you need to customize a control to use an enum data type in your story, for instance the `inline-radio` you can do it like so:

<!-- prettier-ignore-start -->

Expand Down Expand Up @@ -235,6 +235,8 @@ If you don't provide a specific one, it defaults to the number control type.

Controls supports the following configuration [parameters](../writing-stories/parameters.md), either globally or on a per-story basis:

> NOTE: `parameters` uses `controls`, while `argTypes` uses `control`.

## Show full documentation for each property

Since Controls is built on the same engine as Storybook Docs, it can also show property documentation alongside your controls using the expanded parameter (defaults to false). This means you embed a complete [ArgsTable](../writing-docs/doc-blocks.md#argstable) doc block in the controls pane. The description and default value rendering can be [customized](#fully-custom-args) in the same way as the doc block.
Expand Down Expand Up @@ -296,6 +298,15 @@ As with other Storybook properties, such as [decorators](../writing-stories/deco

</div>

### Read-only controls for specific properties

Alternatively, if you wish to still display the control but disallow edits, you can use the `readOnly` option.

<CodeSnippets paths={[
'common/component-story-readonly-controls.js.mdx',
'common/component-story-readonly-controls.mdx.mdx'
]} />

## Hide NoControls warning

If you don't plan to handle the control args inside your Story, you can remove the warning with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export default {
// foo is the property we want to remove from the UI
foo:{
table:{
disable:true
disable: true
}
}
}
};
```
```
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { Meta,Story, Canvas } from '@storybook/addon-docs/blocks';

import { YourComponent } from './your-component'

<Meta
title='My Story'
<Meta
title='My Story'
argTypes={{
foo:{
table:{
disable:true
disable: true
}
}
}} />
Expand All @@ -22,4 +22,4 @@ export const Template = (args) => <YourComponent {...args} />
{Template.bind({})}
</Story>
</Canvas>
```
```
18 changes: 18 additions & 0 deletions docs/snippets/common/component-story-readonly-controls.js.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
```js
// YourComponent.stories.js | YourComponent.stories.ts

import { YourComponent } from './your-component'

export default {
component: YourComponent,
title:'My Story',
argTypes:{
// foo is the property we want to make readonly on the UI
foo:{
control:{
readOnly: true
}
}
}
};
```
25 changes: 25 additions & 0 deletions docs/snippets/common/component-story-readonly-controls.mdx.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
```md
<!--- YourComponent.stories.mdx -->

import { Meta,Story, Canvas } from '@storybook/addon-docs/blocks';

import { YourComponent } from './your-component'

<Meta
title='My Story'
argTypes={{
foo:{
control: {
readOnly: true
}
}
}} />

export const Template = (args) => <YourComponent {...args} />

<Canvas>
<Story name="My Story" args={{ foo: 'bar'}}>
{Template.bind({})}
</Story>
</Canvas>
```
19 changes: 19 additions & 0 deletions examples/official-storybook/stories/addon-controls.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ export default {
component: Button,
argTypes: {
children: { control: 'text', name: 'Children' },
readonlyInput: {
name: 'Readonly input',
control: {
type: 'text',
readOnly: true,
},
},
disabledInput: {
name: 'Disabled input',
control: {
type: 'text',
disable: true,
},
},
type: { control: 'text', name: 'Type' },
json: { control: 'object', name: 'JSON' },
imageUrls: { control: { type: 'file', accept: '.png' }, name: 'Image Urls' },
Expand Down Expand Up @@ -135,3 +149,8 @@ FilteredWithExcludeRegex.parameters = {
exclude: /hello*/,
},
};
export const Readonly = Template.bind({});
Readonly.args = {
readonlyInput: 'This is read-only',
};
Readonly.parameters = {};
6 changes: 6 additions & 0 deletions lib/components/src/blocks/ArgsTable/ArgControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs }) => {
const onBlur = useCallback(() => setFocused(false), []);
const onFocus = useCallback(() => setFocused(true), []);

if (control?.disable && control?.readOnly) {
console.warn(
`Both "disable" and "readOnly" options were defined. The "disable" option takes precedence.`
);
}

if (!control || control.disable) return <NoControl />;

// row.name is a display name and not a suitable DOM input id or name - i might contain whitespace etc.
Expand Down
11 changes: 9 additions & 2 deletions lib/components/src/controls/Boolean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,21 @@ const format = (value: BooleanValue): string | null => (value ? String(value) :
const parse = (value: string | null) => value === 'true';

export type BooleanProps = ControlProps<BooleanValue> & BooleanConfig;
export const BooleanControl: FC<BooleanProps> = ({ name, value, onChange, onBlur, onFocus }) => (
export const BooleanControl: FC<BooleanProps> = ({
name,
value,
onChange,
onBlur,
onFocus,
readOnly,
}) => (
<Label htmlFor={name} title={value ? 'Change to false' : 'Change to true'}>
<input
id={name}
type="checkbox"
onChange={(e) => onChange(e.target.checked)}
checked={value || false}
{...{ name, onBlur, onFocus }}
{...{ name, onBlur, onFocus, readOnly }}
/>
<span>True</span>
<span>False</span>
Expand Down
11 changes: 9 additions & 2 deletions lib/components/src/controls/Date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ 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,
readOnly,
}) => {
const [valid, setValid] = useState(true);
const dateRef = useRef<HTMLInputElement>();
const timeRef = useRef<HTMLInputElement>();
Expand Down Expand Up @@ -102,7 +109,7 @@ export const DateControl: FC<DateProps> = ({ name, value, onChange, onFocus, onB
id={`${name}date`}
name={`${name}date`}
onChange={onDateChange}
{...{ onFocus, onBlur }}
{...{ onFocus, onBlur, readOnly }}
/>
<Form.Input
type="time"
Expand Down
2 changes: 2 additions & 0 deletions lib/components/src/controls/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const FilesControl: FunctionComponent<FilesControlProps> = ({
name,
accept = 'image/*',
value,
readOnly,
}) => {
function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) {
Expand All @@ -43,6 +44,7 @@ export const FilesControl: FunctionComponent<FilesControlProps> = ({
onChange={handleFileChange}
accept={accept}
size="flex"
{...{ readOnly }}
/>
);
};
3 changes: 2 additions & 1 deletion lib/components/src/controls/Number.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const NumberControl: FC<NumberProps> = ({
step,
onBlur,
onFocus,
readOnly,
}) => {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange(parse(event.target.value));
Expand All @@ -39,7 +40,7 @@ export const NumberControl: FC<NumberProps> = ({
size="flex"
placeholder="Adjust number dynamically"
value={value === null ? undefined : value}
{...{ name, min, max, step, onFocus, onBlur }}
{...{ name, min, max, step, onFocus, onBlur, readOnly }}
/>
</Wrapper>
);
Expand Down
3 changes: 2 additions & 1 deletion lib/components/src/controls/Range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export const RangeControl: FC<RangeProps> = ({
step = 1,
onBlur,
onFocus,
readOnly,
}) => {
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange(parse(event.target.value));
Expand All @@ -170,7 +171,7 @@ export const RangeControl: FC<RangeProps> = ({
<RangeInput
type="range"
onChange={handleChange}
{...{ name, value, min, max, step, onFocus, onBlur }}
{...{ name, value, min, max, step, onFocus, onBlur, readOnly }}
/>
<RangeLabel>{`${value} / ${max}`}</RangeLabel>
</RangeWrapper>
Expand Down
11 changes: 9 additions & 2 deletions lib/components/src/controls/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ const Wrapper = styled.label({

const format = (value?: TextValue) => value || '';

export const TextControl: FC<TextProps> = ({ name, value, onChange, onFocus, onBlur }) => {
export const TextControl: FC<TextProps> = ({
name,
value,
onChange,
onFocus,
onBlur,
readOnly,
}) => {
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
onChange(event.target.value);
};
Expand All @@ -23,7 +30,7 @@ export const TextControl: FC<TextProps> = ({ name, value, onChange, onFocus, onB
onChange={handleChange}
size="flex"
placeholder="Adjust string dynamically"
{...{ name, value: format(value), onFocus, onBlur }}
{...{ name, value: format(value), onFocus, onBlur, readOnly }}
/>
</Wrapper>
);
Expand Down
2 changes: 2 additions & 0 deletions lib/components/src/controls/options/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const CheckboxControl: FC<CheckboxProps> = ({
value,
onChange,
isInline,
readOnly,
}) => {
const initial = selectedKeys(value, options);
const [selected, setSelected] = useState(initial);
Expand Down Expand Up @@ -76,6 +77,7 @@ export const CheckboxControl: FC<CheckboxProps> = ({
value={key}
onChange={handleChange}
checked={selected?.includes(key)}
{...{ readOnly }}
/>
<Text>{key}</Text>
</Label>
Expand Down
10 changes: 9 additions & 1 deletion lib/components/src/controls/options/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ const Label = styled.label({

type RadioConfig = NormalizedOptionsConfig & { isInline: boolean };
type RadioProps = ControlProps<OptionsSingleSelection> & RadioConfig;
export const RadioControl: FC<RadioProps> = ({ name, options, value, onChange, isInline }) => {
export const RadioControl: FC<RadioProps> = ({
name,
options,
value,
onChange,
isInline,
readOnly,
}) => {
const selection = selectedKey(value, options);
return (
<Wrapper isInline={isInline}>
Expand All @@ -62,6 +69,7 @@ export const RadioControl: FC<RadioProps> = ({ name, options, value, onChange, i
value={key}
onChange={(e) => onChange(options[e.currentTarget.value])}
checked={key === selection}
{...{ readOnly }}
/>
<Text>{key}</Text>
</Label>
Expand Down
1 change: 1 addition & 0 deletions lib/components/src/controls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface ControlProps<T> {
onChange: (value: T) => T | void;
onFocus?: (evt: any) => void;
onBlur?: (evt: any) => void;
readOnly?: boolean;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me, it fixes TS types in the various control option components, but I'm not sure it's correct.

Also, if it's correct, the disable property should be added there, too.

}

export type ArrayValue = string[] | readonly string[];
Expand Down