Skip to content

Angular: Correctly set the 'required' argType #28718

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

Open
wants to merge 19 commits into
base: next
Choose a base branch
from
Open
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
22 changes: 15 additions & 7 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ import type { FC } from 'react';
import React, { useCallback, useEffect, useState } from 'react';

import { Link } from 'storybook/internal/components';
import type { StrictInputType } from 'storybook/internal/csf';

import {
BooleanControl,
ColorControl,
type ControlProps,
DateControl,
FilesControl,
NumberControl,
ObjectControl,
OptionsControl,
RangeControl,
TextControl,
isControlObject,
} from '../../controls';
import type { ArgType, Args } from './types';
import type { Args } from './types';

export interface ArgControlProps {
row: ArgType;
row: StrictInputType;
arg: any;
updateArgs: (args: Args) => void;
isHovered: boolean;
Expand Down Expand Up @@ -69,8 +70,10 @@ export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs, isHovere
const onBlur = useCallback(() => setFocused(false), []);
const onFocus = useCallback(() => setFocused(true), []);

if (!control || control.disable) {
const canBeSetup = control?.disable !== true && row?.type?.name !== 'function';
if (!control || (isControlObject(control) && control.disable)) {
const canBeSetup = isControlObject(control)
? control.disable !== true && row?.type?.name !== 'function'
: false;
return isHovered && canBeSetup ? (
<Link href="https://storybook.js.org/docs/essentials/controls" target="_blank" withArrow>
Setup controls
Expand All @@ -82,6 +85,11 @@ export const ArgControl: FC<ArgControlProps> = ({ row, arg, updateArgs, isHovere
// row.name is a display name and not a suitable DOM input id or name - i might contain whitespace etc.
// row.key is a hash key and therefore a much safer choice
const props = { name: key, argType: row, value: boxedValue.value, onChange, onBlur, onFocus };
const Control = Controls[control.type] || NoControl;
return <Control {...props} {...control} controlType={control.type} />;
const Control = typeof control !== 'string' ? (Controls[control.type] ?? NoControl) : NoControl;

if (typeof control === 'string') {
return <Control {...props} />;
} else {
return <Control {...props} {...control} controlType={control.type} />;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';

import { ResetWrapper } from 'storybook/internal/components';

import type { InputType } from '@storybook/csf';

import { ArgRow } from './ArgRow';
import { TableWrapper } from './ArgsTable';

Expand Down Expand Up @@ -31,6 +33,7 @@ export const String = {
description: 'someString description',
type: {
required: true,
name: 'string',
},
control: {
type: 'text',
Expand All @@ -43,7 +46,7 @@ export const String = {
summary: 'reallylongstringnospaces',
},
},
},
} satisfies InputType,
},
};

Expand Down Expand Up @@ -127,6 +130,7 @@ export const Number = {
description: 'someNumber description',
type: {
required: false,
name: 'number',
},
table: {
type: {
Expand All @@ -139,7 +143,7 @@ export const Number = {
control: {
type: 'number',
},
},
} satisfies InputType,
},
};

Expand Down
20 changes: 11 additions & 9 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React, { useState } from 'react';

import { codeCommon } from 'storybook/internal/components';

import type { InputType, SBType } from '@storybook/csf';

import Markdown from 'markdown-to-jsx';
import { transparentize } from 'polished';
import type { CSSObject } from 'storybook/theming';
Expand All @@ -12,10 +14,10 @@ import type { ArgControlProps } from './ArgControl';
import { ArgControl } from './ArgControl';
import { ArgJsDoc } from './ArgJsDoc';
import { ArgValue } from './ArgValue';
import type { ArgType, Args, TableAnnotation } from './types';
import type { Args } from './types';

interface ArgRowProps {
row: ArgType;
row: InputType;
arg: any;
updateArgs?: (args: Args) => void;
compact?: boolean;
Expand Down Expand Up @@ -78,9 +80,9 @@ const StyledTd = styled.td<{ expandable: boolean }>(({ expandable }) => ({
paddingLeft: expandable ? '40px !important' : '20px !important',
}));

const toSummary = (value: any) => {
const toSummary = (value: InputType['type']): { summary: string } => {
if (!value) {
return value;
return value as any;
}
const val = typeof value === 'string' ? value : value.name;
return { summary: val };
Expand All @@ -90,10 +92,10 @@ export const ArgRow: FC<ArgRowProps> = (props) => {
const [isHovered, setIsHovered] = useState(false);
const { row, updateArgs, compact, expandable, initialExpandedArgs } = props;
const { name, description } = row;
const table = (row.table || {}) as TableAnnotation;
const type = table.type || toSummary(row.type);
const table = row.table || {};
const tableType = table.type || toSummary(row.type);
const defaultValue = table.defaultValue || row.defaultValue;
const required = row.type?.required;
const required = typeof row.type === 'string' ? false : row.type?.required;
const hasDescription = description != null && description !== '';

return (
Expand All @@ -112,13 +114,13 @@ export const ArgRow: FC<ArgRowProps> = (props) => {
{table.jsDocTags != null ? (
<>
<TypeWithJsDoc hasDescription={hasDescription}>
<ArgValue value={type} initialExpandedArgs={initialExpandedArgs} />
<ArgValue value={tableType} initialExpandedArgs={initialExpandedArgs} />
</TypeWithJsDoc>
<ArgJsDoc tags={table.jsDocTags} />
</>
) : (
<Type hasDescription={hasDescription}>
<ArgValue value={type} initialExpandedArgs={initialExpandedArgs} />
<ArgValue value={tableType} initialExpandedArgs={initialExpandedArgs} />
</Type>
)}
</td>
Expand Down
7 changes: 3 additions & 4 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import React, { useState } from 'react';

import { SyntaxHighlighter, WithTooltipPure, codeCommon } from 'storybook/internal/components';

import type { InputType, SBType } from '@storybook/csf';
import { ChevronSmallDownIcon, ChevronSmallUpIcon } from '@storybook/icons';

import { uniq } from 'es-toolkit/compat';
import memoize from 'memoizerific';
import { styled } from 'storybook/theming';

import type { PropSummaryValue } from './types';

interface ArgValueProps {
value?: PropSummaryValue;
value?: InputType['table']['type'];
initialExpandedArgs?: boolean;
}

Expand All @@ -22,7 +21,7 @@ interface ArgTextProps {
}

interface ArgSummaryProps {
value: PropSummaryValue;
value: InputType['table']['type'];
initialExpandedArgs?: boolean;
}

Expand Down
42 changes: 26 additions & 16 deletions code/addons/docs/src/blocks/components/ArgsTable/ArgsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';

import { once } from 'storybook/internal/client-logger';
import { IconButton, Link, ResetWrapper } from 'storybook/internal/components';
import { includeConditionalArg } from 'storybook/internal/csf';
import { type InputType, includeConditionalArg } from 'storybook/internal/csf';

import { DocumentIcon, UndoIcon } from '@storybook/icons';

Expand All @@ -16,7 +16,7 @@ import { ArgRow } from './ArgRow';
import { Empty } from './Empty';
import { SectionRow } from './SectionRow';
import { Skeleton } from './Skeleton';
import type { ArgType, ArgTypes, Args, Globals } from './types';
import type { Args, Globals } from './types';

export const TableWrapper = styled.table<{
compact?: boolean;
Expand Down Expand Up @@ -167,7 +167,7 @@ export const TableWrapper = styled.table<{
},
}));

const StyledIconButton = styled(IconButton as any)(({ theme }) => ({
const StyledIconButton = styled(IconButton as any)(() => ({
margin: '-4px -12px -4px 0',
}));

Expand All @@ -182,14 +182,22 @@ export enum ArgsTableError {
}

export type SortType = 'alpha' | 'requiredFirst' | 'none';
type SortFn = (a: ArgType, b: ArgType) => number;

const sortFns: Record<SortType, SortFn | null> = {
alpha: (a: ArgType, b: ArgType) => (a.name ?? '').localeCompare(b.name ?? ''),
requiredFirst: (a: ArgType, b: ArgType) =>
Number(!!b.type?.required) - Number(!!a.type?.required) ||
(a.name ?? '').localeCompare(b.name ?? ''),
none: null,
type SortFn = (a: InputType, b: InputType) => number;

const sortFns: Record<SortType, SortFn | null | undefined> = {
alpha: (a: InputType, b: InputType) => a.name?.localeCompare(b.name ?? '') ?? 0,
requiredFirst: (a: InputType, b: InputType) => {
const bType = b.type;
const aType = a.type;
const isBTypeRequired = !!(typeof bType !== 'string' && bType?.required);
const isATypeRequired = !!(typeof aType !== 'string' && aType?.required);

return (
(Number(isBTypeRequired) - Number(isATypeRequired) || a.name?.localeCompare(b.name ?? '')) ??
0
);
},
none: undefined,
};

export interface ArgsTableOptionProps {
Expand All @@ -203,7 +211,9 @@ export interface ArgsTableOptionProps {
sort?: SortType;
}
interface ArgsTableDataProps {
rows: ArgTypes;
rows: {
[key: string]: InputType;
};
args?: Args;
globals?: Globals;
}
Expand All @@ -219,7 +229,7 @@ export interface ArgsTableLoadingProps {
export type ArgsTableProps = ArgsTableOptionProps &
(ArgsTableDataProps | ArgsTableErrorProps | ArgsTableLoadingProps);

type Rows = ArgType[];
type Rows = InputType[];
type Subsection = Rows;
type Section = {
ungrouped: Rows;
Expand All @@ -231,7 +241,7 @@ type Sections = {
sections: Record<string, Section>;
};

const groupRows = (rows: ArgType, sort: SortType): Sections => {
const groupRows = (rows: { [key: string]: InputType }, sort: SortType): Sections => {
const sections: Sections = { ungrouped: [], ungroupedSubsections: {}, sections: {} };

if (!rows) {
Expand Down Expand Up @@ -301,7 +311,7 @@ const groupRows = (rows: ArgType, sort: SortType): Sections => {
* preview in `prepareStory`, and that exception will be bubbled up into the UI in a red screen.
* Nevertheless, we log the error here just in case.
*/
const safeIncludeConditionalArg = (row: ArgType, args: Args, globals: Globals) => {
const safeIncludeConditionalArg = (row: InputType, args: Args, globals: Globals) => {
try {
return includeConditionalArg(row, args, globals);
} catch (err: unknown) {
Expand Down Expand Up @@ -352,7 +362,7 @@ export const ArgsTable: FC<ArgsTableProps> = (props) => {
pickBy(
rows || {},
(row) => !row?.table?.disable && safeIncludeConditionalArg(row, args || {}, globals || {})
),
) as { [key: string]: InputType },
sort
);

Expand Down
45 changes: 0 additions & 45 deletions code/addons/docs/src/blocks/components/ArgsTable/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { Conditional } from 'storybook/internal/types';

// TODO ?
export interface JsDocParam {
name: string;
Expand All @@ -20,49 +18,6 @@ export interface JsDocTags {
returns?: JsDocReturns;
}

export interface PropSummaryValue {
summary: string;
detail?: string;
required?: boolean;
}

export type PropType = PropSummaryValue;
export type PropDefaultValue = PropSummaryValue;

export interface TableAnnotation {
type: PropType;
jsDocTags?: JsDocTags;
defaultValue?: PropDefaultValue;
category?: string;
}

export interface ArgType {
name?: string;
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;
[key: string]: any;
};
[key: string]: any;
}

export interface ArgTypes {
[key: string]: ArgType;
}

export interface Args {
[key: string]: any;
}
Expand Down
5 changes: 3 additions & 2 deletions code/addons/docs/src/blocks/controls/Files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Form } from 'storybook/internal/components';
import { styled } from 'storybook/theming';

import { getControlId } from './helpers';
import type { ControlProps } from './types';
import { type ControlProps, isControlObject } from './types';

export interface FilesControlProps extends ControlProps<string[]> {
/**
Expand Down Expand Up @@ -45,7 +45,8 @@ export const FilesControl: FC<FilesControlProps> = ({
argType,
}) => {
const inputElement = useRef<HTMLInputElement>(null);
const readonly = argType?.control?.readOnly;
const control = argType?.control;
const readonly = isControlObject(control) ? control.readOnly : false;

function handleFileChange(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const Object: Story = {
name: 'DEPRECATED: Object',
args: {
value: [objectOptions.B],
argType: { options: objectOptions },
argType: { options: objectOptions as any },
},
argTypes: { value: { control: { type: 'object' } } },
};
Expand All @@ -90,7 +90,7 @@ export const ObjectInline: Story = {
args: {
type: 'inline-check',
value: [objectOptions.A, objectOptions.C],
argType: { options: objectOptions },
argType: { options: objectOptions as any },
},
argTypes: { value: { control: { type: 'object' } } },
};
Expand All @@ -99,7 +99,7 @@ export const ObjectUndefined: Story = {
name: 'DEPRECATED: Object Undefined',
args: {
value: undefined,
argType: { options: objectOptions },
argType: { options: objectOptions as any },
},
argTypes: { value: { control: { type: 'object' } } },
};
Expand Down
Loading