Skip to content
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

feat(Text, Flex, Box): typed html attributes #1583

Merged
Merged
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
13 changes: 4 additions & 9 deletions src/components/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,13 @@ export interface TextProps<C extends React.ElementType = 'span'>
* Ability to override default html tag
*/
as?: C;
style?: React.CSSProperties;
className?: string;
id?: string;
children?: React.ReactNode;
title?: string;
ellipsisLines?: number;
}

type TextRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];

type TextPropsWithoutRef<C extends React.ElementType> = TextProps<C> &
Omit<React.ComponentPropsWithoutRef<C>, keyof TextProps<C>>;
type TextPropsWithTypedAttrs<T extends React.ElementType> = TextProps<T> &
Omit<React.ComponentPropsWithoutRef<T>, keyof TextProps<T>>;

/**
* A component for working with typography.
Expand Down Expand Up @@ -68,7 +63,7 @@ export const Text = React.forwardRef(function Text<C extends React.ElementType =
style: outerStyle,
qa,
...rest
}: TextPropsWithoutRef<C>,
}: TextPropsWithTypedAttrs<C>,
ref?: TextRef<C>,
) {
const Tag: React.ElementType = as || 'span';
Expand Down Expand Up @@ -104,6 +99,6 @@ export const Text = React.forwardRef(function Text<C extends React.ElementType =
}) as (<C extends React.ElementType = 'span'>({
ref,
...props
}: TextPropsWithoutRef<C> & {ref?: TextRef<C>}) => React.ReactElement) & {displayName: string};
}: TextPropsWithTypedAttrs<C> & {ref?: TextRef<C>}) => React.ReactElement) & {displayName: string};

Text.displayName = 'Text';
44 changes: 44 additions & 0 deletions src/components/Text/__stories__/Text.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';

import type {Meta, StoryObj} from '@storybook/react';

import {Button} from '../../Button';
import {TextInput} from '../../controls';
import {Flex} from '../../layout';
import {Text, colorText, text} from '../index';

Expand Down Expand Up @@ -101,3 +103,45 @@ export const WordBreak: Story = {
wordBreak: 'break-all',
},
};

const LabelWithControlledFocusStory = () => {
const ref = React.useRef<HTMLLabelElement>(null); // don't delete this ref - needed to check correct react html type inshurance
const id = 'some-id';

return (
<Flex gap="5">
<Text as="label" htmlFor={id} ref={ref}>
Click on label to control text input
</Text>
<TextInput id={id} />
</Flex>
);
};

export const LabelWithControlledFocus: Story = {
render: () => <LabelWithControlledFocusStory />,

args: {},
};

const WithCustomElementRenderStory = () => {
return (
<Flex direction={'column'} gap="5">
<Text as="code">
{
'<Text as={Button} size={\'m\'} view="action" color="danger-heavy" variant="header-1">Hello World!</Text>'
}
</Text>

<Text as={Button} size={'m'} view="action" color="danger-heavy" variant="header-1">
Hello World!
</Text>
</Flex>
);
};

export const WithCustomElementRender: Story = {
render: () => <WithCustomElementRenderStory />,

args: {},
};
11 changes: 9 additions & 2 deletions src/components/layout/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export interface BoxProps<T extends React.ElementType = 'div'>
spacing?: SpacingProps;
}

type BoxRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];

type BoxPropsWithTypedAttrs<T extends React.ElementType> = BoxProps<T> &
Omit<React.ComponentPropsWithoutRef<T>, keyof BoxProps<T>>;

/**
* Basic block to build other components and for standalone usage as a smart block with build in support of most usable css properties and shortcut `spacing` properties.
* ```tsx
Expand Down Expand Up @@ -63,7 +68,7 @@ export const Box = React.forwardRef(function Box<T extends React.ElementType = '
overflow,
...props
}: BoxProps<T>,
ref?: React.ComponentPropsWithRef<T>['ref'],
ref?: BoxRef<T>,
) {
const Tag: React.ElementType = as || 'div';

Expand All @@ -88,4 +93,6 @@ export const Box = React.forwardRef(function Box<T extends React.ElementType = '
{children}
</Tag>
);
});
}) as (<C extends React.ElementType = 'div'>(
props: BoxPropsWithTypedAttrs<C> & {ref?: BoxRef<C>},
) => React.ReactElement) & {displayName: string};
22 changes: 14 additions & 8 deletions src/components/layout/Flex/Flex.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';

import type {QAProps} from '../../types';
import {block} from '../../utils/cn';
import {Box} from '../Box/Box';
import type {BoxProps} from '../Box/Box';
Expand All @@ -12,7 +11,7 @@ import './Flex.scss';

const b = block('flex');

export interface FlexProps<T extends React.ElementType = 'div'> extends QAProps, BoxProps<T> {
export interface FlexProps<T extends React.ElementType = 'div'> extends BoxProps<T> {
/**
* `flex-direction` property
*/
Expand Down Expand Up @@ -90,6 +89,11 @@ export interface FlexProps<T extends React.ElementType = 'div'> extends QAProps,
space?: Space | MediaPartial<Space>;
}

type FlexRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];

type FlexPropsWithTypedAttrs<T extends React.ElementType> = FlexProps<T> &
Omit<React.ComponentPropsWithoutRef<T>, keyof FlexProps<T>>;

/**
* Flexbox model utility component.
*
Expand Down Expand Up @@ -124,11 +128,9 @@ export interface FlexProps<T extends React.ElementType = 'div'> extends QAProps,
* ---
* Storybook - https://preview.gravity-ui.com/uikit/?path=/docs/layout--playground#flex
*/
export const Flex = React.forwardRef(function Flex<T extends React.ElementType = 'div'>(
props: FlexProps<T>,
ref: React.ComponentPropsWithRef<T>['ref'],
) {
export const Flex = function Flex<T extends React.ElementType = 'div'>(props: FlexProps<T>) {
const {
as: propsAs,
direction,
grow,
basis,
Expand All @@ -151,6 +153,8 @@ export const Flex = React.forwardRef(function Flex<T extends React.ElementType =
...restProps
} = props;

const as: React.ElementType = propsAs || 'div';

const {
getClosestMediaProps,
theme: {spaceBaseSize},
Expand All @@ -174,6 +178,7 @@ export const Flex = React.forwardRef(function Flex<T extends React.ElementType =

return (
<Box
as={as}
className={b(
{
'center-content': centerContent,
Expand All @@ -198,7 +203,6 @@ export const Flex = React.forwardRef(function Flex<T extends React.ElementType =
justifySelf: applyMediaProps(justifySelf),
...style,
}}
ref={ref}
{...restProps}
>
{space
Expand All @@ -209,4 +213,6 @@ export const Flex = React.forwardRef(function Flex<T extends React.ElementType =
: children}
</Box>
);
});
} as (<C extends React.ElementType = 'div'>(
props: FlexPropsWithTypedAttrs<C> & {ref?: FlexRef<C>},
) => React.ReactElement) & {displayName: string};
Loading