From 2dde534bd525158ec594086db98024dbbd5064a5 Mon Sep 17 00:00:00 2001 From: Toppanto Bence Date: Thu, 10 Apr 2025 13:50:13 +0200 Subject: [PATCH] feat(ui-link): add variants to INSTUI-4506 --- packages/shared-types/src/BaseTheme.ts | 29 +++++++ .../src/ComponentThemeVariables.ts | 6 +- packages/ui-link/src/Link/README.md | 33 ++++++++ packages/ui-link/src/Link/index.tsx | 4 +- packages/ui-link/src/Link/props.ts | 16 +++- packages/ui-link/src/Link/styles.ts | 84 ++++++++++++++++--- packages/ui-link/src/Link/theme.ts | 6 +- packages/ui-text/src/Text/index.tsx | 5 +- .../src/sharedThemeTokens/borders.ts | 2 +- .../src/sharedThemeTokens/breakpoints.ts | 2 +- .../ui-themes/src/sharedThemeTokens/forms.ts | 2 +- .../ui-themes/src/sharedThemeTokens/media.ts | 2 +- .../src/sharedThemeTokens/shadows.ts | 2 +- .../src/sharedThemeTokens/spacing.ts | 2 +- .../src/sharedThemeTokens/stacking.ts | 2 +- .../src/sharedThemeTokens/transitions.ts | 2 +- .../src/sharedThemeTokens/typography.ts | 2 +- 17 files changed, 170 insertions(+), 31 deletions(-) diff --git a/packages/shared-types/src/BaseTheme.ts b/packages/shared-types/src/BaseTheme.ts index 26784a7e4e..93d686d31d 100644 --- a/packages/shared-types/src/BaseTheme.ts +++ b/packages/shared-types/src/BaseTheme.ts @@ -77,6 +77,35 @@ type Spacing = { large: string | 0 xLarge: string | 0 xxLarge: string | 0 + + space0: string | 0 + space2: string | 0 + space4: string | 0 + space8: string | 0 + space12: string | 0 + space16: string | 0 + space24: string | 0 + space36: string | 0 + space48: string | 0 + space60: string | 0 + sections: string | 0 + sectionElements: string | 0 + trayElements: string | 0 + modalElements: string | 0 + moduleElements: string | 0 + paddingCardLarge: string | 0 + paddingCardMedium: string | 0 + paddingCardSmall: string | 0 + selects: string | 0 + textareas: string | 0 + inputFields: string | 0 + checkboxes: string | 0 + radios: string | 0 + toggles: string | 0 + buttons: string | 0 + tags: string | 0 + statusIndicators: string | 0 + dataPoints: string | 0 } type Stacking = { diff --git a/packages/shared-types/src/ComponentThemeVariables.ts b/packages/shared-types/src/ComponentThemeVariables.ts index bfe9448308..be5c2bed80 100644 --- a/packages/shared-types/src/ComponentThemeVariables.ts +++ b/packages/shared-types/src/ComponentThemeVariables.ts @@ -670,6 +670,9 @@ export type ImgTheme = { export type LinkTheme = { fontFamily: Typography['fontFamily'] fontWeight: Typography['fontWeightNormal'] + fontSize: string | 0 + fontSizeSmall: string | 0 + lineHeight: number color: Colors['contrasts']['blue4570'] textDecorationWithinText: string hoverTextDecorationWithinText: string @@ -684,7 +687,8 @@ export type LinkTheme = { focusInverseOutlineColor: Colors['contrasts']['white1010'] focusInverseIconOutlineColor: Colors['contrasts']['white1010'] iconSize: string - iconPlusTextMargin: Spacing['xxSmall'] + iconPlusTextMargin: string | 0 + iconPlusTextMarginSmall: string | 0 textUnderlineOffset: string } diff --git a/packages/ui-link/src/Link/README.md b/packages/ui-link/src/Link/README.md index 37a42470c6..25d0274dbd 100644 --- a/packages/ui-link/src/Link/README.md +++ b/packages/ui-link/src/Link/README.md @@ -22,6 +22,37 @@ type: example ``` +### Variant + +In order to make it easy to get the most commonly used links, we have the variant prop. It will set all the necessary styles (fontSize, lineHeight, and textDecoration). + +```js +--- +type: example +--- +
+
+In a line of text you should use the } href="https://instructure.github.io/instructure-ui/">inline link variant. +
+ +

+
+In a line of text, where the text is smaller, use the } href="https://instructure.github.io/instructure-ui/">inline-small link variant + +
+ +

+
+If the link is standalone (not in a text), use the standalone } href="https://instructure.github.io/instructure-ui/">standalone +
+ +

+
+If the link is standalone (not in a text), but smaller, use the standalone-small } href="https://instructure.github.io/instructure-ui/">standalone-small +
+
+``` + ### Adding margin Use the `margin` prop to add space to the left or right of the Link. Because @@ -76,6 +107,8 @@ Use the `renderIcon` property to put an [icon](#icons) inside a Link. To positio icon _after_ the link text, change the `iconPlacement` property to `end`. You can also render a Link with just an icon. Don't forget to add text for screen readers, though. +NOTE: if you want the icon to have the same `font-size` as the link, do not specify its `size`! + ```js --- type: example diff --git a/packages/ui-link/src/Link/index.tsx b/packages/ui-link/src/Link/index.tsx index 833afa9723..561907c2b9 100644 --- a/packages/ui-link/src/Link/index.tsx +++ b/packages/ui-link/src/Link/index.tsx @@ -252,9 +252,9 @@ class Link extends Component { tabIndex={tabIndex} css={this.props.styles?.link} > - {renderIcon && iconPlacement === 'start' && this.renderIcon()} + {renderIcon && iconPlacement === 'start' ? this.renderIcon() : null} {children} - {renderIcon && iconPlacement === 'end' && this.renderIcon()} + {renderIcon && iconPlacement === 'end' ? this.renderIcon() : null} ) } diff --git a/packages/ui-link/src/Link/props.ts b/packages/ui-link/src/Link/props.ts index 67b11ef82d..18f21f6e88 100644 --- a/packages/ui-link/src/Link/props.ts +++ b/packages/ui-link/src/Link/props.ts @@ -129,6 +129,11 @@ type LinkOwnProps = { * Fires when the Link is hovered */ onMouseEnter?: (event: React.MouseEvent) => void + + /** + * Sets pre-defined values for the component to achieve specific roles for the component + */ + variant?: 'inline' | 'inline-small' | 'standalone' | 'standalone-small' } export type LinkStyleProps = { @@ -174,7 +179,13 @@ const propTypes: PropValidators = { onBlur: PropTypes.func, onClick: PropTypes.func, onFocus: PropTypes.func, - onMouseEnter: PropTypes.func + onMouseEnter: PropTypes.func, + variant: PropTypes.oneOf([ + 'inline', + 'inline-small', + 'standalone', + 'standalone-small' + ]) } const allowedProps: AllowedPropKeys = [ @@ -194,7 +205,8 @@ const allowedProps: AllowedPropKeys = [ 'onBlur', 'onClick', 'onFocus', - 'onMouseEnter' + 'onMouseEnter', + 'variant' ] export type { LinkProps, LinkState, LinkStyle } diff --git a/packages/ui-link/src/Link/styles.ts b/packages/ui-link/src/Link/styles.ts index f67f2d55ed..5c046573c5 100644 --- a/packages/ui-link/src/Link/styles.ts +++ b/packages/ui-link/src/Link/styles.ts @@ -40,10 +40,33 @@ const generateStyle = ( props: LinkProps, state: LinkStyleProps ): LinkStyle => { - const { isWithinText, renderIcon, iconPlacement, color } = props + const { isWithinText, renderIcon, iconPlacement, color, variant } = props const { containsTruncateText, hasVisibleChildren } = state const inverseStyle = color === 'link-inverse' + const variantStyles = { + inline: { + fontSize: componentTheme.fontSize, + lineHeight: componentTheme.lineHeight, + textDecoration: 'underline' + }, + 'inline-small': { + fontSize: componentTheme.fontSizeSmall, + lineHeight: '1.3125rem', + textDecoration: 'underline' + }, + standalone: { + fontSize: componentTheme.fontSize, + lineHeight: componentTheme.lineHeight, + textDecoration: 'none' + }, + 'standalone-small': { + fontSize: componentTheme.fontSizeSmall, + lineHeight: componentTheme.lineHeight, + textDecoration: 'none' + } + } + const baseStyles = { boxSizing: 'border-box', fontFamily: componentTheme.fontFamily, @@ -84,9 +107,6 @@ const generateStyle = ( ...baseStyles, cursor: 'pointer', color: componentTheme.color, - textDecoration: isWithinText - ? componentTheme.textDecorationWithinText - : componentTheme.textDecorationOutsideText, '&:focus': { color: componentTheme.color, outlineColor: componentTheme.focusOutlineColor @@ -96,7 +116,14 @@ const generateStyle = ( textDecoration: isWithinText ? componentTheme.hoverTextDecorationWithinText : componentTheme.hoverTextDecorationOutsideText - } + }, + ...(variant + ? variantStyles[variant] + : { + textDecoration: isWithinText + ? componentTheme.textDecorationWithinText + : componentTheme.textDecorationOutsideText + }) } const buttonStyle = { @@ -105,10 +132,10 @@ const generateStyle = ( background: 'none', border: 'none', cursor: 'pointer', - fontSize: '1em', margin: 0, padding: 0, - textAlign: 'inherit' + textAlign: 'inherit', + ...(variant ? variantStyles[variant] : { fontSize: '1em' }) } const inverseStyles = { @@ -120,7 +147,32 @@ const generateStyle = ( color: componentTheme.colorInverse } } - + const variantIconStyles = { + inline: { + paddingInlineStart: + iconPlacement === 'start' ? 0 : componentTheme.iconPlusTextMargin, + paddingInlineEnd: + iconPlacement === 'start' ? componentTheme.iconPlusTextMargin : 0 + }, + 'inline-small': { + paddingInlineStart: + iconPlacement === 'start' ? 0 : componentTheme.iconPlusTextMarginSmall, + paddingInlineEnd: + iconPlacement === 'start' ? componentTheme.iconPlusTextMarginSmall : 0 + }, + standalone: { + paddingInlineStart: + iconPlacement === 'start' ? 0 : componentTheme.iconPlusTextMargin, + paddingInlineEnd: + iconPlacement === 'start' ? componentTheme.iconPlusTextMargin : 0 + }, + 'standalone-small': { + paddingInlineStart: + iconPlacement === 'start' ? 0 : componentTheme.iconPlusTextMarginSmall, + paddingInlineEnd: + iconPlacement === 'start' ? componentTheme.iconPlusTextMarginSmall : 0 + } + } return { link: { label: 'link', @@ -145,10 +197,18 @@ const generateStyle = ( ...(renderIcon && { fontSize: componentTheme.iconSize, boxSizing: 'border-box', - paddingInlineStart: - iconPlacement === 'start' ? 0 : componentTheme.iconPlusTextMargin, - paddingInlineEnd: - iconPlacement === 'start' ? componentTheme.iconPlusTextMargin : 0 + ...(variant + ? variantIconStyles[variant] + : { + paddingInlineStart: + iconPlacement === 'start' + ? 0 + : componentTheme.iconPlusTextMargin, + paddingInlineEnd: + iconPlacement === 'start' + ? componentTheme.iconPlusTextMargin + : 0 + }) }) } } diff --git a/packages/ui-link/src/Link/theme.ts b/packages/ui-link/src/Link/theme.ts index dfbeb55279..aa576c0cf4 100644 --- a/packages/ui-link/src/Link/theme.ts +++ b/packages/ui-link/src/Link/theme.ts @@ -52,6 +52,9 @@ const generateComponentTheme = (theme: Theme): LinkTheme => { const componentVariables: LinkTheme = { fontFamily: typography?.fontFamily, fontWeight: typography?.fontWeightNormal, + fontSize: typography?.content, + lineHeight: typography?.lineHeight150, + fontSizeSmall: typography?.contentSmall, color: colors?.contrasts?.blue5782, textDecorationWithinText: 'underline', @@ -71,7 +74,8 @@ const generateComponentTheme = (theme: Theme): LinkTheme => { focusInverseIconOutlineColor: colors?.contrasts?.white1010, iconSize: '1.125em', // make icon slightly larger than inherited font-size, - iconPlusTextMargin: spacing?.xxSmall, + iconPlusTextMargin: spacing.space4, + iconPlusTextMarginSmall: spacing.space2, textUnderlineOffset: 'auto' } diff --git a/packages/ui-text/src/Text/index.tsx b/packages/ui-text/src/Text/index.tsx index c460a6ced1..072d4eba6c 100644 --- a/packages/ui-text/src/Text/index.tsx +++ b/packages/ui-text/src/Text/index.tsx @@ -54,11 +54,8 @@ class Text extends Component { } as const checkProps() { - const { variant, size, lineHeight, weight, fontStyle } = this.props + const { variant, lineHeight, weight, fontStyle } = this.props if (variant) { - if (size) { - console.warn("[Text]: Don't use 'size' with 'variant' ") - } if (lineHeight) { console.warn("[Text]: Don't use 'lineHeight' with 'variant' ") } diff --git a/packages/ui-themes/src/sharedThemeTokens/borders.ts b/packages/ui-themes/src/sharedThemeTokens/borders.ts index aa0a6e477e..9bb8439b6c 100644 --- a/packages/ui-themes/src/sharedThemeTokens/borders.ts +++ b/packages/ui-themes/src/sharedThemeTokens/borders.ts @@ -26,7 +26,7 @@ import { Border } from '@instructure/shared-types' // use for consistency between buttons, text inputs, etc. -const borders: Border = Object.freeze({ +const borders: Border = Object.freeze({ radiusSmall: '0.125rem', // 2px radiusMedium: '0.25rem', // 4px radiusLarge: '0.5rem', // 8px diff --git a/packages/ui-themes/src/sharedThemeTokens/breakpoints.ts b/packages/ui-themes/src/sharedThemeTokens/breakpoints.ts index 58a2bbd457..a27579aae2 100644 --- a/packages/ui-themes/src/sharedThemeTokens/breakpoints.ts +++ b/packages/ui-themes/src/sharedThemeTokens/breakpoints.ts @@ -38,7 +38,7 @@ const values = { xLarge: 75 // 1200px } -const breakpoints: Breakpoints = Object.freeze({ +const breakpoints: Breakpoints = Object.freeze({ xxSmall: `${values.xxSmall}em`, xSmall: `${values.xSmall}em`, small: `${values.small}em`, diff --git a/packages/ui-themes/src/sharedThemeTokens/forms.ts b/packages/ui-themes/src/sharedThemeTokens/forms.ts index a3c3cc3a42..ed5dab7517 100644 --- a/packages/ui-themes/src/sharedThemeTokens/forms.ts +++ b/packages/ui-themes/src/sharedThemeTokens/forms.ts @@ -24,7 +24,7 @@ import { Forms } from '@instructure/shared-types' -const forms: Forms = Object.freeze({ +const forms: Forms = Object.freeze({ inputHeightSmall: '1.75rem', inputHeightMedium: '2.375rem', inputHeightLarge: '3rem' diff --git a/packages/ui-themes/src/sharedThemeTokens/media.ts b/packages/ui-themes/src/sharedThemeTokens/media.ts index cd29bb3910..dd6ba2cb08 100644 --- a/packages/ui-themes/src/sharedThemeTokens/media.ts +++ b/packages/ui-themes/src/sharedThemeTokens/media.ts @@ -25,7 +25,7 @@ import { breakpoints } from './breakpoints' import { Media } from '@instructure/shared-types' -const media: Media = Object.freeze({ +const media: Media = Object.freeze({ mediumMin: `min-width: ${breakpoints.medium}`, largeMin: `min-width: ${breakpoints.large}`, xLargeMin: `min-width: ${breakpoints.xLarge}` diff --git a/packages/ui-themes/src/sharedThemeTokens/shadows.ts b/packages/ui-themes/src/sharedThemeTokens/shadows.ts index 4cfd408e42..4fbe58a722 100644 --- a/packages/ui-themes/src/sharedThemeTokens/shadows.ts +++ b/packages/ui-themes/src/sharedThemeTokens/shadows.ts @@ -31,7 +31,7 @@ const values = [ '0 0.375rem 0.4375rem rgba(0, 0, 0, 0.1), 0 0.625rem 1.75rem rgba(0, 0, 0, 0.25)' ] -const shadows: Shadows = Object.freeze({ +const shadows: Shadows = Object.freeze({ depth1: values[0], depth2: values[1], depth3: values[2], diff --git a/packages/ui-themes/src/sharedThemeTokens/spacing.ts b/packages/ui-themes/src/sharedThemeTokens/spacing.ts index c51dfdce0b..1c357f3339 100644 --- a/packages/ui-themes/src/sharedThemeTokens/spacing.ts +++ b/packages/ui-themes/src/sharedThemeTokens/spacing.ts @@ -24,7 +24,7 @@ import { Spacing } from '@instructure/shared-types' -const spacing: Spacing = Object.freeze({ +const spacing: Spacing = Object.freeze({ // legacy spacing tokens: xxxSmall: '0.125rem', // 2px xxSmall: '0.375rem', // 6px diff --git a/packages/ui-themes/src/sharedThemeTokens/stacking.ts b/packages/ui-themes/src/sharedThemeTokens/stacking.ts index a908979fe8..c0f84657cc 100644 --- a/packages/ui-themes/src/sharedThemeTokens/stacking.ts +++ b/packages/ui-themes/src/sharedThemeTokens/stacking.ts @@ -24,7 +24,7 @@ import { Stacking } from '@instructure/shared-types' -const stacking: Stacking = Object.freeze({ +const stacking: Stacking = Object.freeze({ topmost: 9999, above: 1, below: -1, diff --git a/packages/ui-themes/src/sharedThemeTokens/transitions.ts b/packages/ui-themes/src/sharedThemeTokens/transitions.ts index 98c74ad0c4..1b020249ad 100644 --- a/packages/ui-themes/src/sharedThemeTokens/transitions.ts +++ b/packages/ui-themes/src/sharedThemeTokens/transitions.ts @@ -24,7 +24,7 @@ import { Transitions } from '@instructure/shared-types' -const transitions: Transitions = Object.freeze({ +const transitions: Transitions = Object.freeze({ duration: '300ms', timing: 'ease-in-out' } as const) diff --git a/packages/ui-themes/src/sharedThemeTokens/typography.ts b/packages/ui-themes/src/sharedThemeTokens/typography.ts index a0f7d8317e..78b3d64186 100644 --- a/packages/ui-themes/src/sharedThemeTokens/typography.ts +++ b/packages/ui-themes/src/sharedThemeTokens/typography.ts @@ -24,7 +24,7 @@ import { Typography } from '@instructure/shared-types' -const typography: Typography = Object.freeze({ +const typography: Typography = Object.freeze({ fontFamily: 'LatoWeb, Lato, "Helvetica Neue", Helvetica, Arial, sans-serif', fontFamilyMonospace: 'Menlo, Consolas, Monaco, "Andale Mono", monospace',