feat(ui): add useDefinition type definitions

Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
Johan Persson
2026-01-09 15:52:48 +01:00
parent ec66afb03e
commit f9e5f3a8ca
@@ -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> = T extends Responsive<infer U> ? U : T;
export interface PropDefConfig<T> {
dataAttribute?: boolean;
default?: UnwrapResponsive<T>;
}
export interface ComponentConfig<
P extends Record<string, any>,
S extends Record<string, string>,
> {
styles: S;
classNames: Record<string, keyof S>;
propDefs: { [K in keyof P]: PropDefConfig<P[K]> };
utilityProps?: string[];
surface?: 'container' | 'leaf';
}
export interface UseDefinitionOptions<D extends ComponentConfig<any, any>> {
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<any>,
> = Config['default'] extends {} // Check if default is present (not undefined)
? Exclude<UnwrapResponsive<T>, undefined> // Only remove undefined
: UnwrapResponsive<T>;
// Build ownProps shape from propDefs
type ResolvedOwnProps<
P,
PropDefs extends Record<keyof P, PropDefConfig<any>>,
> = {
[K in keyof P]: ResolvePropType<P[K], PropDefs[K]>;
};
// Conditional children type based on surface
type ChildrenProps<Surface extends 'container' | 'leaf' | undefined> =
Surface extends 'container'
? { surfaceChildren: ReactNode }
: { children: ReactNode };
// Data attributes type
type DataAttributeKeys<PropDefs> = {
[K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true }
? K
: never;
}[keyof PropDefs];
type DataAttributes<PropDefs> = {
[K in DataAttributeKeys<PropDefs> as `data-${string & K}`]?: string;
} & { 'data-on-surface'?: string };
// Helper to define the base rest props
type BaseRestProps<P, PropDefs> = Omit<P, keyof PropDefs>;
export interface UseDefinitionResult<
D extends ComponentConfig<any, any>,
P extends Record<string, any>,
> {
ownProps: {
classes: Record<keyof D['classNames'], string>;
} & ResolvedOwnProps<P, D['propDefs']> &
ChildrenProps<D['surface']>;
restProps: keyof BaseRestProps<P, D['propDefs']> extends never
? Record<string, never>
: BaseRestProps<P, D['propDefs']>;
dataAttributes: DataAttributes<D['propDefs']>;
utilityStyle: CSSProperties;
}