diff --git a/docs/contributing/transpile-diff.md b/docs/contributing/transpile-diff.md
index bd735c6521..ebe5721fee 100644
--- a/docs/contributing/transpile-diff.md
+++ b/docs/contributing/transpile-diff.md
@@ -1,6 +1,7 @@
---
title: Comparing Transpiled Output
-category: Contributor Guides
+category: Contributing
+order: 8
---
# Comparing Transpiled Output
diff --git a/docs/theming/legacy-theme-overrides.md b/docs/theming/legacy-theme-overrides.md
index a163a42961..7a118e0624 100644
--- a/docs/theming/legacy-theme-overrides.md
+++ b/docs/theming/legacy-theme-overrides.md
@@ -7,6 +7,15 @@ relevantForAI: true
## Using theme overrides
+```js
+---
+type: embed
+---
+
+ The examples on this page use the legacy theming system and are designed for v11.6 components. If you are viewing the v11.7 version, switch to v11.6 to see the examples working correctly.
+
+```
+
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:
diff --git a/docs/theming/new-theme-overrides.md b/docs/theming/new-theme-overrides.md
index cbd36566a5..671cfaaf55 100644
--- a/docs/theming/new-theme-overrides.md
+++ b/docs/theming/new-theme-overrides.md
@@ -7,6 +7,15 @@ relevantForAI: true
## New Theme Override Patterns
+```js
+---
+type: embed
+---
+
+ The examples on this page use the new theming system and require v11.7+ components. If you are viewing the v11.6 version, switch to v11.7 to see the examples working correctly.
+
+```
+
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.
diff --git a/packages/__docs__/src/ColorCard/index.tsx b/packages/__docs__/src/ColorCard/index.tsx
index 8046589237..21df88e8a4 100644
--- a/packages/__docs__/src/ColorCard/index.tsx
+++ b/packages/__docs__/src/ColorCard/index.tsx
@@ -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 {
- 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()
+
+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 {
+ 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 (
({}))
class ColorName extends Component {
static displayName = 'ColorName'
static allowedProps = allowedProps
@@ -41,18 +47,38 @@ class ColorName extends Component {
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 (
-
- {name}
+
+ {name}
{name}
diff --git a/packages/__docs__/src/ColorName/props.ts b/packages/__docs__/src/ColorName/props.ts
index ba0ad39fab..4ba761c5f7 100644
--- a/packages/__docs__/src/ColorName/props.ts
+++ b/packages/__docs__/src/ColorName/props.ts
@@ -23,6 +23,8 @@
*/
import type { AsElementType } from '@instructure/shared-types'
+import type { ComponentStyle, WithStyleProps } from '@instructure/emotion'
+
type ColorNameOwnProps = {
name: string
as?: AsElementType
@@ -30,11 +32,12 @@ type ColorNameOwnProps = {
}
type PropKeys = keyof ColorNameOwnProps
type AllowedPropKeys = Readonly>
-type ColorNameProps = ColorNameOwnProps
+type ColorNameProps = ColorNameOwnProps & WithStyleProps
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 }
diff --git a/packages/__docs__/src/ColorName/styles.ts b/packages/__docs__/src/ColorName/styles.ts
new file mode 100644
index 0000000000..1073181e41
--- /dev/null
+++ b/packages/__docs__/src/ColorName/styles.ts
@@ -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