From e93f2045d559ad6814cc48e901a8f8e86b926981 Mon Sep 17 00:00:00 2001 From: Nandor_Czegledi Date: Thu, 29 Jan 2026 13:40:00 +0100 Subject: [PATCH] feat(ui-icons,ui-billboard): migrate to new theming system --- docs/guides/upgrade-guide.md | 17 ++++ packages/ui-billboard/src/Billboard/README.md | 8 +- packages/ui-billboard/src/Billboard/index.tsx | 71 ++++++++++++----- packages/ui-billboard/src/Billboard/props.ts | 2 +- packages/ui-billboard/src/Billboard/styles.ts | 13 +--- packages/ui-billboard/src/Billboard/theme.ts | 77 ------------------- .../src/lucide/wrapLucideIcon/props.ts | 12 ++- .../src/lucide/wrapLucideIcon/styles.ts | 6 ++ 8 files changed, 94 insertions(+), 112 deletions(-) delete mode 100644 packages/ui-billboard/src/Billboard/theme.ts diff --git a/docs/guides/upgrade-guide.md b/docs/guides/upgrade-guide.md index 181509b98c..78d20542a3 100644 --- a/docs/guides/upgrade-guide.md +++ b/docs/guides/upgrade-guide.md @@ -95,6 +95,23 @@ type: embed ``` +### Billboard + +```js +--- +type: embed +--- + + +``` + ### Breadcrumb ```js diff --git a/packages/ui-billboard/src/Billboard/README.md b/packages/ui-billboard/src/Billboard/README.md index 06f20b5c31..7bb6a011b4 100644 --- a/packages/ui-billboard/src/Billboard/README.md +++ b/packages/ui-billboard/src/Billboard/README.md @@ -43,7 +43,7 @@ type: example onClick={function () { alert('This Billboard was clicked!') }} - hero={(size) => } + hero={(size) => } /> ``` @@ -57,7 +57,7 @@ type: example margin="large" message="Click this link" href="http://instructure.com" - hero={(size) => } + hero={(size) => } /> ``` @@ -73,7 +73,7 @@ type: example onClick={function () { alert('This Billboard was clicked!') }} - hero={(size) => } + hero={(size) => } /> ``` @@ -89,7 +89,7 @@ type: example onClick={function () { alert('This Billboard was clicked!') }} - hero={(size) => } + hero={(size) => } disabled /> ``` diff --git a/packages/ui-billboard/src/Billboard/index.tsx b/packages/ui-billboard/src/Billboard/index.tsx index 1c6f673126..976c2c7b53 100644 --- a/packages/ui-billboard/src/Billboard/index.tsx +++ b/packages/ui-billboard/src/Billboard/index.tsx @@ -31,23 +31,33 @@ import { callRenderProp, getElementType } from '@instructure/ui-react-utils' +import { renderIconWithProps } from '@instructure/ui-icons' -import { withStyleRework as withStyle } from '@instructure/emotion' +import { withStyle } from '@instructure/emotion' import generateStyle from './styles' -import generateComponentTheme from './theme' import { allowedProps } from './props' -import type { BillboardProps, HeroIconSize } from './props' +import type { BillboardProps } from './props' import type { ViewProps } from '@instructure/ui-view' +// Map Billboard sizes to Lucide icon sizes +const billboardSizeToIconSize = { + small: 'illu-sm', + medium: 'illu-md', + large: 'illu-lg' +} as const + /** --- category: components --- **/ -@withStyle(generateStyle, generateComponentTheme) -class Billboard extends Component { +@withStyle(generateStyle) +class Billboard extends Component< + BillboardProps, + { isHovered: boolean; isActive: boolean } +> { static readonly componentId = 'Billboard' static allowedProps = allowedProps @@ -61,6 +71,11 @@ class Billboard extends Component { elementRef: () => {} } as const + state = { + isHovered: false, + isActive: false + } + ref: Element | null = null handleRef = (el: Element | null) => { @@ -93,25 +108,37 @@ class Billboard extends Component { ) } - get SVGIconSize(): HeroIconSize { - const size = this.props.size + handleMouseEnter = () => { + this.setState({ isHovered: true }) + } - // serve up appropriate SVGIcon size for each Billboard size - if (size === 'small') { - return 'medium' - } else if (size === 'large') { - return 'x-large' - } else { - return 'large' - } + handleMouseLeave = () => { + this.setState({ isHovered: false, isActive: false }) + } + + handleMouseDown = () => { + this.setState({ isActive: true }) + } + + handleMouseUp = () => { + this.setState({ isActive: false }) } renderHero() { - if (typeof this.props.hero === 'function') { - return this.props.hero(this.SVGIconSize) - } else { - return this.props.hero - } + const { hero, size } = this.props + const { isHovered, isActive } = this.state + + if (!hero) return null + + const lucideSize = billboardSizeToIconSize[size!] + // Priority: active > hover > default + const iconColor = isActive + ? 'onColor' + : isHovered + ? 'infoColor' + : 'baseColor' + + return renderIconWithProps(hero, lucideSize, iconColor) } renderContent() { @@ -157,6 +184,10 @@ class Billboard extends Component { css={styles?.billboard} href={href} onClick={this.handleClick} + onMouseEnter={this.handleMouseEnter} + onMouseLeave={this.handleMouseLeave} + onMouseDown={this.handleMouseDown} + onMouseUp={this.handleMouseUp} disabled={disabled} aria-disabled={disabled || readOnly ? 'true' : undefined} > diff --git a/packages/ui-billboard/src/Billboard/props.ts b/packages/ui-billboard/src/Billboard/props.ts index f1a6755902..b43946762c 100644 --- a/packages/ui-billboard/src/Billboard/props.ts +++ b/packages/ui-billboard/src/Billboard/props.ts @@ -40,7 +40,7 @@ type BillboardOwnProps = { /** * Provide an component or Instructure Icon for the hero image */ - hero?: React.ReactElement | ((iconSize: HeroIconSize) => React.ReactElement) + hero?: React.ReactElement | ((iconSize?: HeroIconSize) => React.ReactElement) /** * If you're using an icon, this prop will size it. Also sets the font-size * of the headline and message. diff --git a/packages/ui-billboard/src/Billboard/styles.ts b/packages/ui-billboard/src/Billboard/styles.ts index 3ba329f795..7ea4965a79 100644 --- a/packages/ui-billboard/src/Billboard/styles.ts +++ b/packages/ui-billboard/src/Billboard/styles.ts @@ -22,8 +22,8 @@ * SOFTWARE. */ -import type { BillboardTheme } from '@instructure/shared-types' import type { BillboardProps, BillboardStyle } from './props' +import type { NewComponentTypes } from '@instructure/ui-themes' /** * --- @@ -36,7 +36,7 @@ import type { BillboardProps, BillboardStyle } from './props' * @return {Object} The final style object, which will be used in the component */ const generateStyle = ( - componentTheme: BillboardTheme, + componentTheme: NewComponentTypes['Billboard'], props: BillboardProps ): BillboardStyle => { const { size, href, onClick, disabled, hero, heading } = props @@ -83,15 +83,11 @@ const generateStyle = ( '&:hover, &:focus': { textDecoration: 'none', outline: 'none', - borderColor: componentTheme.iconHoverColor, - - '& [class$=-billboard__hero]': { - color: componentTheme.iconHoverColor - } + borderColor: componentTheme.messageColorClickable }, '&:active': { background: componentTheme.clickableActiveBg, - borderColor: componentTheme.iconHoverColor, + borderColor: componentTheme.messageColorClickable, '& [class$=-billboard__hero], & [class$=-billboard__message]': { color: componentTheme.clickableActiveText @@ -128,7 +124,6 @@ const generateStyle = ( hero: { label: 'billboard__hero', display: 'block', - color: componentTheme.iconColor, ...sizeVariants[size!].hero, '& > img, & > svg': { diff --git a/packages/ui-billboard/src/Billboard/theme.ts b/packages/ui-billboard/src/Billboard/theme.ts deleted file mode 100644 index 49a8212c7b..0000000000 --- a/packages/ui-billboard/src/Billboard/theme.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2015 - present Instructure, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/* Global variables (colors, typography, spacing, etc.) are defined in lib/themes */ - -import type { Theme, ThemeSpecificStyle } from '@instructure/ui-themes' -import { BillboardTheme } from '@instructure/shared-types' - -/** - * Generates the theme object for the component from the theme and provided additional information - * @param {Object} theme The actual theme object. - * @return {Object} The final theme object with the overrides and component variables - */ -const generateComponentTheme = (theme: Theme): BillboardTheme => { - const { borders, colors, spacing, typography, key: themeName } = theme - - const themeSpecificStyle: ThemeSpecificStyle = { - canvas: { - iconHoverColor: theme['ic-link-color'], - messageColorClickable: theme['ic-link-color'], - clickableActiveBg: theme['ic-brand-primary'] - } - } - - const componentVariables: BillboardTheme = { - fontFamily: typography?.fontFamily, - paddingSmall: spacing?.small, - paddingMedium: spacing?.medium, - paddingLarge: spacing?.medium, - iconColor: colors?.contrasts?.grey4570, - mediumMargin: spacing?.small, - largeMargin: spacing?.medium, - iconHoverColor: colors?.contrasts?.blue4570, - backgroundColor: colors?.contrasts?.white1010, - iconHoverColorInverse: colors?.contrasts?.white1010, - buttonBorderWidth: borders?.widthMedium, - buttonBorderRadius: borders?.radiusLarge, - messageColor: colors?.contrasts?.blue4570, - messageColorClickable: colors?.contrasts?.blue4570, - messageColorInverse: colors?.contrasts?.grey1111, - messageFontSizeSmall: typography?.fontSizeSmall, - messageFontSizeMedium: typography?.fontSizeMedium, - messageFontSizeLarge: typography?.fontSizeLarge, - clickableActiveBg: colors?.contrasts?.blue4570, - clickableActiveText: colors?.contrasts?.white1010, - buttonBorderStyle: borders?.style, - buttonHoverBorderStyle: 'dashed' - } - - return { - ...componentVariables, - ...themeSpecificStyle[themeName] - } -} - -export default generateComponentTheme diff --git a/packages/ui-icons/src/lucide/wrapLucideIcon/props.ts b/packages/ui-icons/src/lucide/wrapLucideIcon/props.ts index f6c0c79194..f6e8937425 100644 --- a/packages/ui-icons/src/lucide/wrapLucideIcon/props.ts +++ b/packages/ui-icons/src/lucide/wrapLucideIcon/props.ts @@ -48,7 +48,17 @@ type LegacyColorTokens = /** * Semantic size tokens for icons - includes SVGIcon legacy tokens, they are DEPRECATED and will be deleted, DON'T USE THEM. */ -type IconSizeToken = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | SVGIconSizeToken +type IconSizeToken = + | 'xs' + | 'sm' + | 'md' + | 'lg' + | 'xl' + | '2xl' + | 'illu-sm' + | 'illu-md' + | 'illu-lg' + | SVGIconSizeToken /** * Semantic color tokens from Icon theme diff --git a/packages/ui-icons/src/lucide/wrapLucideIcon/styles.ts b/packages/ui-icons/src/lucide/wrapLucideIcon/styles.ts index 21b587f4a8..b7a59c0feb 100644 --- a/packages/ui-icons/src/lucide/wrapLucideIcon/styles.ts +++ b/packages/ui-icons/src/lucide/wrapLucideIcon/styles.ts @@ -51,6 +51,9 @@ const convertSemanticSize = ( lg: componentTheme.sizeLg, xl: componentTheme.sizeXl, '2xl': componentTheme.size2xl, + 'illu-sm': componentTheme.illuSm, + 'illu-md': componentTheme.illuMd, + 'illu-lg': componentTheme.illuLg, // Legacy SVGIcon size tokens (DEPRECATED) 'x-small': '1.125rem', small: '2rem', @@ -78,6 +81,9 @@ const convertSizeToStrokeWidth = ( lg: componentTheme.strokeWidthLg, xl: componentTheme.strokeWidthXl, '2xl': componentTheme.strokeWidth2xl, + 'illu-sm': componentTheme.strokeWidthIlluSm, + 'illu-md': componentTheme.strokeWidthIlluMd, + 'illu-lg': componentTheme.strokeWidthIlluLg, // Legacy SVGIcon stroke tokens (DEPRECATED) 'x-small': '0.0859375rem', small: '0.15625rem',