refactor: update text and link components to be polymorphic (#171)
This commit is contained in:
@@ -15,12 +15,16 @@ interface LinkItem {
|
|||||||
|
|
||||||
const links: LinkItem[] = [
|
const links: LinkItem[] = [
|
||||||
{
|
{
|
||||||
text: "Summer '24 Course Schedule",
|
text: "Fall '24 Course Schedule",
|
||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/',
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20249/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Fall '24 Course Schedule",
|
text: "Summer '24 Course Schedule",
|
||||||
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20236/',
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20246/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Spring '24 Course Schedule",
|
||||||
|
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Registration Info Sheet',
|
text: 'Registration Info Sheet',
|
||||||
|
|||||||
@@ -7,31 +7,28 @@ import React from 'react';
|
|||||||
|
|
||||||
import styles from './Link.module.scss';
|
import styles from './Link.module.scss';
|
||||||
|
|
||||||
type Props = Omit<TextProps, 'span'> & {
|
type Props = TextProps<'a'> & {
|
||||||
url?: string;
|
href?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
title?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reusable Text component with props that build on top of the design system for the extension
|
* A reusable Text component with props that build on top of the design system for the extension
|
||||||
*/
|
*/
|
||||||
export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
export default function Link(props: PropsWithChildren<Props>): JSX.Element {
|
||||||
let passedProps = {
|
let { className, href, ...passedProps } = props;
|
||||||
...props,
|
|
||||||
};
|
|
||||||
const { url } = props;
|
|
||||||
|
|
||||||
if (url && !props.onClick) {
|
if (href && !props.onClick) {
|
||||||
passedProps.onClick = () => background.openNewTab({ url });
|
passedProps.onClick = () => background.openNewTab({ url: href });
|
||||||
}
|
}
|
||||||
const isDisabled = props.disabled || (!url && !props.onClick);
|
const isDisabled = props.disabled || (!href && !props.onClick);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
color='bluebonnet'
|
color='bluebonnet'
|
||||||
{...passedProps}
|
{...passedProps}
|
||||||
as='span'
|
as='a'
|
||||||
|
tabIndex={isDisabled ? -1 : 0}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
styles.link,
|
styles.link,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,34 +1,49 @@
|
|||||||
|
import type { PropsOf, ReactTag } from '@headlessui/react/dist/types';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { ElementType, ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import styles from './Text.module.scss';
|
import styles from './Text.module.scss';
|
||||||
|
|
||||||
/**
|
type PropsWeControl = 'as' | 'children';
|
||||||
* Props for the Text component.
|
type CleanProps<TTag extends ReactTag, TOmitableProps extends PropertyKey = never> = Omit<
|
||||||
*/
|
PropsOf<TTag>,
|
||||||
export type TextProps = {
|
TOmitableProps | PropsWeControl
|
||||||
variant?: Variant;
|
>;
|
||||||
} & (
|
type OurProps<TTag extends ReactTag> = {
|
||||||
| (React.HTMLAttributes<HTMLSpanElement> & { as?: 'span' })
|
as?: TTag;
|
||||||
| (React.HTMLAttributes<HTMLDivElement> & { as: 'div' })
|
children?: ReactNode;
|
||||||
);
|
};
|
||||||
|
|
||||||
|
type AsProps<TTag extends ReactTag, TOverrides = {}> = CleanProps<TTag, keyof TOverrides> & OurProps<TTag> & TOverrides;
|
||||||
|
|
||||||
const variants = ['mini', 'small', 'p', 'h4', 'h3-course', 'h3', 'h2-course', 'h2', 'h1-course', 'h1'] as const;
|
const variants = ['mini', 'small', 'p', 'h4', 'h3-course', 'h3', 'h2-course', 'h2', 'h1-course', 'h1'] as const;
|
||||||
|
|
||||||
type Variant = (typeof variants)[number];
|
type Variant = (typeof variants)[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the Text component.
|
||||||
|
*/
|
||||||
|
export type TextProps<TTag extends ElementType = 'span'> = PropsOf<TTag>['className'] extends string
|
||||||
|
? AsProps<
|
||||||
|
TTag,
|
||||||
|
{
|
||||||
|
variant?: Variant;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
: never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reusable Text component with props that build on top of the design system for the extension
|
* A reusable Text component with props that build on top of the design system for the extension
|
||||||
*/
|
*/
|
||||||
export default function Text({ variant, as, className, ...props }: PropsWithChildren<TextProps>): JSX.Element {
|
export default function Text<TTag extends ElementType = 'span'>({
|
||||||
const mergedClassName = clsx(styles.text, styles[variant], className);
|
as,
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
...rest
|
||||||
|
}: TextProps<TTag>): JSX.Element {
|
||||||
|
const Comp = as || 'span';
|
||||||
|
const mergedClassName = clsx(styles.text, styles[variant || 'p'], className);
|
||||||
|
|
||||||
if (as === 'div') return <div className={mergedClassName} {...props} />;
|
return <Comp className={mergedClassName} {...rest} />;
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={mergedClassName} {...props}>
|
|
||||||
{props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ export default function RecruitmentBanner(): JSX.Element {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Text color='white'>
|
<Text color='white'>
|
||||||
Interested in helping us develop UT Registration Plus? Check out our{' '}
|
Interested in helping us develop UT Registration Plus? Check out our{' '}
|
||||||
<Link color='white' url={DISCORD_URL}>
|
<Link color='white' href={DISCORD_URL}>
|
||||||
Discord Server
|
Discord Server
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
and{' '}
|
and{' '}
|
||||||
<Link color='white' url={GITHUB_URL}>
|
<Link color='white' href={GITHUB_URL}>
|
||||||
GitHub
|
GitHub
|
||||||
</Link>
|
</Link>
|
||||||
!
|
!
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default defineConfig({
|
|||||||
rules: [
|
rules: [
|
||||||
[
|
[
|
||||||
'btn-transition',
|
'btn-transition',
|
||||||
{ transition: 'color 180ms, border-color 150ms, background-color 150ms, box-shadow 100ms, transform 50ms' },
|
{ transition: 'color 180ms, border-color 150ms, background-color 150ms, box-shadow 0ms, transform 50ms' },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'ring-offset-0',
|
'ring-offset-0',
|
||||||
@@ -21,7 +21,8 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4',
|
focusable: 'outline-none ring-blue-500/50 dark:ring-blue-400/60 ring-0 focus-visible:ring-4',
|
||||||
btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable',
|
btn: 'h-10 w-auto flex cursor-pointer justify-center items-center gap-2 rounded-1 px-4 py-0 text-4.5 btn-transition disabled:(cursor-not-allowed opacity-50) active:enabled:scale-96 focusable',
|
||||||
|
link: 'text-ut-burntorange underline underline-offset-2 hover:text-ut-orange focus-visible:text-ut-orange focusable btn-transition ease-out-expo',
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
easing: {
|
easing: {
|
||||||
|
|||||||
Reference in New Issue
Block a user