Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/contributing/transpile-diff.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Comparing Transpiled Output
category: Contributor Guides
category: Contributing
order: 8
---

# Comparing Transpiled Output
Expand Down
9 changes: 9 additions & 0 deletions docs/theming/legacy-theme-overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ relevantForAI: true

## Using theme overrides

```js
---
type: embed
---
<Alert variant="warning" margin="0 0 medium">
The examples on this page use the <strong>legacy theming system</strong> and are designed for <strong>v11.6</strong> components. If you are viewing the v11.7 version, <Link href={window.location.pathname.match(/v\d+_\d+/) ? window.location.pathname.replace(/v\d+_\d+/, 'v11_6') : `/v11_6${window.location.pathname}`}>switch to v11.6</Link> to see the examples working correctly.
</Alert>
```

This document gives an overview on how you can customize Instructure UI components by tweaking their theme variables.
While this gives you a level of flexibility on the look and feel of the components you should be aware of 2 things:

Expand Down
9 changes: 9 additions & 0 deletions docs/theming/new-theme-overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ relevantForAI: true

## New Theme Override Patterns

```js
---
type: embed
---
<Alert variant="warning" margin="0 0 medium">
The examples on this page use the <strong>new theming system</strong> and require <strong>v11.7+</strong> components. If you are viewing the v11.6 version, <Link href={window.location.pathname.match(/v\d+_\d+/) ? window.location.pathname.replace(/v\d+_\d+/, 'v11_7') : `/v11_7${window.location.pathname}`}>switch to v11.7</Link> to see the examples working correctly.
</Alert>
```

This guide covers all the override patterns available in the new theming system (v11.7+). The new system uses a layered token architecture: **primitives** (raw values) -> **semantics** (meaning) -> **components** (per-component tokens).

Overrides are applied via the `themeOverride` prop on `InstUISettingsProvider`, which is separate from the `theme` prop. The `theme` prop replaces the active theme entirely; `themeOverride` layers modifications on top.
Expand Down
109 changes: 66 additions & 43 deletions packages/__docs__/src/ColorCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,58 +29,81 @@ import { View } from '@instructure/ui-view'
import { ColorName } from '../ColorName'
import type { ColorCardProps } from './props'
import { allowedProps } from './props'
class ColorCard extends Component<ColorCardProps> {
static displayName = 'ColorCard'
static allowedProps = allowedProps
static defaultProps = {
minimal: false
}

parseColor(value: string) {
// 6-digit hex: #RRGGBB
const hex6 = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value)
if (hex6) {
return {
hex: value.toUpperCase(),
rgb: `${parseInt(hex6[1], 16)},${parseInt(hex6[2], 16)},${parseInt(
hex6[3],
16
)}`
}
type ParsedColor = {
hex?: string
rgb?: string
alpha?: string
} | null

// Cache parsed results so theme re-renders skip the regex work.
const colorCache = new Map<string, ParsedColor>()

function parseColor(value: string): ParsedColor {
if (colorCache.has(value)) return colorCache.get(value)!

// 6-digit hex: #RRGGBB
const hex6 = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value)
if (hex6) {
const result: ParsedColor = {
hex: value.toUpperCase(),
rgb: `${parseInt(hex6[1], 16)},${parseInt(hex6[2], 16)},${parseInt(
hex6[3],
16
)}`
}
colorCache.set(value, result)
return result
}
// 8-digit hex with alpha: #RRGGBBAA
const hex8 = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(
value
)
if (hex8) {
const alpha = Math.round((parseInt(hex8[4], 16) / 255) * 100)
const result: ParsedColor = {
hex: value.toUpperCase(),
rgb: `${parseInt(hex8[1], 16)},${parseInt(hex8[2], 16)},${parseInt(
hex8[3],
16
)}`,
alpha: `${alpha}%`
}
// 8-digit hex with alpha: #RRGGBBAA
const hex8 = /^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(
colorCache.set(value, result)
return result
}
// rgba(r,g,b,a)
const rgba =
/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/i.exec(
value
)
if (hex8) {
const alpha = Math.round((parseInt(hex8[4], 16) / 255) * 100)
return {
hex: value.toUpperCase(),
rgb: `${parseInt(hex8[1], 16)},${parseInt(hex8[2], 16)},${parseInt(
hex8[3],
16
)}`,
alpha: `${alpha}%`
}
}
// rgba(r,g,b,a)
const rgba =
/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)/i.exec(
value
)
if (rgba) {
const alpha =
rgba[4] !== undefined
? `${Math.round(parseFloat(rgba[4]) * 100)}%`
: undefined
return { rgb: `${rgba[1]},${rgba[2]},${rgba[3]}`, alpha }
if (rgba) {
const alpha =
rgba[4] !== undefined
? `${Math.round(parseFloat(rgba[4]) * 100)}%`
: undefined
const result: ParsedColor = {
rgb: `${rgba[1]},${rgba[2]},${rgba[3]}`,
alpha
}
return null
colorCache.set(value, result)
return result
}

colorCache.set(value, null)
return null
}

class ColorCard extends Component<ColorCardProps> {
static displayName = 'ColorCard'
static allowedProps = allowedProps
static defaultProps = {
minimal: false
}

render() {
const { name, hex, minimal } = this.props
const parsed = this.parseColor(hex)
const parsed = parseColor(hex)
return (
<View
as="figure"
Expand Down
38 changes: 32 additions & 6 deletions packages/__docs__/src/ColorName/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@
import { Component } from 'react'

import { Text } from '@instructure/ui-text'
import { TruncateText } from '@instructure/ui-truncate-text'
import { Tooltip } from '@instructure/ui-tooltip'
import { ScreenReaderContent } from '@instructure/ui-a11y-content'
import type { ColorNameProps, ColorNameState } from './props'

import { withStyleForDocs } from '../withStyleForDocs'
import generateStyle from './styles'

import { allowedProps } from './props'
import type { ColorNameProps, ColorNameState } from './props'

// CSS truncation instead of heavy TruncateText
@withStyleForDocs(generateStyle, () => ({}))
class ColorName extends Component<ColorNameProps, ColorNameState> {
static displayName = 'ColorName'
static allowedProps = allowedProps
Expand All @@ -41,18 +47,38 @@ class ColorName extends Component<ColorNameProps, ColorNameState> {
isTruncated: false
}

handleUpdate = (isTruncated: boolean) => {
private strongRef: HTMLElement | null = null

componentDidMount() {
this.props.makeStyles?.()
this.checkTruncation()
}

componentDidUpdate(prevProps: ColorNameProps) {
this.props.makeStyles?.()
if (prevProps.name !== this.props.name) {
this.checkTruncation()
}
}

checkTruncation = () => {
if (!this.strongRef) return
const isTruncated = this.strongRef.scrollWidth > this.strongRef.clientWidth
if (this.state.isTruncated !== isTruncated) {
this.setState({ isTruncated })
}
}

handleStrongRef = (el: HTMLElement | null) => {
this.strongRef = el
}

renderText() {
const { name, ...passthrough } = this.props
const { name, styles, makeStyles, ...passthrough } = this.props
return (
<Text {...passthrough}>
<strong aria-hidden>
<TruncateText onUpdate={this.handleUpdate}>{name}</TruncateText>
<strong aria-hidden ref={this.handleStrongRef} css={styles?.truncate}>
{name}
</strong>
<ScreenReaderContent>{name}</ScreenReaderContent>
</Text>
Expand Down
7 changes: 5 additions & 2 deletions packages/__docs__/src/ColorName/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@
*/

import type { AsElementType } from '@instructure/shared-types'
import type { ComponentStyle, WithStyleProps } from '@instructure/emotion'

type ColorNameOwnProps = {
name: string
as?: AsElementType
lineHeight?: 'default' | 'fit' | 'condensed' | 'double'
}
type PropKeys = keyof ColorNameOwnProps
type AllowedPropKeys = Readonly<Array<PropKeys>>
type ColorNameProps = ColorNameOwnProps
type ColorNameProps = ColorNameOwnProps & WithStyleProps<null, ColorNameStyle>

const allowedProps: AllowedPropKeys = ['name', 'as', 'lineHeight']
type ColorNameState = {
isTruncated: boolean
}
type ColorNameStyle = ComponentStyle<'truncate'>
export { allowedProps }
export type { ColorNameState, ColorNameProps }
export type { ColorNameState, ColorNameProps, ColorNameStyle }
42 changes: 42 additions & 0 deletions packages/__docs__/src/ColorName/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.
*/
import type { ColorNameStyle } from './props'

/**
* Generates the style object from the theme and provided additional information
* @return {Object} The final style object, which will be used in the component
*/
const generateStyle = (): ColorNameStyle => {
return {
truncate: {
label: 'colorName__truncate',
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}
}

export default generateStyle
Loading