import React, { ReactNode } from 'react'; import styled from 'styled-components'; import type { GlobalProps } from '@lendi-ui/commons/utils'; import { filterGlobalProps, ThemeNames, getThemeName } from '@lendi-ui/commons/utils'; import getTypography from '@lendi-ui/commons/typography'; import type { TextVariant } from '@lendi-ui/commons/typography'; import { BreakpointMedia } from '../../tokens'; import { getTextColor, linkStyle, getFontSize, getLineHeight } from './styles'; export interface FontSizeType { mobile: string; desktop?: string; } export type TextAlignProp = 'left' | 'right' | 'center'; interface BaseTextProps extends GlobalProps { children: ReactNode; /** * Indicates which html tag should be used. Use the appropriate tag for the content structure for accessibility. */ tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'label'; /** * Override the text alignment if needed */ align?: TextAlignProp; /** * If you need to use Text as a wrapper, what selector should the styles target. If you use this, pass 'span' to the tag prop. */ styleTarget?: string; /** * Activate mobile styles. For docs and testing purposes only, do not use. */ isMobile?: boolean; /** * White text for on dark backgrounds */ isInverse?: boolean; /** * CSS display options */ display?: 'block' | 'flex' | 'inline-flex' | 'inline-block'; id?: string; /** * Use only when needed for error text. In most cases, errors will be provided by other LUI components and this won't be necessary. */ role?: 'alert'; className?: string; } interface BasicAppearanceProps { variant?: TextVariant; appearance?: never; } interface SecondaryAppearanceProps { variant?: 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'lead1' | 'lead2' | 'overline'; appearance?: 'subtle'; } interface ErrorAppearanceProps { variant?: 'detail1' | 'detail2'; appearance?: 'error' | 'subtle'; } export type TextAppearance = BasicAppearanceProps | SecondaryAppearanceProps | ErrorAppearanceProps; type TextProps = BaseTextProps & TextAppearance & { for?: never; }; type LabelTextProps = BaseTextProps & { tag: 'label'; /** * Only available when using the label tag */ for?: string; variant?: TextVariant; appearance?: never; }; export type TextComponentProps = TextProps | LabelTextProps; // For the styled component for Text export type StyledTextProps = Omit & { htmlFor?: string; $display?: string }; const getThemeTextAlignment = (themeName: string | undefined) => { if (themeName === ThemeNames.Lendi) { return 'center'; } return 'left'; }; const checkIsHeading = (variant: string) => { const headingsVariants = ['display', 'display1', 'display2', 'title1', 'title2', 'title3', 'title4']; return headingsVariants.includes(variant); }; const Component = styled.p( ({ variant, theme, align, isMobile, styleTarget, $display, appearance, isInverse }) => { const fontSizeValue: FontSizeType = getFontSize(variant); const isHeading = checkIsHeading(variant); const hasDesktopFontSize = Object.prototype.hasOwnProperty.call(fontSizeValue, 'desktop'); const themeName = theme?.name ?? getThemeName(); // fallback to old getTheme function when latest Theme not being used. To be removed in next major let styleObj = { margin: 0, display: $display ?? 'block', fontFamily: isHeading ? theme.asset.font.heading.family : theme.asset.font.body.family, fontWeight: getTypography(variant, 'fontWeight'), textAlign: align ?? getThemeTextAlignment(themeName), color: isInverse ? theme.color.text.inverse : getTextColor(theme, appearance), fontSize: fontSizeValue.mobile, lineHeight: getLineHeight(variant), a: linkStyle(theme), } as const; if (variant === 'overline') { styleObj = Object.assign({ textTransform: 'uppercase', letterSpacing: '0.05em' }, styleObj); } if (hasDesktopFontSize && !isMobile) { styleObj = Object.assign( { [BreakpointMedia.sm]: { fontSize: fontSizeValue.desktop, }, }, styleObj ); } if (styleTarget) { return { [styleTarget]: styleObj, }; } return styleObj; } ); export const Text = ({ children, variant = 'body1', className, ...props }: TextComponentProps) => { const globalProps = filterGlobalProps(props); let textVariantClass; switch (variant) { case 'display': textVariantClass = 'lui-display'; break; case 'display1': textVariantClass = 'lui-display1'; break; case 'display2': textVariantClass = 'lui-display2'; break; case 'title1': textVariantClass = 'lui-title1'; break; case 'title2': textVariantClass = 'lui-title2'; break; case 'title3': textVariantClass = 'lui-title3'; break; case 'title4': textVariantClass = 'lui-title4'; break; case 'lead1': textVariantClass = 'lui-lead1'; break; case 'lead2': textVariantClass = 'lui-lead2'; break; case 'subtitle1': textVariantClass = 'lui-subtitle1'; break; case 'subtitle2': textVariantClass = 'lui-subtitle2'; break; case 'body1': textVariantClass = 'lui-body1'; break; case 'body2': textVariantClass = 'lui-body2'; break; case 'button1': textVariantClass = 'lui-button1'; break; case 'button2': textVariantClass = 'lui-button2'; break; case 'detail1': textVariantClass = 'lui-detail1'; break; case 'detail2': textVariantClass = 'lui-detail2'; break; case 'overline': textVariantClass = 'lui-overline'; break; default: textVariantClass = 'lui-body1'; } return ( {children} ); };