Skip to content

Commit 9618859

Browse files
committed
docs: improve multi-version docs behavior and routing and add new Component versioning guide page
1 parent ced0fb5 commit 9618859

10 files changed

Lines changed: 263 additions & 127 deletions

File tree

docs/guides/accessing-the-dom.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Accessing the DOM
33
category: Guides
4-
order: 3
4+
order: 4
55
relevantForAI: true
66
---
77

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: Component versioning
3+
category: Guides
4+
order: 2
5+
---
6+
7+
## Why components are versioned
8+
9+
When InstUI needs to make a breaking change to a component (renamed props, changed behaviour, etc.), the old version is **kept alongside the new one** instead of being replaced. Upgrading the library no longer forces you to immediately rewrite every component usage — you can migrate to new versions on your own schedule.
10+
11+
New component versions appear with InstUI **minor** version bumps (e.g. `11.7``11.8`). Patch releases never include breaking changes.
12+
13+
> The docs UI "Component version" selector labels library `v11_6` as **`v1 (legacy)`** and library `v11_7` as **`v2`**. This page uses the `v11_X` form because it matches the actual NPM import paths.
14+
15+
## Import paths
16+
17+
Every InstUI component package supports three import styles:
18+
19+
### Default — `@instructure/ui-<name>`
20+
21+
Always points to the **oldest** still-supported component version. Upgrading the library without changing your imports will keep your code working without surprises.
22+
23+
```js
24+
---
25+
type: code
26+
---
27+
import { Alert } from '@instructure/ui-alerts'
28+
```
29+
30+
### Pinned — `@instructure/ui-<name>/v11_X`
31+
32+
Locks the import to a specific InstUI minor version of the component. When you are ready to adopt a breaking change, update the path to the next pinned version.
33+
34+
```js
35+
---
36+
type: code
37+
---
38+
import { Alert } from '@instructure/ui-alerts/v11_7'
39+
```
40+
41+
### Latest — `@instructure/ui-<name>/latest`
42+
43+
Always points to the newest component version. This may bring breaking changes when you upgrade InstUI itself.
44+
45+
```js
46+
---
47+
type: code
48+
---
49+
import { Alert } from '@instructure/ui-alerts/latest'
50+
```
51+
52+
### Per-package or umbrella package
53+
54+
InstUI also ships an umbrella package, `@instructure/ui`, which re-exports every component from the individual `@instructure/ui-*` packages. Two equivalent import styles work — both resolve to the same component:
55+
56+
```js
57+
---
58+
type: code
59+
---
60+
// per-package import
61+
import { Alert } from '@instructure/ui-alerts/v11_7'
62+
63+
// umbrella package import
64+
import { Alert } from '@instructure/ui/v11_7'
65+
```
66+
67+
The same three path styles (default / `/v11_X` / `/latest`) work on the umbrella package as well. Pick per-package imports when you want better tree-shaking and only pull in what you use, or the umbrella package when you'd rather depend on a single `@instructure/ui` entry in your `package.json`.
68+
69+
## Versions and theming engines
70+
71+
InstUI is in the middle of a transition between two theming systems. Which engine a component uses depends on which version you import:
72+
73+
- **`v1`** (library `v11_6` and earlier) — legacy theming. Components are configured through the Canvas theme variables and the `themeOverride` prop, which accepts a function or object that maps to the component's own theme map. See the [Legacy theme overrides](legacy-theme-overrides) guide.
74+
75+
- **`v2`** (library `v11_7` and newer) — new theming system. Components consume pre-resolved design tokens, and theming is done through the new token override structure. See the [New theme overrides](new-theme-overrides) guide.
76+
77+
Mixing imports from both groups in the same app is fully supported — the two engines run side-by-side without conflict.
78+
79+
### Supported themes per version
80+
81+
Each version of the library is only compatible with the themes built for its theming engine:
82+
83+
- **`v1`** (library `v11_6` and earlier) supports the legacy themes:
84+
85+
- `canvas`
86+
- `canvasHighContrast`
87+
88+
- **`v2`** (library `v11_7` and newer) supports themes built on the new token system. The legacy Canvas looks are preserved (re-implemented on the new engine) and two brand-new themes are added:
89+
90+
- `legacyCanvas` — same visual style as the old `canvas`, but on the new engine
91+
- `legacyCanvasHighContrast` — same visual style as the old `canvasHighContrast`, on the new engine
92+
- `light` — new light theme
93+
- `dark` — new dark theme
94+
95+
This means that when you move a component import from `/v11_6` to `/v11_7` you don't have to change anything visually — you can stay on the `legacyCanvas` theme and opt in to `light` or `dark` only when you're ready.

docs/guides/forms.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Forms
33
category: Guides
4-
order: 4
4+
order: 5
55
relevantForAI: true
66
---
77

docs/guides/module-federation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Module federation
33
category: Guides
4-
order: 2
4+
order: 3
55
relevantForAI: true
66
---
77

docs/theming/legacy-theme-overrides.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ relevantForAI: true
77

88
## Using theme overrides
99

10-
```js
11-
---
12-
type: embed
13-
---
14-
<Alert variant="warning" margin="0 0 medium">
15-
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.
16-
</Alert>
17-
```
18-
1910
This document gives an overview on how you can customize Instructure UI components by tweaking their theme variables.
2011
While this gives you a level of flexibility on the look and feel of the components you should be aware of 2 things:
2112

docs/theming/new-theme-overrides.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@ relevantForAI: true
77

88
## New Theme Override Patterns
99

10-
```js
11-
---
12-
type: embed
13-
---
14-
<Alert variant="warning" margin="0 0 medium">
15-
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.
16-
</Alert>
17-
```
18-
1910
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).
2011

2112
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.
@@ -527,9 +518,52 @@ type: example
527518
</InstUISettingsProvider>
528519
```
529520

530-
### 13. Provider-level overrides cannot target a child component selectively
521+
### 13. Independent overrides for child parts of compound components
522+
523+
Most compound components expose each part as a separate component with its own `componentId`. This means you can independently override each part via `components` — both overrides take effect:
524+
525+
```js
526+
---
527+
type: example
528+
---
529+
<InstUISettingsProvider theme={canvas}>
530+
<InstUISettingsProvider
531+
themeOverride={{
532+
components: {
533+
TableColHeader: {
534+
background: 'rebeccapurple',
535+
color: 'gold'
536+
},
537+
TableRowHeader: {
538+
background: 'deeppink',
539+
color: 'white'
540+
}
541+
}
542+
}}
543+
>
544+
<Table caption="Independent overrides: ColHeader purple, RowHeader deeppink">
545+
<Table.Head>
546+
<Table.Row>
547+
<Table.ColHeader id="h-row">TableColHeader — purple</Table.ColHeader>
548+
<Table.ColHeader id="h-col">TableColHeader — purple</Table.ColHeader>
549+
</Table.Row>
550+
</Table.Head>
551+
<Table.Body>
552+
<Table.Row>
553+
<Table.RowHeader>TableRowHeader — deeppink</Table.RowHeader>
554+
<Table.Cell>TableCell — unchanged</Table.Cell>
555+
</Table.Row>
556+
<Table.Row>
557+
<Table.RowHeader>TableRowHeader — deeppink</Table.RowHeader>
558+
<Table.Cell>TableCell — unchanged</Table.Cell>
559+
</Table.Row>
560+
</Table.Body>
561+
</Table>
562+
</InstUISettingsProvider>
563+
</InstUISettingsProvider>
564+
```
531565

532-
Because `Button` uses `BaseButton`'s theme internally, a `components.Button` entry in the provider's `themeOverride` does **not** override `BaseButton`'s tokens for `Button` instances only. Both `BaseButton` and `Button` share the same `BaseButton` theme variables, so a `components.BaseButton` override affects both, regardless of whether a separate `components.Button` entry is also present.
566+
**Exception — `Button` and `BaseButton`:** `Button` uses `BaseButton`'s `componentId` internally, so `components.Button` has no effect. A `components.BaseButton` override affects all `BaseButton` instances including those rendered inside `Button` — there is no way to target only one:
533567

534568
```js
535569
---

packages/__docs__/src/App/index.tsx

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,10 @@ class App extends Component<AppProps, AppState> {
302302
fetchMinorVersionData(signal)
303303
.then((minorVersionsData) => {
304304
if (minorVersionsData && minorVersionsData.libraryVersions.length > 0) {
305-
// If URL has a version, use it; otherwise use default
306-
const selectedMinorVersion =
307-
urlMinorVersion ?? minorVersionsData.defaultVersion
305+
const { libraryVersions } = minorVersionsData
306+
const latestVersion = libraryVersions[libraryVersions.length - 1]
307+
// If URL has a version, use it; otherwise use the latest version
308+
const selectedMinorVersion = urlMinorVersion ?? latestVersion
308309
// Update globals before fetching docs so renders use correct components
309310
updateGlobalsForVersion(selectedMinorVersion)
310311
this.setState({
@@ -342,6 +343,48 @@ class App extends Component<AppProps, AppState> {
342343
) {
343344
this.handleNavigationFocusRegion()
344345
}
346+
347+
if (prevState.key !== this.state.key) {
348+
this.handleMinorVersionForPage(this.state.key)
349+
}
350+
351+
if (!prevState.minorVersionsData && this.state.minorVersionsData) {
352+
this.handleMinorVersionForPage(this.state.key)
353+
}
354+
}
355+
356+
getLatestMinorVersion = () => {
357+
const { minorVersionsData } = this.state
358+
if (!minorVersionsData) return undefined
359+
const { libraryVersions } = minorVersionsData
360+
return libraryVersions[libraryVersions.length - 1]
361+
}
362+
363+
handleMinorVersionForPage = (key: string | undefined) => {
364+
const { selectedMinorVersion, minorVersionsData, docsData } = this.state
365+
if (!minorVersionsData || !selectedMinorVersion || !key) return
366+
367+
const latestVersion = this.getLatestMinorVersion()!
368+
369+
if (key === 'legacy-theme-overrides') {
370+
if (selectedMinorVersion !== 'v11_6') {
371+
this.handleMinorVersionChange('v11_6')
372+
}
373+
} else if (key === 'new-theme-overrides') {
374+
if (selectedMinorVersion !== latestVersion) {
375+
this.handleMinorVersionChange(latestVersion)
376+
}
377+
} else {
378+
const category = docsData?.docs[key]?.category
379+
const isGuidePage =
380+
!!category &&
381+
!category.startsWith('components') &&
382+
!category.startsWith('utilities')
383+
if (isGuidePage && selectedMinorVersion !== latestVersion) {
384+
// Guide pages (.md) always show with the latest version
385+
this.handleMinorVersionChange(latestVersion)
386+
}
387+
}
345388
}
346389

347390
componentWillUnmount() {
@@ -555,8 +598,9 @@ class App extends Component<AppProps, AppState> {
555598
}
556599

557600
renderThemeSelect() {
601+
const { minorVersionsData, selectedMinorVersion } = this.state
558602
const allThemeKeys = Object.keys(this.state.docsData!.themes)
559-
const showNewThemes = this.state.selectedMinorVersion !== 'v11_6'
603+
const showNewThemes = selectedMinorVersion !== 'v11_6'
560604

561605
const themeKeys = showNewThemes
562606
? allThemeKeys.filter(
@@ -579,17 +623,56 @@ class App extends Component<AppProps, AppState> {
579623
return themeKey
580624
}
581625

626+
const formatMinorVersion = (version: string) => {
627+
if (version === 'v11_6') return 'v1 (legacy)'
628+
if (version === 'v11_7') return 'v2'
629+
return version.replace(/_/g, '.')
630+
}
631+
582632
const smallScreen = this.state.layout === 'small'
583633
const currentThemeKey =
584634
this.state.themeKey && themeKeys.includes(this.state.themeKey)
585635
? this.state.themeKey
586636
: themeKeys[0]
587637

638+
const { key, docsData } = this.state
639+
const versionLockedPages = ['legacy-theme-overrides', 'new-theme-overrides']
640+
if (versionLockedPages.includes(key ?? '')) return null
641+
642+
// Only show on Components pages — other categories (guides, utilities,
643+
// themes, etc.) don't need theme or component-version switching.
644+
const category = key ? docsData?.docs[key]?.category : undefined
645+
if (!category || !category.startsWith('components')) return null
646+
647+
const showMinorVersionSelect =
648+
minorVersionsData && minorVersionsData.libraryVersions.length > 1
649+
588650
return themeKeys.length > 1 ? (
589651
<Flex
590652
margin={smallScreen ? 'none none medium' : 'none none x-small'}
591653
justifyItems={!smallScreen ? 'end' : 'start'}
654+
wrap="wrap"
655+
gap="small"
592656
>
657+
{showMinorVersionSelect && (
658+
<Flex.Item shouldGrow={smallScreen} shouldShrink={smallScreen}>
659+
<Select
660+
name="minorVersion"
661+
renderLabel="Component version"
662+
onChange={(_e, { value }) => {
663+
if (value) this.handleMinorVersionChange(value as string)
664+
}}
665+
value={selectedMinorVersion ?? this.getLatestMinorVersion()}
666+
width="16.5rem"
667+
>
668+
{[...minorVersionsData!.libraryVersions].reverse().map((ver) => (
669+
<option key={ver} value={ver}>
670+
{formatMinorVersion(ver)}
671+
</option>
672+
))}
673+
</Select>
674+
</Flex.Item>
675+
)}
593676
<Flex.Item shouldGrow={smallScreen} shouldShrink={smallScreen}>
594677
<Select
595678
name="theme"
@@ -741,9 +824,19 @@ class App extends Component<AppProps, AppState> {
741824
// New-theme components are looked up via themeVariables.newTheme.components
742825
const themeVariables = themes[themeKey!].resource as ThemeType
743826
const heading = currentData.extension !== '.md' ? currentData.title : ''
827+
const isGuidePage = currentData.extension === '.md'
744828
const documentContent = (
745829
<View as="div" padding="x-large none none">
746830
{this.renderThemeSelect()}
831+
{isGuidePage && (
832+
<Alert variant="info" margin="xx-large 0">
833+
This page is made for the latest version (
834+
{this.getLatestMinorVersion()?.replace('_', '.')}) of InstUI.{' '}
835+
<Link href="component-versioning">
836+
Learn more about the versioning system
837+
</Link>
838+
</Alert>
839+
)}
747840
<View
748841
elementRef={this.mainContentRef}
749842
tabIndex={0}
@@ -997,8 +1090,6 @@ class App extends Component<AppProps, AppState> {
9971090
version={version}
9981091
versionsData={versionsData}
9991092
minorVersionsData={this.state.minorVersionsData}
1000-
selectedMinorVersion={this.state.selectedMinorVersion}
1001-
onMinorVersionChange={this.handleMinorVersionChange}
10021093
/>
10031094

10041095
<Nav

0 commit comments

Comments
 (0)