From f9e5f3a8ca9970955d10115bd3ae5e4f86413bcd Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Fri, 9 Jan 2026 15:52:48 +0100 Subject: [PATCH] feat(ui): add useDefinition type definitions Signed-off-by: Johan Persson --- packages/ui/src/hooks/useDefinition/types.ts | 96 ++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/ui/src/hooks/useDefinition/types.ts diff --git a/packages/ui/src/hooks/useDefinition/types.ts b/packages/ui/src/hooks/useDefinition/types.ts new file mode 100644 index 0000000000..bd5e8353ce --- /dev/null +++ b/packages/ui/src/hooks/useDefinition/types.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ReactNode, CSSProperties } from 'react'; +import type { Responsive } from '../../types'; + +// Extract raw value from Responsive wrapper +type UnwrapResponsive = T extends Responsive ? U : T; + +export interface PropDefConfig { + dataAttribute?: boolean; + default?: UnwrapResponsive; +} + +export interface ComponentConfig< + P extends Record, + S extends Record, +> { + styles: S; + classNames: Record; + propDefs: { [K in keyof P]: PropDefConfig }; + utilityProps?: string[]; + surface?: 'container' | 'leaf'; +} + +export interface UseDefinitionOptions> { + utilityTarget?: keyof D['classNames'] | null; + classNameTarget?: keyof D['classNames'] | null; +} + +// Resolve prop type: unwrap Responsive, make non-nullable if default exists +type ResolvePropType< + T, + Config extends PropDefConfig, +> = Config['default'] extends {} // Check if default is present (not undefined) + ? Exclude, undefined> // Only remove undefined + : UnwrapResponsive; + +// Build ownProps shape from propDefs +type ResolvedOwnProps< + P, + PropDefs extends Record>, +> = { + [K in keyof P]: ResolvePropType; +}; + +// Conditional children type based on surface +type ChildrenProps = + Surface extends 'container' + ? { surfaceChildren: ReactNode } + : { children: ReactNode }; + +// Data attributes type +type DataAttributeKeys = { + [K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true } + ? K + : never; +}[keyof PropDefs]; + +type DataAttributes = { + [K in DataAttributeKeys as `data-${string & K}`]?: string; +} & { 'data-on-surface'?: string }; + +// Helper to define the base rest props +type BaseRestProps = Omit; + +export interface UseDefinitionResult< + D extends ComponentConfig, + P extends Record, +> { + ownProps: { + classes: Record; + } & ResolvedOwnProps & + ChildrenProps; + + restProps: keyof BaseRestProps extends never + ? Record + : BaseRestProps; + + dataAttributes: DataAttributes; + + utilityStyle: CSSProperties; +}