fix(ui-scripts,emotion): fix primitives and semantics override functionality#2495
fix(ui-scripts,emotion): fix primitives and semantics override functionality#2495
Conversation
|
4e09919 to
348cc7a
Compare
348cc7a to
7b25cc6
Compare
|
TODO:
|
196adf1 to
07b822c
Compare
07b822c to
2250600
Compare
| <InstUISettingsProvider theme={canvas}> | ||
| <Alert variant="info" margin="small"> | ||
| Default info Alert. | ||
| </Alert> | ||
| <Alert | ||
| variant="info" | ||
| margin="small" | ||
| themeOverride={{ | ||
| infoBorderColor: 'crimson', | ||
| infoIconBackground: 'crimson' | ||
| }} | ||
| > | ||
| This specific Alert has crimson info styling. | ||
| </Alert> | ||
| </InstUISettingsProvider> |
There was a problem hiding this comment.
This example might give someone the false impression that InstUISettingsProvider wrapper is needed around the component when using the themeOverride prop (which is not)?
There was a problem hiding this comment.
Good catch. removed.
|
|
||
| export type { | ||
| BaseThemeOrOverride, | ||
| ThemeOrOverride, |
There was a problem hiding this comment.
Technically this is a breaking change, but I think we can leave it in
matyasf
left a comment
There was a problem hiding this comment.
Looks good, please add a task to write unit tests for this functionality
| }, | ||
| Pill: { | ||
| primaryBackground: 'purple', | ||
| primaryBorderColor: 'purple' |
There was a problem hiding this comment.
The variable names for the pill are incorrect. Please use backgroundColor and baseBorderColor instead.
| Pill:{ | ||
|
|
||
| } | ||
| } |
There was a problem hiding this comment.
Is the double InstUISettingsProvider wrapper intentional?
Was it intentional to modify this old documentation page?
2250600 to
6623b92
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 138 out of 151 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
packages/ui-scripts/lib/build/buildThemes/setupThemes.js:257
- The theme index template declares
export type Theme = BaseTheme<Primitives, Semantics>but then emitsconst theme: any = { ... }. Usinganyhere removes most of the value of generating the types and makes it easy for token shape regressions to slip through. Prefer emittingconst theme: Theme = ...and fix/cast only the specific fields that currently don’t type-check.
packages/emotion/src/getComponentThemeOverride.ts:56 getComponentThemeOverridenow types itsthemeparameter asThemeOverride, but all call sites (e.g.withStyleLegacy,useStyleLegacy, and tests) pass the value returned fromuseTheme()which isBaseThemeOrOverride(and often a full theme likecanvas). This looks like it will create TypeScript assignability errors. The parameter type should likely stay asBaseThemeOrOverride/ThemeOrLegacyOverride(or be widened) rather than the legacy override-only type.
import type {
ThemeOverride,
Overrides,
ComponentOverride
} from './EmotionTypes'
import type { ComponentTheme } from '@instructure/shared-types'
import { ThemeOverrideProp } from './withStyle'
import { ThemeOverrideValue } from './useStyle'
type ComponentName = keyof ComponentOverride | undefined
/**
* ---
* private: true
* ---
* This is a utility function which calculates the correct component theme
* based on every possible override there is.
* @param theme - Theme object
* @param displayName - Name of the component
* @param componentId - componentId of the component
* @param themeOverride - The theme override object
* @param componentTheme - The component's default theme
* @returns The calculated theme override object
*/
const getComponentThemeOverride = (
theme: ThemeOverride,
displayName: string,
componentId?: string,
// ThemeOverrideProp is the old type, ThemeOverrideValue is the new one
themeOverride?: ThemeOverrideProp['themeOverride'] | ThemeOverrideValue,
componentTheme?: ComponentTheme
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { SharedTokens } from '../commonTypes' | ||
| import { Semantics } from "./semantics" | ||
|
|
There was a problem hiding this comment.
In the generated sharedTokens.ts content you import Semantics via import { Semantics } from "./semantics", but Semantics is a type export. This should be a type-only import to avoid runtime imports / TS errors under stricter module settings (and it’s already done as import type { Semantics } ... elsewhere in this generator).
| <InstUISettingsProvider | ||
| theme={{ | ||
| componentOverrides: { | ||
| themeOverride={{ | ||
| components:{ | ||
| Alert: { | ||
| infoIconBackground: "darkblue", | ||
| infoBorderColor: "darkblue" | ||
| }, | ||
| Pill:{ | ||
|
|
||
| } | ||
| } | ||
| }} | ||
| > | ||
| <InstUISettingsProvider | ||
| themeOverride={{ | ||
| components:{ | ||
| Alert: { | ||
| infoIconBackground: "darkblue", | ||
| infoBorderColor: "darkblue" | ||
| }, | ||
| Pill:{ | ||
| infoTextColor:"red" | ||
| } | ||
| } | ||
| }} | ||
| > | ||
| <Alert variant="info" margin="small"> | ||
| My icon background and border should be dark blue in any theme. | ||
| </Alert> | ||
| <Pill | ||
| statusLabel="Status" | ||
| color="info" | ||
| margin="x-small" | ||
| > | ||
| Draft | ||
| </Pill> | ||
| </InstUISettingsProvider> | ||
| </InstUISettingsProvider> |
There was a problem hiding this comment.
This guide section is titled/structured around the legacy theme={{ componentOverrides: ... }} API, but the updated example uses nested InstUISettingsProvider themeOverride={...} blocks, repeats the same Alert override twice, and includes an empty Pill: {} override. This makes the example hard to follow and suggests a no-op override. Consider simplifying to a single provider (or clearly demonstrating nesting) and remove the empty/duplicated override entries.
| theme={{ | ||
| componentOverrides: { | ||
| TextInput: { backgroundColor: "yellow" } | ||
| TextInput: { background: "yellow" } |
There was a problem hiding this comment.
In this example the componentOverrides entry for TextInput uses { background: "yellow" }, but the TextInput theme variables in this repo use backgroundColor / backgroundHoverColor / etc. As written, the override is likely a no-op. Update the example to use the correct token key so readers can copy/paste it successfully.
| TextInput: { background: "yellow" } | |
| TextInput: { backgroundColor: "yellow" } |
| const componentId = | ||
| useTokensFrom ?? ComposedComponent.componentId?.replace('.', '') | ||
|
|
There was a problem hiding this comment.
componentId can end up undefined when withStyle is used on a component without componentId and without useTokensFrom. That causes lookups like themeOverride?.components?.[componentId] and theme.newTheme.components[componentId]?.(...) to use the string key 'undefined', and can lead to styles being computed with an empty theme unexpectedly. Consider validating componentId (and failing fast in dev) or providing a safe fallback (e.g., require componentId / useTokensFrom for this codepath).
| const componentId = | |
| useTokensFrom ?? ComposedComponent.componentId?.replace('.', '') | |
| const resolvedComponentId = | |
| useTokensFrom ?? ComposedComponent.componentId?.replace('.', '') | |
| if ( | |
| !resolvedComponentId && | |
| typeof process !== 'undefined' && | |
| process?.env?.NODE_ENV !== 'production' | |
| ) { | |
| warn( | |
| false, | |
| `[withStyle] ${displayName || 'Component'} must define either componentId or useTokensFrom. Falling back to an empty component id to avoid invalid theme lookups.` | |
| ) | |
| } | |
| const componentId = resolvedComponentId ?? '' |
| // if a new theme has been added to the lib since this component version has been frozen, it can't be used with this | ||
| // theme, so we throw an error. Solution: upgrade to the latest version, it will support it | ||
| if (frozenTheme && !frozenTheme[themeKey]) { | ||
| console.error( | ||
| `The version of ${displayName} you are using does not support the currently applied "${themeKey}" theme. ` + | ||
| `Please upgrade to the latest version of ${displayName}.` | ||
| ) | ||
| } |
There was a problem hiding this comment.
The comment says we "throw an error" when a frozen component version doesn't support the applied theme, but the code only calls console.error and continues rendering. This can both spam logs (it runs on every render) and leave the UI in an undefined state. Either actually throw (or warn(false, ...) once) or update the comment/behavior so it's consistent and non-noisy (e.g., gate behind dev-only and/or de-dupe).
note: its not complete, the WithStyleProps still has wrong types in most v2 components
…ides and add freeze functionlionality to old components
6623b92 to
e0c9869
Compare


The PR converts the theme system from static token objects to lazy functions (semantics(primitives), components(semantics), sharedTokens(semantics)), enabling override-by-recomputation. It also
introduces frozen themes so older component version's themes stay pinned to a snapshot.
This solution stores the primitives, semantics, sharedTokens (every part of every theme, except the unaffected components) and the affected components, for example
packages/ui-themes/src/themes/frozenThemes/v11_7/dark/primitives.tsTEST_PLAN:
Notes:
TODO for later: Find ALL v2 components that use
themeOverrideinternally and migrate to this new system. (e.g. ColorPreset, Select)