From f7cb5383e926b1d6f59fce9afafc8bb926339a55 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 25 Mar 2025 20:57:09 +0100 Subject: [PATCH] canon: prop extraction fixes Signed-off-by: Patrik Oldsberg --- .changeset/famous-eggs-dance.md | 5 +++ packages/canon/src/utils/extractProps.ts | 54 ++++++++++++++++-------- 2 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 .changeset/famous-eggs-dance.md diff --git a/.changeset/famous-eggs-dance.md b/.changeset/famous-eggs-dance.md new file mode 100644 index 0000000000..8293746402 --- /dev/null +++ b/.changeset/famous-eggs-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Internal refactor and fixes to the prop extraction logic for layout components. diff --git a/packages/canon/src/utils/extractProps.ts b/packages/canon/src/utils/extractProps.ts index 8631f4e228..1668117909 100644 --- a/packages/canon/src/utils/extractProps.ts +++ b/packages/canon/src/utils/extractProps.ts @@ -14,6 +14,16 @@ * limitations under the License. */ +type BasePropDef = { + type: string; + values?: readonly unknown[]; + default?: unknown; + required?: boolean; + className?: string; + responsive?: true; + customProperties?: string[]; +}; + export function extractProps( props: { className?: string; @@ -22,45 +32,53 @@ export function extractProps( as?: keyof JSX.IntrinsicElements; [key: string]: any; }, - propDefs: { [key: string]: any }, + propDefs: { [name in string]: BasePropDef }, ) { let className: string[] = (props.className || '').split(' '); let style: React.CSSProperties = { ...props.style }; - const hasProp = (key: string) => props.hasOwnProperty(key); for (const key in propDefs) { + const propDef = propDefs[key]; + // Check if the prop is present or has a default value - if (!hasProp(key) && !propDefs[key].hasOwnProperty('default')) { + if (!Object.hasOwn(props, key) && !propDef.hasOwnProperty('default')) { continue; // Skip processing if neither is present } - const value = hasProp(key) ? props[key] : propDefs[key].default; - const propDefsValues = propDefs[key].values; - const propDefsCustomProperties = propDefs[key].customProperties; - const propDefsClassName = propDefs[key].className; - const isResponsive = propDefs[key].responsive; + const value = Object.hasOwn(props, key) + ? (props[key] as unknown) + : propDefs[key].default; + const propDefsValues = propDef.values; + const propDefsCustomProperties = propDef.customProperties; + const propDefsClassName = propDef.className; + const isResponsive = propDef.responsive; - const handleValue = (val: string, prefix: string = '') => { + const handleValue = (val: unknown, prefix: string = '') => { // Skip adding class name if the key is "as" if (key === 'as') return; - if (propDefsValues.includes(val)) { + if (propDefsValues?.includes(val)) { className.push(`${prefix}${propDefsClassName}-${val}`); } else { - const customPropertyKey = - isResponsive && prefix - ? `${propDefsCustomProperties}-${prefix.slice(0, -1)}` - : propDefsCustomProperties; - (style as any)[customPropertyKey] = val; + if (propDefsCustomProperties) { + for (const customProperty of propDefsCustomProperties) { + const customPropertyKey = + isResponsive && prefix + ? `${customProperty}-${prefix.slice(0, -1)}` + : customProperty; + style[customPropertyKey as keyof typeof style] = val as any; + } + } className.push(`${prefix}${propDefsClassName}`); } }; - if (isResponsive && typeof value === 'object') { + if (isResponsive && typeof value === 'object' && value !== null) { + const breakpointValues = value as { [key: string]: unknown }; // Handle responsive object values - for (const breakpoint in value) { + for (const breakpoint in breakpointValues) { const prefix = breakpoint === 'initial' ? '' : `${breakpoint}:`; - handleValue(value[breakpoint], prefix); + handleValue(breakpointValues[breakpoint], prefix); } } else { handleValue(value);