Skip to content

Commit

Permalink
System: Initial work on \forwardRef\ system.
Browse files Browse the repository at this point in the history
This is heavily based on NextUI, kudos to them!
  • Loading branch information
filiphsps authored Nov 17, 2023
1 parent 7fd9a02 commit 2751a52
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 25 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"prepare": "husky install",
"dev": "concurrently -i --raw npm:dev:*",
"dev:docs": "turbo dev --filter=@nordcom/nordstar-docs",
"dev:packages": "",
"dev:packages": "turbo dev --filter=!@nordcom/nordstar-docs --filter=!@nordcom/nordstar-storybook",
"dev:storybook": "turbo dev --filter=@nordcom/nordstar-storybook",
"build": "turbo build",
"build:docs": "turbo build --filter=@nordcom/nordstar-docs",
Expand Down
9 changes: 6 additions & 3 deletions packages/components/heading/src/heading.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@
line-height: 1;
}

&.heading {
&.h1 {
font-weight: 800;
font-size: 4.25rem;
line-height: 0.9;
text-transform: uppercase;
}

&.subheading {
&.h2 {
font-weight: 400;
font-size: 2.75rem;
line-height: 1.1;
letter-spacing: 0.02rem;
text-transform: lowercase;
color: var(--color-foreground-secondary);
}
&.h3 {
}
&.h4 {
}
}
16 changes: 8 additions & 8 deletions packages/components/heading/src/heading.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import type { Meta } from '@storybook/react';
import React from 'react';
import type { HeadingProps } from '../src';
import { Heading } from '../src';

const story: Meta<typeof Heading> = {
title: 'Components/Heading',
title: 'Components/Typography/Heading',
component: Heading,
argTypes: {
subheading: {
control: { type: 'boolean' }
level: {
options: ['h1', 'h2', 'h3', 'h4'],
control: { type: 'radio' }
}
}
};

const Template = (args: HeadingProps) => <Heading {...args}>A compelling page-title</Heading>;

export const Standard = {
export const h1 = {
render: Template,
args: {
subheading: false
level: 'h1'
}
};

export const SubHeading = {
export const h2 = {
render: Template,
args: {
subheading: true
level: 'h2'
}
};

Expand Down
24 changes: 11 additions & 13 deletions packages/components/heading/src/heading.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import type { ElementType, HTMLProps } from 'react';
import { forwardRef } from '@nordcom/nordstar-system';
import styles from './heading.module.scss';

export type HeadingProps = {
subheading?: boolean;
level?: 'h1' | 'h2' | 'h3' | 'h4';
as?: ElementType;
} & HTMLProps<HTMLHeadingElement>;

const Heading = (props: HeadingProps) => {
const { as, subheading, children, className } = props;
const Heading = forwardRef<'h1', HeadingProps>((props, ref) => {
const { as, level = 'h1', className } = props;

const Tag = as || subheading ? 'h2' : 'h1';
const classes = `${styles.container} ${subheading ? styles.subheading : styles.heading}${
className ? ` ${className}` : ''
}`;
const Tag = as || level;
const classes = `${styles.container} ${styles[level]}${className ? ` ${className}` : ''}`;

return (
<Tag {...{ ...props, subheading: undefined }} className={classes}>
{children}
</Tag>
);
};
// TODO: Figure out a better way to exclude props from being passed to the DOM element.
return <Tag {...{ ...props, level: undefined, as: undefined, className: undefined }} className={classes} />;
});

Heading.displayName = 'Nordstar.Heading';

export default Heading;
4 changes: 4 additions & 0 deletions packages/core/system/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
// Utilities
export * from './utils';

// Components
export * from './nordstar-provider';
56 changes: 56 additions & 0 deletions packages/core/system/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { forwardRef as baseForwardRef } from 'react';

export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
as?: As;
};

export type OmitCommonProps<Target, OmitAdditionalProps extends keyof any = never> = Omit<
Target,
'as' | OmitAdditionalProps
>;

export type RightJoinProps<SourceProps extends object = {}, OverrideProps extends object = {}> = OmitCommonProps<
SourceProps,
keyof OverrideProps
> &
OverrideProps;

export type As<Props = any> = React.ElementType<Props>;
export type MergeWithAs<
ComponentProps extends object,
AsProps extends object,
AdditionalProps extends object = {},
AsComponent extends As = As
> = (RightJoinProps<ComponentProps, AdditionalProps> | RightJoinProps<AsProps, AdditionalProps>) & {
as?: AsComponent;
};

export type InternalForwardRefRenderFunction<
Component extends As,
Props extends object = {},
OmitKeys extends keyof any = never
> = {
<AsComponent extends As = Component>(
props: MergeWithAs<
React.ComponentPropsWithoutRef<Component>,
Omit<React.ComponentPropsWithoutRef<AsComponent>, OmitKeys>,
Props,
AsComponent
>
): React.ReactElement | null;
readonly $$typeof: symbol;
defaultProps?: Partial<Props> | undefined;
propTypes?: React.WeakValidationMap<Props> | undefined;
displayName?: string | undefined;
};

export function forwardRef<Component extends As, Props extends object, OmitKeys extends keyof any = never>(
component: React.ForwardRefRenderFunction<
any,
RightJoinProps<PropsOf<Component>, Props> & {
as?: As;
}
>
) {
return baseForwardRef(component) as InternalForwardRefRenderFunction<Component, Props, OmitKeys>;
}

0 comments on commit 2751a52

Please sign in to comment.