From 16f50861152f067eedab96558639d863f1ad457f Mon Sep 17 00:00:00 2001 From: "ryan.bendel" Date: Fri, 26 Jul 2024 17:35:46 +0100 Subject: [PATCH 01/11] feat: Correctly set the 'required' argType when an Angular @Input({required: true}) is represented in compodoc output --- .../angular/src/client/docs/compodoc.test.ts | 66 ++++++++++++++----- .../angular/src/client/docs/compodoc.ts | 13 ++-- .../angular/src/client/docs/types.ts | 1 + .../angular/template/cli/button.component.ts | 2 +- .../template/components/button.component.ts | 2 +- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/code/frameworks/angular/src/client/docs/compodoc.test.ts b/code/frameworks/angular/src/client/docs/compodoc.test.ts index ff91661c8d2..fb9d3b895e2 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.test.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { extractType, setCompodocJson } from './compodoc'; -import { CompodocJson, Decorator } from './types'; +import { CompodocJson, Decorator, Method, Property } from './types'; const makeProperty = (compodocType?: string) => ({ type: compodocType, @@ -11,6 +11,33 @@ const makeProperty = (compodocType?: string) => ({ const getDummyCompodocJson = () => { return { + components: [ + { + name: 'ButtonComponent', + type: 'component', + propertiesClass: [], + inputsClass: [ + { + required: true, + name: 'label', + defaultValue: "'Button'", + type: 'string', + decorators: [], + }, + { + name: 'primary', + defaultValue: 'false', + deprecated: false, + deprecationMessage: '', + line: 23, + type: 'boolean', + decorators: [], + }, + ], + outputsClass: [], + methodsClass: [], + }, + ], miscellaneous: { typealiases: [ { @@ -100,20 +127,29 @@ describe('extractType', () => { describe('with compodoc type', () => { setCompodocJson(getDummyCompodocJson()); it.each([ - ['string', { name: 'string' }], - ['boolean', { name: 'boolean' }], - ['number', { name: 'number' }], + ['string', { name: 'string', required: false }], + ['boolean', { name: 'boolean', required: false }], + ['number', { name: 'number', required: false }], // ['object', { name: 'object' }], // seems to be wrong | TODO: REVISIT // ['foo', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - [null, { name: 'other', value: 'void' }], - [undefined, { name: 'other', value: 'void' }], + [null, { name: 'other', value: 'void', required: false }], + [undefined, { name: 'other', value: 'void', required: false }], // ['T[]', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - ['[]', { name: 'other', value: 'empty-enum' }], - ['"primary" | "secondary"', { name: 'enum', value: ['primary', 'secondary'] }], - ['TypeAlias', { name: 'enum', value: ['Type Alias 1', 'Type Alias 2', 'Type Alias 3'] }], + ['[]', { name: 'other', value: 'empty-enum', required: false }], + [ + '"primary" | "secondary"', + { name: 'enum', value: ['primary', 'secondary'], required: false }, + ], + [ + 'TypeAlias', + { name: 'enum', value: ['Type Alias 1', 'Type Alias 2', 'Type Alias 3'], required: false }, + ], // ['EnumNumeric', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT // ['EnumNumericInitial', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - ['EnumStringValues', { name: 'enum', value: ['PRIMARY', 'SECONDARY', 'TERTIARY'] }], + [ + 'EnumStringValues', + { name: 'enum', value: ['PRIMARY', 'SECONDARY', 'TERTIARY'], required: false }, + ], ])('%s', (compodocType, expected) => { expect(extractType(makeProperty(compodocType), null)).toEqual(expected); }); @@ -121,13 +157,13 @@ describe('extractType', () => { describe('without compodoc type', () => { it.each([ - ['string', { name: 'string' }], - ['', { name: 'string' }], - [false, { name: 'boolean' }], - [10, { name: 'number' }], + ['string', { name: 'string', required: false }], + ['', { name: 'string', required: false }], + [false, { name: 'boolean', required: false }], + [10, { name: 'number', required: false }], // [['abc'], { name: 'object' }], // seems to be wrong | TODO: REVISIT // [{ foo: 1 }, { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - [undefined, { name: 'other', value: 'void' }], + [undefined, { name: 'other', value: 'void', required: false }], ])('%s', (defaultValue, expected) => { expect(extractType(makeProperty(null), defaultValue)).toEqual(expected); }); diff --git a/code/frameworks/angular/src/client/docs/compodoc.ts b/code/frameworks/angular/src/client/docs/compodoc.ts index e71d1f463ad..d4b13b83973 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.ts @@ -20,6 +20,9 @@ export const isMethod = (methodOrProp: Method | Property): methodOrProp is Metho return (methodOrProp as Method).args !== undefined; }; +export const isRequired = (methodOrProp: Method | Property) => + methodOrProp.hasOwnProperty('required'); + export const setCompodocJson = (compodocJson: CompodocJson) => { global.__STORYBOOK_COMPODOC_JSON__ = compodocJson; }; @@ -138,19 +141,19 @@ const extractEnumValues = (compodocType: any) => { export const extractType = (property: Property, defaultValue: any): SBType => { const compodocType = property.type || extractTypeFromValue(defaultValue); switch (compodocType) { - case 'string': case 'boolean': + case 'string': case 'number': - return { name: compodocType }; + return { name: compodocType, required: isRequired(property) }; case undefined: case null: - return { name: 'other', value: 'void' }; + return { name: 'other', value: 'void', required: isRequired(property) }; default: { const resolvedType = resolveTypealias(compodocType); const enumValues = extractEnumValues(resolvedType); return enumValues - ? { name: 'enum', value: enumValues } - : { name: 'other', value: 'empty-enum' }; + ? { name: 'enum', value: enumValues, required: isRequired(property) } + : { name: 'other', value: 'empty-enum', required: isRequired(property) }; } } }; diff --git a/code/frameworks/angular/src/client/docs/types.ts b/code/frameworks/angular/src/client/docs/types.ts index d1ac2f2d52d..d7057369279 100644 --- a/code/frameworks/angular/src/client/docs/types.ts +++ b/code/frameworks/angular/src/client/docs/types.ts @@ -20,6 +20,7 @@ export interface Property { type: string; optional: boolean; defaultValue?: string; + required?: boolean; description?: string; rawdescription?: string; jsdoctags?: JsDocTag[]; diff --git a/code/frameworks/angular/template/cli/button.component.ts b/code/frameworks/angular/template/cli/button.component.ts index badc6bad7f1..2a64a13bd97 100644 --- a/code/frameworks/angular/template/cli/button.component.ts +++ b/code/frameworks/angular/template/cli/button.component.ts @@ -39,7 +39,7 @@ export class ButtonComponent { * * @required */ - @Input() + @Input({ required: true }) label = 'Button'; /** diff --git a/code/frameworks/angular/template/components/button.component.ts b/code/frameworks/angular/template/components/button.component.ts index cc28c096578..0b48c78cae7 100644 --- a/code/frameworks/angular/template/components/button.component.ts +++ b/code/frameworks/angular/template/components/button.component.ts @@ -37,7 +37,7 @@ export default class FrameworkButtonComponent { * * @required */ - @Input() + @Input({ required: true }) label = 'Button'; /** From cef08f4a22e89f0618bd8e51ad8801a766565ec7 Mon Sep 17 00:00:00 2001 From: "ryan.bendel" Date: Fri, 26 Jul 2024 17:53:00 +0100 Subject: [PATCH 02/11] tt: remove unused imports --- code/frameworks/angular/src/client/docs/compodoc.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/frameworks/angular/src/client/docs/compodoc.test.ts b/code/frameworks/angular/src/client/docs/compodoc.test.ts index fb9d3b895e2..b0fad1811b3 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.test.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { extractType, setCompodocJson } from './compodoc'; -import { CompodocJson, Decorator, Method, Property } from './types'; +import { CompodocJson, Decorator } from './types'; const makeProperty = (compodocType?: string) => ({ type: compodocType, From 383c5ae0e2265ea014768300194bdd380975b13b Mon Sep 17 00:00:00 2001 From: "ryan.bendel" Date: Fri, 26 Jul 2024 21:02:27 +0100 Subject: [PATCH 03/11] tt: Refactor to only set property if exists and is true --- .../angular/src/client/docs/compodoc.test.ts | 37 +++++++------------ .../angular/src/client/docs/compodoc.ts | 12 +++--- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/code/frameworks/angular/src/client/docs/compodoc.test.ts b/code/frameworks/angular/src/client/docs/compodoc.test.ts index b0fad1811b3..e0c91c2f353 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.test.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.test.ts @@ -127,29 +127,20 @@ describe('extractType', () => { describe('with compodoc type', () => { setCompodocJson(getDummyCompodocJson()); it.each([ - ['string', { name: 'string', required: false }], - ['boolean', { name: 'boolean', required: false }], - ['number', { name: 'number', required: false }], + ['string', { name: 'string' }], + ['boolean', { name: 'boolean' }], + ['number', { name: 'number' }], // ['object', { name: 'object' }], // seems to be wrong | TODO: REVISIT // ['foo', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - [null, { name: 'other', value: 'void', required: false }], - [undefined, { name: 'other', value: 'void', required: false }], + [null, { name: 'other', value: 'void' }], + [undefined, { name: 'other', value: 'void' }], // ['T[]', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - ['[]', { name: 'other', value: 'empty-enum', required: false }], - [ - '"primary" | "secondary"', - { name: 'enum', value: ['primary', 'secondary'], required: false }, - ], - [ - 'TypeAlias', - { name: 'enum', value: ['Type Alias 1', 'Type Alias 2', 'Type Alias 3'], required: false }, - ], + ['[]', { name: 'other', value: 'empty-enum' }], + ['"primary" | "secondary"', { name: 'enum', value: ['primary', 'secondary'] }], + ['TypeAlias', { name: 'enum', value: ['Type Alias 1', 'Type Alias 2', 'Type Alias 3'] }], // ['EnumNumeric', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT // ['EnumNumericInitial', { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - [ - 'EnumStringValues', - { name: 'enum', value: ['PRIMARY', 'SECONDARY', 'TERTIARY'], required: false }, - ], + ['EnumStringValues', { name: 'enum', value: ['PRIMARY', 'SECONDARY', 'TERTIARY'] }], ])('%s', (compodocType, expected) => { expect(extractType(makeProperty(compodocType), null)).toEqual(expected); }); @@ -157,13 +148,13 @@ describe('extractType', () => { describe('without compodoc type', () => { it.each([ - ['string', { name: 'string', required: false }], - ['', { name: 'string', required: false }], - [false, { name: 'boolean', required: false }], - [10, { name: 'number', required: false }], + ['string', { name: 'string' }], + ['', { name: 'string' }], + [false, { name: 'boolean' }], + [10, { name: 'number' }], // [['abc'], { name: 'object' }], // seems to be wrong | TODO: REVISIT // [{ foo: 1 }, { name: 'other', value: 'empty-enum' }], // seems to be wrong | TODO: REVISIT - [undefined, { name: 'other', value: 'void', required: false }], + [undefined, { name: 'other', value: 'void' }], ])('%s', (defaultValue, expected) => { expect(extractType(makeProperty(null), defaultValue)).toEqual(expected); }); diff --git a/code/frameworks/angular/src/client/docs/compodoc.ts b/code/frameworks/angular/src/client/docs/compodoc.ts index d4b13b83973..06e8652ac57 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.ts @@ -20,8 +20,8 @@ export const isMethod = (methodOrProp: Method | Property): methodOrProp is Metho return (methodOrProp as Method).args !== undefined; }; -export const isRequired = (methodOrProp: Method | Property) => - methodOrProp.hasOwnProperty('required'); +export const isRequired = (prop: Property): object => + prop.hasOwnProperty('required') && prop.required ? { required: true } : {}; export const setCompodocJson = (compodocJson: CompodocJson) => { global.__STORYBOOK_COMPODOC_JSON__ = compodocJson; @@ -144,16 +144,16 @@ export const extractType = (property: Property, defaultValue: any): SBType => { case 'boolean': case 'string': case 'number': - return { name: compodocType, required: isRequired(property) }; + return { name: compodocType, ...isRequired(property) }; case undefined: case null: - return { name: 'other', value: 'void', required: isRequired(property) }; + return { name: 'other', value: 'void', ...isRequired(property) }; default: { const resolvedType = resolveTypealias(compodocType); const enumValues = extractEnumValues(resolvedType); return enumValues - ? { name: 'enum', value: enumValues, required: isRequired(property) } - : { name: 'other', value: 'empty-enum', required: isRequired(property) }; + ? { name: 'enum', value: enumValues } + : { name: 'other', value: 'empty-enum' }; } } }; From 53274835ef6cd02b22f9c5e33f5fe2296d53826a Mon Sep 17 00:00:00 2001 From: "ryan.bendel" Date: Fri, 9 Aug 2024 15:15:04 +0100 Subject: [PATCH 04/11] tt: Refactor naming of required utility functions --- .../angular/src/client/docs/compodoc.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/code/frameworks/angular/src/client/docs/compodoc.ts b/code/frameworks/angular/src/client/docs/compodoc.ts index 06e8652ac57..5a40254c15d 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.ts @@ -20,8 +20,13 @@ export const isMethod = (methodOrProp: Method | Property): methodOrProp is Metho return (methodOrProp as Method).args !== undefined; }; -export const isRequired = (prop: Property): object => - prop.hasOwnProperty('required') && prop.required ? { required: true } : {}; +export const isRequired = (item: Property): boolean => { + return item.hasOwnProperty('required') && item.required; +}; + +export const setRequiredProperty = (property: Property): object => { + return isRequired(property) ? { required: true } : {}; +}; export const setCompodocJson = (compodocJson: CompodocJson) => { global.__STORYBOOK_COMPODOC_JSON__ = compodocJson; @@ -144,10 +149,10 @@ export const extractType = (property: Property, defaultValue: any): SBType => { case 'boolean': case 'string': case 'number': - return { name: compodocType, ...isRequired(property) }; + return { name: compodocType, ...setRequiredProperty(property) }; case undefined: case null: - return { name: 'other', value: 'void', ...isRequired(property) }; + return { name: 'other', value: 'void', ...setRequiredProperty(property) }; default: { const resolvedType = resolveTypealias(compodocType); const enumValues = extractEnumValues(resolvedType); @@ -248,7 +253,6 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec ? { name: 'other', value: 'void' } : extractType(item as Property, defaultValue); const action = section === 'outputs' ? { action: item.name } : {}; - const argType = { name: item.name, description: item.rawdescription || item.description, @@ -258,7 +262,7 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec category: section, type: { summary: isMethod(item) ? displaySignature(item) : item.type, - required: isMethod(item) ? false : !item.optional, + required: isMethod(item) ? false : item.required, }, defaultValue: { summary: defaultValue }, }, From 9f440259ec06dc5c1faf336bb67b431ca10a041e Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 10 Sep 2024 14:06:35 +0200 Subject: [PATCH 05/11] chore: Update control types --- .../angular/src/client/docs/compodoc.ts | 5 +-- .../src/components/ArgsTable/ArgControl.tsx | 24 +++++++--- .../src/components/ArgsTable/ArgRow.tsx | 18 ++++---- .../src/components/ArgsTable/ArgValue.tsx | 7 ++- .../src/components/ArgsTable/ArgsTable.tsx | 22 ++++----- .../blocks/src/components/ArgsTable/types.ts | 45 ------------------- code/lib/blocks/src/controls/Files.tsx | 5 ++- code/lib/blocks/src/controls/types.ts | 7 ++- 8 files changed, 52 insertions(+), 81 deletions(-) diff --git a/code/frameworks/angular/src/client/docs/compodoc.ts b/code/frameworks/angular/src/client/docs/compodoc.ts index da1184bc3d2..fa9f72f49fe 100644 --- a/code/frameworks/angular/src/client/docs/compodoc.ts +++ b/code/frameworks/angular/src/client/docs/compodoc.ts @@ -25,7 +25,7 @@ export const isRequired = (item: Property): boolean => { return item.hasOwnProperty('required') && item.required; }; -export const setRequiredProperty = (property: Property): object => { +export const setRequiredProperty = (property: Property) => { return isRequired(property) ? { required: true } : {}; }; @@ -254,7 +254,7 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec ? { name: 'other', value: 'void' } : extractType(item as Property, defaultValue); const action = section === 'outputs' ? { action: item.name } : {}; - const argType = { + const argType: InputType = { name: item.name, description: item.rawdescription || item.description, type, @@ -263,7 +263,6 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec category: section, type: { summary: isMethod(item) ? displaySignature(item) : item.type, - required: isMethod(item) ? false : item.required, }, defaultValue: { summary: defaultValue }, }, diff --git a/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx b/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx index 36e75aa81d3..463a8d8d03a 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx @@ -3,6 +3,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Link } from 'storybook/internal/components'; +import type { StrictInputType } from '@storybook/csf'; + import { BooleanControl, ColorControl, @@ -13,17 +15,18 @@ import { 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; } -const Controls: Record = { +const Controls: Record>> = { array: ObjectControl, object: ObjectControl, boolean: BooleanControl, @@ -68,8 +71,10 @@ export const ArgControl: FC = ({ 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 ? ( = ({ 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 ; + const Control = typeof control !== 'string' ? Controls[control.type] ?? NoControl : NoControl; + + if (typeof control === 'string') { + return ; + } else { + return ; + } }; diff --git a/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx b/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx index 84ed13a524f..9ffb9cd572d 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx @@ -5,6 +5,8 @@ import { codeCommon } from 'storybook/internal/components'; import type { CSSObject } from 'storybook/internal/theming'; import { styled } from 'storybook/internal/theming'; +import type { InputType, SBType, StrictInputType } from '@storybook/csf'; + import Markdown from 'markdown-to-jsx'; import { transparentize } from 'polished'; @@ -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: StrictInputType; arg: any; updateArgs?: (args: Args) => void; compact?: boolean; @@ -78,9 +80,9 @@ const StyledTd = styled.td<{ expandable: boolean }>(({ theme, expandable }) => ( paddingLeft: expandable ? '40px !important' : '20px !important', })); -const toSummary = (value: any) => { +const toSummary = (value: SBType): { summary: string } => { if (!value) { - return value; + return value as any; } const val = typeof value === 'string' ? value : value.name; return { summary: val }; @@ -90,8 +92,8 @@ export const ArgRow: FC = (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 hasDescription = description != null && description !== ''; @@ -112,13 +114,13 @@ export const ArgRow: FC = (props) => { {table.jsDocTags != null ? ( <> - + ) : ( - + )} diff --git a/code/lib/blocks/src/components/ArgsTable/ArgValue.tsx b/code/lib/blocks/src/components/ArgsTable/ArgValue.tsx index 0df1accdada..de5d1da532e 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgValue.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgValue.tsx @@ -4,15 +4,14 @@ import React, { useState } from 'react'; import { SyntaxHighlighter, WithTooltipPure, codeCommon } from 'storybook/internal/components'; import { styled } from 'storybook/internal/theming'; +import type { InputType, SBType } from '@storybook/csf'; import { ChevronSmallDownIcon, ChevronSmallUpIcon } from '@storybook/icons'; import uniq from 'lodash/uniq.js'; import memoize from 'memoizerific'; -import type { PropSummaryValue } from './types'; - interface ArgValueProps { - value?: PropSummaryValue; + value?: InputType['table']['type']; initialExpandedArgs?: boolean; } @@ -22,7 +21,7 @@ interface ArgTextProps { } interface ArgSummaryProps { - value: PropSummaryValue; + value: InputType['table']['type']; initialExpandedArgs?: boolean; } diff --git a/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx b/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx index e0dab71a171..7ba8f353f12 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx @@ -5,7 +5,7 @@ import { once } from 'storybook/internal/client-logger'; import { IconButton, Link, ResetWrapper } from 'storybook/internal/components'; import { styled } from 'storybook/internal/theming'; -import { includeConditionalArg } from '@storybook/csf'; +import { type StrictInputType, includeConditionalArg } from '@storybook/csf'; import { DocumentIcon, UndoIcon } from '@storybook/icons'; import pickBy from 'lodash/pickBy.js'; @@ -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; @@ -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', })); @@ -182,11 +182,11 @@ export enum ArgsTableError { } export type SortType = 'alpha' | 'requiredFirst' | 'none'; -type SortFn = (a: ArgType, b: ArgType) => number; +type SortFn = (a: StrictInputType, b: StrictInputType) => number; const sortFns: Record = { - alpha: (a: ArgType, b: ArgType) => a.name.localeCompare(b.name), - requiredFirst: (a: ArgType, b: ArgType) => + alpha: (a: StrictInputType, b: StrictInputType) => a.name.localeCompare(b.name), + requiredFirst: (a: StrictInputType, b: StrictInputType) => Number(!!b.type?.required) - Number(!!a.type?.required) || a.name.localeCompare(b.name), none: undefined, }; @@ -202,7 +202,9 @@ export interface ArgsTableOptionProps { sort?: SortType; } interface ArgsTableDataProps { - rows: ArgTypes; + rows: { + [key: string]: StrictInputType; + }; args?: Args; globals?: Globals; } @@ -218,7 +220,7 @@ export interface ArgsTableLoadingProps { export type ArgsTableProps = ArgsTableOptionProps & (ArgsTableDataProps | ArgsTableErrorProps | ArgsTableLoadingProps); -type Rows = ArgType[]; +type Rows = StrictInputType[]; type Subsection = Rows; type Section = { ungrouped: Rows; @@ -230,7 +232,7 @@ type Sections = { sections: Record; }; -const groupRows = (rows: ArgType, sort: SortType) => { +const groupRows = (rows: { [key: string]: StrictInputType }, sort: SortType) => { const sections: Sections = { ungrouped: [], ungroupedSubsections: {}, sections: {} }; if (!rows) { @@ -298,7 +300,7 @@ const groupRows = (rows: ArgType, sort: SortType) => { * 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: StrictInputType, args: Args, globals: Globals) => { try { return includeConditionalArg(row, args, globals); } catch (err) { diff --git a/code/lib/blocks/src/components/ArgsTable/types.ts b/code/lib/blocks/src/components/ArgsTable/types.ts index 9a335f3bfea..be7ffb6db51 100644 --- a/code/lib/blocks/src/components/ArgsTable/types.ts +++ b/code/lib/blocks/src/components/ArgsTable/types.ts @@ -1,5 +1,3 @@ -import type { Conditional } from 'storybook/internal/types'; - // TODO ? export interface JsDocParam { name: string; @@ -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; } diff --git a/code/lib/blocks/src/controls/Files.tsx b/code/lib/blocks/src/controls/Files.tsx index 8bb0653e618..0ab261cbd2e 100644 --- a/code/lib/blocks/src/controls/Files.tsx +++ b/code/lib/blocks/src/controls/Files.tsx @@ -5,7 +5,7 @@ import { Form } from 'storybook/internal/components'; import { styled } from 'storybook/internal/theming'; import { getControlId } from './helpers'; -import type { ControlProps } from './types'; +import { type ControlProps, isControlObject } from './types'; export interface FilesControlProps extends ControlProps { /** @@ -44,7 +44,8 @@ export const FilesControl: FC = ({ argType, }) => { const inputElement = useRef(null); - const readonly = argType?.control?.readOnly; + const control = argType?.control; + const readonly = isControlObject(control) ? control.readOnly : false; function handleFileChange(e: ChangeEvent) { if (!e.target.files) { diff --git a/code/lib/blocks/src/controls/types.ts b/code/lib/blocks/src/controls/types.ts index ed6fd71621b..007ad787f1b 100644 --- a/code/lib/blocks/src/controls/types.ts +++ b/code/lib/blocks/src/controls/types.ts @@ -1,10 +1,10 @@ -import type { ArgType } from '../components/ArgsTable'; +import type { StrictInputType } from '@storybook/csf'; export interface ControlProps { name: string; value?: T; defaultValue?: T; - argType?: ArgType; + argType?: StrictInputType; onChange: (value?: T) => T | void; onFocus?: (evt: any) => void; onBlur?: (evt: any) => void; @@ -88,3 +88,6 @@ export type Control = | OptionsConfig | RangeConfig | TextConfig; + +export const isControlObject = (control: Control): control is Control & object => + typeof control === 'object'; From 210316a2078e7d65d86f58da10449e53361e6067 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 10 Sep 2024 14:33:14 +0200 Subject: [PATCH 06/11] Update control types validation in isControlObject function --- code/lib/blocks/src/controls/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/blocks/src/controls/types.ts b/code/lib/blocks/src/controls/types.ts index 007ad787f1b..9109d7b4d4f 100644 --- a/code/lib/blocks/src/controls/types.ts +++ b/code/lib/blocks/src/controls/types.ts @@ -90,4 +90,4 @@ export type Control = | TextConfig; export const isControlObject = (control: Control): control is Control & object => - typeof control === 'object'; + typeof control === 'object' && control !== null && control !== undefined; From 6b91f74c9ade4e4dfb0231403c29009ced8b17ee Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 10 Sep 2024 14:54:10 +0200 Subject: [PATCH 07/11] refactor: Update label property type in button component --- code/frameworks/angular/template/cli/button.component.ts | 2 +- code/frameworks/angular/template/components/button.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/frameworks/angular/template/cli/button.component.ts b/code/frameworks/angular/template/cli/button.component.ts index fb83a834ac1..bd044a107a0 100644 --- a/code/frameworks/angular/template/cli/button.component.ts +++ b/code/frameworks/angular/template/cli/button.component.ts @@ -34,7 +34,7 @@ export class ButtonComponent { * @required */ @Input({ required: true }) - label = 'Button'; + label: string; /** Optional click handler */ @Output() diff --git a/code/frameworks/angular/template/components/button.component.ts b/code/frameworks/angular/template/components/button.component.ts index 3c6a1535857..8efe3499866 100644 --- a/code/frameworks/angular/template/components/button.component.ts +++ b/code/frameworks/angular/template/components/button.component.ts @@ -32,7 +32,7 @@ export default class FrameworkButtonComponent { * @required */ @Input({ required: true }) - label = 'Button'; + label: string; /** Optional click handler */ @Output() From 003317e1a7b8056c9d83005db0e1fe7b1a2a2c5a Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 10 Sep 2024 15:30:58 +0200 Subject: [PATCH 08/11] Fix linting --- .../components/ArgsTable/ArgRow.stories.tsx | 8 +++++-- .../src/components/ArgsTable/ArgRow.tsx | 8 +++---- .../src/components/ArgsTable/ArgsTable.tsx | 24 ++++++++++++------- .../controls/options/CheckOptions.stories.tsx | 6 ++--- .../controls/options/RadioOptions.stories.tsx | 6 ++--- .../options/SelectOptions.stories.tsx | 11 ++++----- code/lib/blocks/src/controls/types.ts | 4 ++-- 7 files changed, 37 insertions(+), 30 deletions(-) diff --git a/code/lib/blocks/src/components/ArgsTable/ArgRow.stories.tsx b/code/lib/blocks/src/components/ArgsTable/ArgRow.stories.tsx index 8f8e5d1ad51..56067014ad9 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgRow.stories.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgRow.stories.tsx @@ -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'; @@ -31,6 +33,7 @@ export const String = { description: 'someString description', type: { required: true, + name: 'string', }, control: { type: 'text', @@ -43,7 +46,7 @@ export const String = { summary: 'reallylongstringnospaces', }, }, - }, + } satisfies InputType, }, }; @@ -127,6 +130,7 @@ export const Number = { description: 'someNumber description', type: { required: false, + name: 'number', }, table: { type: { @@ -139,7 +143,7 @@ export const Number = { control: { type: 'number', }, - }, + } satisfies InputType, }, }; diff --git a/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx b/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx index 9ffb9cd572d..d1c149e134b 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgRow.tsx @@ -5,7 +5,7 @@ import { codeCommon } from 'storybook/internal/components'; import type { CSSObject } from 'storybook/internal/theming'; import { styled } from 'storybook/internal/theming'; -import type { InputType, SBType, StrictInputType } from '@storybook/csf'; +import type { InputType, SBType } from '@storybook/csf'; import Markdown from 'markdown-to-jsx'; import { transparentize } from 'polished'; @@ -17,7 +17,7 @@ import { ArgValue } from './ArgValue'; import type { Args } from './types'; interface ArgRowProps { - row: StrictInputType; + row: InputType; arg: any; updateArgs?: (args: Args) => void; compact?: boolean; @@ -80,7 +80,7 @@ const StyledTd = styled.td<{ expandable: boolean }>(({ theme, expandable }) => ( paddingLeft: expandable ? '40px !important' : '20px !important', })); -const toSummary = (value: SBType): { summary: string } => { +const toSummary = (value: InputType['type']): { summary: string } => { if (!value) { return value as any; } @@ -95,7 +95,7 @@ export const ArgRow: FC = (props) => { 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 ( diff --git a/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx b/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx index 7ba8f353f12..1097b7e6bae 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgsTable.tsx @@ -5,7 +5,7 @@ import { once } from 'storybook/internal/client-logger'; import { IconButton, Link, ResetWrapper } from 'storybook/internal/components'; import { styled } from 'storybook/internal/theming'; -import { type StrictInputType, includeConditionalArg } from '@storybook/csf'; +import { type InputType, includeConditionalArg } from '@storybook/csf'; import { DocumentIcon, UndoIcon } from '@storybook/icons'; import pickBy from 'lodash/pickBy.js'; @@ -182,12 +182,18 @@ export enum ArgsTableError { } export type SortType = 'alpha' | 'requiredFirst' | 'none'; -type SortFn = (a: StrictInputType, b: StrictInputType) => number; +type SortFn = (a: InputType, b: InputType) => number; const sortFns: Record = { - alpha: (a: StrictInputType, b: StrictInputType) => a.name.localeCompare(b.name), - requiredFirst: (a: StrictInputType, b: StrictInputType) => - Number(!!b.type?.required) - Number(!!a.type?.required) || a.name.localeCompare(b.name), + alpha: (a: InputType, b: InputType) => a.name.localeCompare(b.name), + 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); + }, none: undefined, }; @@ -203,7 +209,7 @@ export interface ArgsTableOptionProps { } interface ArgsTableDataProps { rows: { - [key: string]: StrictInputType; + [key: string]: InputType; }; args?: Args; globals?: Globals; @@ -220,7 +226,7 @@ export interface ArgsTableLoadingProps { export type ArgsTableProps = ArgsTableOptionProps & (ArgsTableDataProps | ArgsTableErrorProps | ArgsTableLoadingProps); -type Rows = StrictInputType[]; +type Rows = InputType[]; type Subsection = Rows; type Section = { ungrouped: Rows; @@ -232,7 +238,7 @@ type Sections = { sections: Record; }; -const groupRows = (rows: { [key: string]: StrictInputType }, sort: SortType) => { +const groupRows = (rows: { [key: string]: InputType }, sort: SortType) => { const sections: Sections = { ungrouped: [], ungroupedSubsections: {}, sections: {} }; if (!rows) { @@ -300,7 +306,7 @@ const groupRows = (rows: { [key: string]: StrictInputType }, sort: SortType) => * 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: StrictInputType, args: Args, globals: Globals) => { +const safeIncludeConditionalArg = (row: InputType, args: Args, globals: Globals) => { try { return includeConditionalArg(row, args, globals); } catch (err) { diff --git a/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx b/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx index 8e98d88c080..8bccc60b825 100644 --- a/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx +++ b/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx @@ -79,7 +79,7 @@ export const Object: Story = { name: 'DEPRECATED: Object', args: { value: [objectOptions.B], - argType: { options: objectOptions }, + argType: { options: [objectOptions] }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -89,7 +89,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' } } }, }; @@ -98,7 +98,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' } } }, }; diff --git a/code/lib/blocks/src/controls/options/RadioOptions.stories.tsx b/code/lib/blocks/src/controls/options/RadioOptions.stories.tsx index e96f52b781c..a47f0de0b06 100644 --- a/code/lib/blocks/src/controls/options/RadioOptions.stories.tsx +++ b/code/lib/blocks/src/controls/options/RadioOptions.stories.tsx @@ -79,7 +79,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' } } }, }; @@ -89,7 +89,7 @@ export const ObjectInline: Story = { args: { type: 'inline-radio', value: objectOptions.A, - argType: { options: objectOptions }, + argType: { options: objectOptions as any }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -98,7 +98,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' } } }, }; diff --git a/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx b/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx index 81efc451c7f..234eef1bf02 100644 --- a/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx +++ b/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx @@ -34,7 +34,9 @@ const meta = { args: { name: 'select', type: 'select', - argType: { options: arrayOptions }, + argType: { + options: arrayOptions, + }, onChange: fn(), }, argTypes: { @@ -97,7 +99,7 @@ export const Object: Story = { name: 'DEPRECATED: Object', args: { value: objectOptions.B, - argType: { options: objectOptions }, + argType: { options: [objectOptions] }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -107,7 +109,6 @@ export const ObjectMulti: Story = { args: { type: 'multi-select', value: [objectOptions.A, objectOptions.B], - argType: { options: objectOptions }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -116,7 +117,6 @@ export const ObjectUndefined: Story = { name: 'DEPRECATED: Object Undefined', args: { value: undefined, - argType: { options: objectOptions }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -126,7 +126,6 @@ export const ObjectMultiUndefined: Story = { args: { type: 'multi-select', value: undefined, - argType: { options: objectOptions }, }, argTypes: { value: { control: { type: 'object' } } }, }; @@ -135,7 +134,6 @@ export const ArrayReadonly: Story = { args: { value: arrayOptions[0], argType: { - options: arrayOptions, table: { readonly: true, }, @@ -154,7 +152,6 @@ export const ArrayMultiReadonly: Story = { type: 'multi-select', value: [arrayOptions[1], arrayOptions[2]], argType: { - options: arrayOptions, table: { readonly: true, }, diff --git a/code/lib/blocks/src/controls/types.ts b/code/lib/blocks/src/controls/types.ts index 9109d7b4d4f..ed8f28f0213 100644 --- a/code/lib/blocks/src/controls/types.ts +++ b/code/lib/blocks/src/controls/types.ts @@ -1,10 +1,10 @@ -import type { StrictInputType } from '@storybook/csf'; +import type { InputType } from '@storybook/csf'; export interface ControlProps { name: string; value?: T; defaultValue?: T; - argType?: StrictInputType; + argType?: InputType; onChange: (value?: T) => T | void; onFocus?: (evt: any) => void; onBlur?: (evt: any) => void; From fc770e9569b7aeeba43ce87b3fd400c307202b09 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 1 Nov 2024 15:41:23 +0100 Subject: [PATCH 09/11] Fix linting --- code/lib/blocks/src/components/ArgsTable/ArgControl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx b/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx index ff0f341fb7f..42785ed2308 100644 --- a/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx +++ b/code/lib/blocks/src/components/ArgsTable/ArgControl.tsx @@ -86,7 +86,7 @@ export const ArgControl: FC = ({ 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 = typeof control !== 'string' ? Controls[control.type] ?? NoControl : NoControl; + const Control = typeof control !== 'string' ? (Controls[control.type] ?? NoControl) : NoControl; if (typeof control === 'string') { return ; From f1b76128b7369f09c1bcbd68ad5e6acf35a4a5e8 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 1 Nov 2024 15:57:29 +0100 Subject: [PATCH 10/11] Update code/lib/blocks/src/controls/options/CheckOptions.stories.tsx --- code/lib/blocks/src/controls/options/CheckOptions.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx b/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx index 8bccc60b825..3176b784c8e 100644 --- a/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx +++ b/code/lib/blocks/src/controls/options/CheckOptions.stories.tsx @@ -79,7 +79,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' } } }, }; From 20a4456aae4167bf6168c1ef8bfa0e5d9aac2227 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 1 Nov 2024 16:02:34 +0100 Subject: [PATCH 11/11] Fix types --- code/lib/blocks/src/controls/options/SelectOptions.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx b/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx index 234eef1bf02..fda240470f7 100644 --- a/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx +++ b/code/lib/blocks/src/controls/options/SelectOptions.stories.tsx @@ -99,7 +99,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' } } }, };