diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md index 71de834796..604f806122 100644 --- a/.claude/commands/commit.md +++ b/.claude/commands/commit.md @@ -7,6 +7,7 @@ Create a commit following the Instructure UI commit conventions: ## Format Requirements Use Conventional Commits format: + ``` type(scope): subject @@ -25,6 +26,7 @@ Document any breaking changes here with BREAKING CHANGE: prefix ## Breaking Changes Mark breaking changes with an exclamation mark after scope and document in body: + ``` feat(ui-select)!: remove deprecated onOpen prop @@ -32,6 +34,7 @@ BREAKING CHANGE: The onOpen prop has been removed. Use onShowOptions instead. ``` Breaking changes include: + - Removing/renaming props or components - Changing prop types or behavior - Changing defaults that affect behavior @@ -40,6 +43,7 @@ Breaking changes include: ## Commit Footer Always include: + ``` πŸ€– Generated with [Claude Code](https://claude.com/claude-code) diff --git a/.claude/commands/pr.md b/.claude/commands/pr.md index 6d9ebb8c78..df907553e1 100644 --- a/.claude/commands/pr.md +++ b/.claude/commands/pr.md @@ -28,23 +28,27 @@ All PRs must include: 6. **If Jira ticket number is unknown, ask the user for it before creating the PR** 7. Push to remote if needed: `git push -u origin ` 8. Create PR with `gh pr create --title "title" --body "$(cat <<'EOF' + ## Summary + - Bullet point 1 - Bullet point 2 ## Test Plan + - [ ] Step 1 - [ ] Step 2 ## Jira Reference + Fixes INST-XXXX (or omit this section if not applicable) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) EOF -)"` -9. Return the PR URL +)"` 9. Return the PR URL **Important**: + - Base branch is usually `master` (not main) - Analyze ALL commits in the branch, not just the latest one - Use markdown checklists for test plan diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 8f35b44778..e3cbd45cdd 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -12,6 +12,9 @@ on: jobs: deploy-preview: runs-on: ubuntu-latest + env: + GITHUB_PULL_REQUEST_PREVIEW: 'true' + PR_NUMBER: ${{ github.event.pull_request.number }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 diff --git a/.husky/commit-msg b/.husky/commit-msg index 0a4b97de53..69d7554578 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1 +1 @@ -npx --no -- commitlint --edit $1 +npx --no -- commitlint --edit "$(realpath "$1")" diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg index ebac6cae4e..b49ee5889b 100755 --- a/.husky/prepare-commit-msg +++ b/.husky/prepare-commit-msg @@ -6,7 +6,9 @@ if [ "$2" = "commit" ] && [ -n "$3" ]; then fi # Check if a rebase is in progress -if [ -d ".git/rebase-merge" ] || [ -d ".git/rebase-apply" ]; then +if [ -d "$(git rev-parse --git-path rebase-merge)" ] || + [ -d "$(git rev-parse --git-path rebase-apply)" ]; then + echo "Skipping prepare-commit-msg git hook during rebase" exit 0 fi diff --git a/.inst-ai/templates/jira/README.md b/.inst-ai/templates/jira/README.md index f67f18140d..8d50d8b35d 100644 --- a/.inst-ai/templates/jira/README.md +++ b/.inst-ai/templates/jira/README.md @@ -5,10 +5,12 @@ This directory contains prompt templates for generating Jira tickets from Slack ## Template Types ### Bug Report Templates + - `bug-report.extraction.md` - Extracts structured data from bug report conversations - `bug-report.generation.md` - Generates final Jira ticket content for bugs ### Feature Request Templates + - `feature-request.extraction.md` - Extracts structured data from feature request conversations - `feature-request.generation.md` - Generates final Jira ticket content for features @@ -42,4 +44,4 @@ export const config = { ## Template Format -Templates should be valid Markdown files with embedded prompts for the AI model. The AI will process the template content and generate responses in the expected format (JSON for extraction, ADF JSON for generation). \ No newline at end of file +Templates should be valid Markdown files with embedded prompts for the AI model. The AI will process the template content and generate responses in the expected format (JSON for extraction, ADF JSON for generation). diff --git a/.inst-ai/templates/jira/bug-report.extraction.md b/.inst-ai/templates/jira/bug-report.extraction.md index d5900d9c52..9616da2c53 100644 --- a/.inst-ai/templates/jira/bug-report.extraction.md +++ b/.inst-ai/templates/jira/bug-report.extraction.md @@ -3,10 +3,11 @@ **Task:** Analyze the conversation and extract entities into a JSON object. Use `null` for missing values. **Entities:** + - `component_name`: string | null - The name of the UI component or module mentioned - `browser_name`: string | null - Browser where the issue occurs (e.g., "Chrome", "Firefox", "Safari") - `os_name`: string | null - Operating system where the issue occurs (e.g., "macOS", "Windows", "Linux") -- `instui_version`: string | null - InstUI version (e.g., "8.51.0", "v8.51.0"). Look for @instructure/ui-* package versions in package.json, version mentions in conversation, or CodeSandbox dependencies +- `instui_version`: string | null - InstUI version (e.g., "8.51.0", "v8.51.0"). Look for @instructure/ui-\* package versions in package.json, version mentions in conversation, or CodeSandbox dependencies - `summary_of_bug`: string - Brief description of the bug - `reporter_name`: string - Name of the person reporting the bug - `environment_text`: string | null - Additional environment details @@ -18,4 +19,4 @@ **Conversation:** {{CONVERSATION_CONTENT}} -**JSON Output:** \ No newline at end of file +**JSON Output:** diff --git a/.inst-ai/templates/jira/bug-report.generation.md b/.inst-ai/templates/jira/bug-report.generation.md index 9fa1f77fbb..ebddeb6eb2 100644 --- a/.inst-ai/templates/jira/bug-report.generation.md +++ b/.inst-ai/templates/jira/bug-report.generation.md @@ -5,6 +5,7 @@ **Task:** Use the `CONTEXT` to generate a JSON object with a `summary` and an ADF `description`. **Requirements:** + - The `summary` must be: `Fix: [] `. Use the component_name from extracted data if available, otherwise use a generic name based on the affected area. - The `description` must be a valid Atlassian Document Format (ADF) JSON object. - Include all relevant technical details from the context. @@ -24,4 +25,4 @@ **Your Turn (Use the CONTEXT provided above):** **IMPORTANT:** Return ONLY valid JSON. Every property must be followed by a comma except the last one in an object or array. Double-check all commas before responding. -**Output JSON:** \ No newline at end of file +**Output JSON:** diff --git a/.inst-ai/templates/jira/feature-request.extraction.md b/.inst-ai/templates/jira/feature-request.extraction.md index b9c82a46bc..0515653a79 100644 --- a/.inst-ai/templates/jira/feature-request.extraction.md +++ b/.inst-ai/templates/jira/feature-request.extraction.md @@ -3,6 +3,7 @@ **Task:** Analyze the conversation and extract entities into a JSON object. Use `null` for missing values. **Entities:** + - `feature_name`: string | null - The name of the requested feature - `requestor_name`: string - Name of the person requesting the feature - `business_justification`: string | null - Why this feature is needed @@ -15,4 +16,4 @@ **Conversation:** {{CONVERSATION_CONTENT}} -**JSON Output:** \ No newline at end of file +**JSON Output:** diff --git a/.inst-ai/templates/jira/feature-request.generation.md b/.inst-ai/templates/jira/feature-request.generation.md index 285e4e6632..44f4e61569 100644 --- a/.inst-ai/templates/jira/feature-request.generation.md +++ b/.inst-ai/templates/jira/feature-request.generation.md @@ -5,6 +5,7 @@ **Task:** Use the `CONTEXT` to generate a JSON object with a `summary` and an ADF `description`. **Requirements:** + - The `summary` must be: `Feature: [] `. Use the feature_name or affected_components from extracted data if available, otherwise use a generic name based on the affected area. - The `description` must be a valid Atlassian Document Format (ADF) JSON object. - Include business justification, proposed solution, and success criteria. @@ -24,4 +25,4 @@ **Your Turn (Use the CONTEXT provided above):** **IMPORTANT:** Return ONLY valid JSON. Every property must be followed by a comma except the last one in an object or array. Double-check all commas before responding. -**Output JSON:** \ No newline at end of file +**Output JSON:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c50d0cf4..d8351b48d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,67 +5,45 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # [11.6.0](https://github.com/instructure/instructure-ui/compare/v11.5.0...v11.6.0) (2026-02-18) - ### Bug Fixes -* **ui-modal:** voiceOver in Chrome treats scrollable modal body as single interactive object preventing line-by-line navigation ([0be3fb1](https://github.com/instructure/instructure-ui/commit/0be3fb1dee541a37c2d38ae6656fd0330f651265)) - +- **ui-modal:** voiceOver in Chrome treats scrollable modal body as single interactive object preventing line-by-line navigation ([0be3fb1](https://github.com/instructure/instructure-ui/commit/0be3fb1dee541a37c2d38ae6656fd0330f651265)) ### Features -* **ui-date-input:** add missing role and aria-label to DateInput2 dialog ([6f2219a](https://github.com/instructure/instructure-ui/commit/6f2219a4f8df3f1261e5d41052c9233fdfaff6e8)) - - - - +- **ui-date-input:** add missing role and aria-label to DateInput2 dialog ([6f2219a](https://github.com/instructure/instructure-ui/commit/6f2219a4f8df3f1261e5d41052c9233fdfaff6e8)) # [11.5.0](https://github.com/instructure/instructure-ui/compare/v11.4.0...v11.5.0) (2026-02-03) - ### Features -* **ui-table:** expose min-width prop, so one can make a table where each column has no wrapping text ([317897a](https://github.com/instructure/instructure-ui/commit/317897a996288108865e8204be075b1cfab7530f)) -* **ui-top-nav-bar:** allow to set aria-label on TopNavBar's overflow menu button ([2702ad4](https://github.com/instructure/instructure-ui/commit/2702ad42e42e67f1d69d8aa11d4135d9eea6bff2)) -* **ui-view:** allow to set View's display prop to contents, inherit, initial, revert, revert-layer, unset ([3c3c93f](https://github.com/instructure/instructure-ui/commit/3c3c93f824317155552778c7dbe24c5e18b32615)) - - - - +- **ui-table:** expose min-width prop, so one can make a table where each column has no wrapping text ([317897a](https://github.com/instructure/instructure-ui/commit/317897a996288108865e8204be075b1cfab7530f)) +- **ui-top-nav-bar:** allow to set aria-label on TopNavBar's overflow menu button ([2702ad4](https://github.com/instructure/instructure-ui/commit/2702ad42e42e67f1d69d8aa11d4135d9eea6bff2)) +- **ui-view:** allow to set View's display prop to contents, inherit, initial, revert, revert-layer, unset ([3c3c93f](https://github.com/instructure/instructure-ui/commit/3c3c93f824317155552778c7dbe24c5e18b32615)) # [11.4.0](https://github.com/instructure/instructure-ui/compare/v11.3.0...v11.4.0) (2026-01-20) - ### Features -* **ui-color-picker:** add new labelLevel prop to set heading level in ColorContrast ([06c9e79](https://github.com/instructure/instructure-ui/commit/06c9e79fc87c31da4ca8eff61e93094aa09c8be5)) -* **ui-instructure:** add privacy notice link to the ai information panel ([447e40b](https://github.com/instructure/instructure-ui/commit/447e40b8ef1a5b776e01e831b2c5a6d45ca8274e)) - - - - +- **ui-color-picker:** add new labelLevel prop to set heading level in ColorContrast ([06c9e79](https://github.com/instructure/instructure-ui/commit/06c9e79fc87c31da4ca8eff61e93094aa09c8be5)) +- **ui-instructure:** add privacy notice link to the ai information panel ([447e40b](https://github.com/instructure/instructure-ui/commit/447e40b8ef1a5b776e01e831b2c5a6d45ca8274e)) # [11.3.0](https://github.com/instructure/instructure-ui/compare/v11.2.0...v11.3.0) (2026-01-12) - ### Bug Fixes -* **ui-color-picker:** fix mixer button alignment and visual jump ([68c3e60](https://github.com/instructure/instructure-ui/commit/68c3e60acc5e1f410ed79a623a35fa3eaf7107f5)) -* **ui-color-picker:** fix popover scrolling when content exceeds viewport ([66f2b18](https://github.com/instructure/instructure-ui/commit/66f2b18af0dead1f62ee61629262c39c6273dad0)) -* **ui-icons:** fix ai info icon ([723ef9f](https://github.com/instructure/instructure-ui/commit/723ef9fb972fbcbf7627653698d2e47f8af49822)) -* **ui-link:** fix Link outline styles and overrides ([f23269e](https://github.com/instructure/instructure-ui/commit/f23269ed1139bb9833abbc894493a95bb80e27f4)) -* **ui-list:** align ordered list start position with unordered lists ([baed912](https://github.com/instructure/instructure-ui/commit/baed912a2f246797b5bcd58c40c64b26de6baaf2)) -* **ui-modal:** adjust scrollbar detection tolerance in ModalBody ([5ae1f42](https://github.com/instructure/instructure-ui/commit/5ae1f42f47102b87a8c7afabdd9ab51b331de96a)) -* **ui-selectable,ui-select:** fix typing of Select and Selectable event types and TypeScript errors in the examples ([bde40cc](https://github.com/instructure/instructure-ui/commit/bde40cc121674666cceb7eb24e116a50e1879445)) -* **ui-top-nav-bar:** fix aria-expanded added twice when displaying menus/popups ([b58a1bc](https://github.com/instructure/instructure-ui/commit/b58a1bc8daba596c987dc368d7772e53253c1d77)) - +- **ui-color-picker:** fix mixer button alignment and visual jump ([68c3e60](https://github.com/instructure/instructure-ui/commit/68c3e60acc5e1f410ed79a623a35fa3eaf7107f5)) +- **ui-color-picker:** fix popover scrolling when content exceeds viewport ([66f2b18](https://github.com/instructure/instructure-ui/commit/66f2b18af0dead1f62ee61629262c39c6273dad0)) +- **ui-icons:** fix ai info icon ([723ef9f](https://github.com/instructure/instructure-ui/commit/723ef9fb972fbcbf7627653698d2e47f8af49822)) +- **ui-link:** fix Link outline styles and overrides ([f23269e](https://github.com/instructure/instructure-ui/commit/f23269ed1139bb9833abbc894493a95bb80e27f4)) +- **ui-list:** align ordered list start position with unordered lists ([baed912](https://github.com/instructure/instructure-ui/commit/baed912a2f246797b5bcd58c40c64b26de6baaf2)) +- **ui-modal:** adjust scrollbar detection tolerance in ModalBody ([5ae1f42](https://github.com/instructure/instructure-ui/commit/5ae1f42f47102b87a8c7afabdd9ab51b331de96a)) +- **ui-selectable,ui-select:** fix typing of Select and Selectable event types and TypeScript errors in the examples ([bde40cc](https://github.com/instructure/instructure-ui/commit/bde40cc121674666cceb7eb24e116a50e1879445)) +- **ui-top-nav-bar:** fix aria-expanded added twice when displaying menus/popups ([b58a1bc](https://github.com/instructure/instructure-ui/commit/b58a1bc8daba596c987dc368d7772e53253c1d77)) ### Features -* **ui-tabs:** add tabIndex prop to the Panel for WCAG-compliant focus control (defaults to 0 for backward compatibility) ([9b2121f](https://github.com/instructure/instructure-ui/commit/9b2121f3e0f5fb0abf4ad12158f9972aa1acc5a4)) - - - - +- **ui-tabs:** add tabIndex prop to the Panel for WCAG-compliant focus control (defaults to 0 for backward compatibility) ([9b2121f](https://github.com/instructure/instructure-ui/commit/9b2121f3e0f5fb0abf4ad12158f9972aa1acc5a4)) # [11.2.0](https://github.com/instructure/instructure-ui/compare/v11.0.1...v11.2.0) (2025-11-06) diff --git a/CLAUDE.md b/CLAUDE.md index 1de5053982..0145b3f9a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,7 @@ pnpm run build:types # Build TypeScript declarations # Testing pnpm run test:vitest # Unit tests +pnpm run test:vitest ui-radio-input # Run tests for a single package pnpm run cy:component # Cypress component tests # Linting @@ -152,6 +153,7 @@ All components **MUST**: ```bash pnpm run test:vitest # Unit tests pnpm run cy:component # Cypress tests +pnpm run test:vitest ui-radio-input # Run tests for a single package # Visual regression tests (in regression-test directory) cd regression-test diff --git a/cypress/component/Alerts.cy.tsx b/cypress/component/Alerts.cy.tsx index 35e3821be7..99de022bc1 100644 --- a/cypress/component/Alerts.cy.tsx +++ b/cypress/component/Alerts.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Alert } from '@instructure/ui' +import { Alert } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/AppNav.cy.tsx b/cypress/component/AppNav.cy.tsx index e3fdb2fb86..2ed373f49c 100644 --- a/cypress/component/AppNav.cy.tsx +++ b/cypress/component/AppNav.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { AppNav } from '@instructure/ui' +import { AppNav } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/ColorIndicator.cy.tsx b/cypress/component/ColorIndicator.cy.tsx index 5082d67788..196e0891ad 100644 --- a/cypress/component/ColorIndicator.cy.tsx +++ b/cypress/component/ColorIndicator.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { ColorIndicator } from '@instructure/ui' +import { ColorIndicator } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/ColorMixer.cy.tsx b/cypress/component/ColorMixer.cy.tsx index f496b087c9..2f65662edc 100644 --- a/cypress/component/ColorMixer.cy.tsx +++ b/cypress/component/ColorMixer.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { ColorMixer } from '@instructure/ui' +import { ColorMixer } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/ColorPicker.cy.tsx b/cypress/component/ColorPicker.cy.tsx index 97555b699b..7f50071f4b 100644 --- a/cypress/component/ColorPicker.cy.tsx +++ b/cypress/component/ColorPicker.cy.tsx @@ -28,7 +28,7 @@ import { ColorPreset, ColorContrast, Button -} from '@instructure/ui' +} from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/ColorPreset.cy.tsx b/cypress/component/ColorPreset.cy.tsx index 79d3a7bfe2..298900e462 100644 --- a/cypress/component/ColorPreset.cy.tsx +++ b/cypress/component/ColorPreset.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { ColorPreset } from '@instructure/ui' +import { ColorPreset } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/DateInput.cy.tsx b/cypress/component/DateInput.cy.tsx index b16c73181b..7cec0a7244 100644 --- a/cypress/component/DateInput.cy.tsx +++ b/cypress/component/DateInput.cy.tsx @@ -25,7 +25,7 @@ import 'cypress-real-events' import '../support/component' -import { DateInput, Calendar } from '@instructure/ui' +import { DateInput, Calendar } from '@instructure/ui/latest' const weekdayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] diff --git a/cypress/component/DateInput2.cy.tsx b/cypress/component/DateInput2.cy.tsx index a7335082a7..622c3b23b6 100644 --- a/cypress/component/DateInput2.cy.tsx +++ b/cypress/component/DateInput2.cy.tsx @@ -25,7 +25,7 @@ import { useState } from 'react' import 'cypress-real-events' import '../support/component' -import { DateInput2, ApplyLocale } from '@instructure/ui' +import { DateInput2, ApplyLocale } from '@instructure/ui/latest' import { SinonSpy } from 'cypress/types/sinon' const TIMEZONES_DST = [ @@ -618,7 +618,10 @@ describe('', () => { cy.get('table').should('be.visible') - cy.contains('button', dayForSelect).should('be.enabled').click().wait(500) + cy.contains('button', dayForSelect) + .should('be.enabled') + .click() + .wait(500) cy.get('input') .invoke('val') diff --git a/cypress/component/DateTimeInput.cy.tsx b/cypress/component/DateTimeInput.cy.tsx index 73807f530a..b7cf45616c 100644 --- a/cypress/component/DateTimeInput.cy.tsx +++ b/cypress/component/DateTimeInput.cy.tsx @@ -25,7 +25,7 @@ import 'cypress-real-events' import '../support/component' -import { DateTimeInput } from '@instructure/ui' +import { DateTimeInput } from '@instructure/ui/latest' import { DateTime } from '@instructure/ui-i18n' describe('', () => { diff --git a/cypress/component/Dialog.cy.tsx b/cypress/component/Dialog.cy.tsx index eea47419ce..3878815dad 100644 --- a/cypress/component/Dialog.cy.tsx +++ b/cypress/component/Dialog.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import { useState, useRef } from 'react' -import { Dialog } from '@instructure/ui' +import { Dialog } from '@instructure/ui/latest' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/DrawerLayout.cy.tsx b/cypress/component/DrawerLayout.cy.tsx index 27956ec125..98c2f59514 100644 --- a/cypress/component/DrawerLayout.cy.tsx +++ b/cypress/component/DrawerLayout.cy.tsx @@ -26,7 +26,7 @@ import { expect } from 'chai' import '../support/component' import { px, within } from '@instructure/ui-utils' -import DrawerLayoutFixture from '@instructure/ui-drawer-layout/src/DrawerLayout/__fixtures__/DrawerLayout.fixture' +import DrawerLayoutFixture from '@instructure/ui-drawer-layout/src/DrawerLayout/v1/__fixtures__/DrawerLayout.fixture' describe('', () => { it('with no overlay, layout content should have margin equal to tray width with placement=start', () => { diff --git a/cypress/component/DrawerTray.cy.tsx b/cypress/component/DrawerTray.cy.tsx index b433e37d1b..4a81f89a99 100644 --- a/cypress/component/DrawerTray.cy.tsx +++ b/cypress/component/DrawerTray.cy.tsx @@ -27,7 +27,7 @@ import canvas from '@instructure/ui-themes' import { DrawerLayoutContext, DrawerTray -} from '@instructure/ui-drawer-layout/src/DrawerLayout' +} from '@instructure/ui-drawer-layout/src/DrawerLayout/v1' import { InstUISettingsProvider } from '@instructure/emotion' describe('', () => { diff --git a/cypress/component/Drilldown.cy.tsx b/cypress/component/Drilldown.cy.tsx index 092b94c3f0..f6164f8cd4 100644 --- a/cypress/component/Drilldown.cy.tsx +++ b/cypress/component/Drilldown.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { Drilldown } from '@instructure/ui' +import { Drilldown } from '@instructure/ui/latest' import '../support/component' const data = Array(5) diff --git a/cypress/component/DrilldownGroup.cy.tsx b/cypress/component/DrilldownGroup.cy.tsx index 5f11fd498d..18630aca9b 100644 --- a/cypress/component/DrilldownGroup.cy.tsx +++ b/cypress/component/DrilldownGroup.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { Drilldown } from '@instructure/ui' +import { Drilldown } from '@instructure/ui/latest' import '../support/component' function mountDrilldown(selectableType, defaultSelected) { diff --git a/cypress/component/DrilldownOption.cy.tsx b/cypress/component/DrilldownOption.cy.tsx index ecc107c46d..d48ebfe147 100644 --- a/cypress/component/DrilldownOption.cy.tsx +++ b/cypress/component/DrilldownOption.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { Drilldown } from '@instructure/ui' +import { Drilldown } from '@instructure/ui/latest' import '../support/component' describe('', () => { diff --git a/cypress/component/DrilldownPage.cy.tsx b/cypress/component/DrilldownPage.cy.tsx index 06729cbbcf..3c0c759577 100644 --- a/cypress/component/DrilldownPage.cy.tsx +++ b/cypress/component/DrilldownPage.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { Drilldown } from '@instructure/ui' +import { Drilldown } from '@instructure/ui/latest' import '../support/component' describe('', () => { diff --git a/cypress/component/DrilldownSeparator.cy.tsx b/cypress/component/DrilldownSeparator.cy.tsx index ecd9cbcc27..34e1fa07b3 100644 --- a/cypress/component/DrilldownSeparator.cy.tsx +++ b/cypress/component/DrilldownSeparator.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { Drilldown } from '@instructure/ui' +import { Drilldown } from '@instructure/ui/latest' import '../support/component' describe('', () => { diff --git a/cypress/component/FileDrop.cy.tsx b/cypress/component/FileDrop.cy.tsx index 19905ba2d4..402cd60130 100644 --- a/cypress/component/FileDrop.cy.tsx +++ b/cypress/component/FileDrop.cy.tsx @@ -26,7 +26,7 @@ import { expect } from 'chai' import 'cypress-real-events' import '../support/component' -import { FileDrop } from '@instructure/ui' +import { FileDrop } from '@instructure/ui/latest' describe('', () => { describe('file-type checking when onDrop', () => { diff --git a/cypress/component/FlexItem.cy.tsx b/cypress/component/FlexItem.cy.tsx index 8e71498ff2..c06ff60acf 100644 --- a/cypress/component/FlexItem.cy.tsx +++ b/cypress/component/FlexItem.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Flex } from '@instructure/ui' +import { Flex } from '@instructure/ui/latest' describe('', () => { it('visually reorders items when order prop is set', () => { diff --git a/cypress/component/Focusable.cy.tsx b/cypress/component/Focusable.cy.tsx index c405c5e0ca..6d02674af8 100644 --- a/cypress/component/Focusable.cy.tsx +++ b/cypress/component/Focusable.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Focusable } from '@instructure/ui' +import { Focusable } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/FormField.cy.tsx b/cypress/component/FormField.cy.tsx index 5312e64439..aa4832c003 100644 --- a/cypress/component/FormField.cy.tsx +++ b/cypress/component/FormField.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { FormFieldLayout } from '@instructure/ui' +import { FormFieldLayout } from '@instructure/ui/latest' import '../support/component' diff --git a/cypress/component/Img.cy.tsx b/cypress/component/Img.cy.tsx index 2e9e15de14..978b5909e4 100644 --- a/cypress/component/Img.cy.tsx +++ b/cypress/component/Img.cy.tsx @@ -24,7 +24,7 @@ import '../support/component' import 'cypress-real-events' -import { Img } from '@instructure/ui' +import { Img } from '@instructure/ui/latest' const HEIGHT = 32 const WIDTH = 24 diff --git a/cypress/component/InlineSVG.cy.tsx b/cypress/component/InlineSVG.cy.tsx index 9ec17bf69a..61e7bc141a 100644 --- a/cypress/component/InlineSVG.cy.tsx +++ b/cypress/component/InlineSVG.cy.tsx @@ -24,7 +24,7 @@ import '../support/component' import 'cypress-real-events' -import { InlineSVG } from '@instructure/ui' +import { InlineSVG } from '@instructure/ui/latest' import { expect } from 'chai' const WIDTH = '100px' diff --git a/cypress/component/ListItem.cy.tsx b/cypress/component/ListItem.cy.tsx index 2abd7cfc8b..7753943c4c 100644 --- a/cypress/component/ListItem.cy.tsx +++ b/cypress/component/ListItem.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' -import { ListItem } from '@instructure/ui' +import { ListItem } from '@instructure/ui/latest' import '../support/component' describe('', () => { diff --git a/cypress/component/Menu.cy.tsx b/cypress/component/Menu.cy.tsx index 0633289dc9..b19bad234e 100644 --- a/cypress/component/Menu.cy.tsx +++ b/cypress/component/Menu.cy.tsx @@ -24,7 +24,7 @@ import 'cypress-real-events' import '../support/component' -import { Menu, MenuItem } from '@instructure/ui' +import { Menu, MenuItem } from '@instructure/ui/latest' describe('', () => { it('should move focus properly', () => { @@ -270,7 +270,8 @@ describe('', () => { .should('be.focused') }) - it(`should show and focus flyout menu on space keyDown`, () => { + // This test is failing randomly + it.skip(`should show and focus flyout menu on space keyDown`, () => { cy.mount( diff --git a/cypress/component/MenuItem.cy.tsx b/cypress/component/MenuItem.cy.tsx index b8ae8f31d2..ba6668109b 100644 --- a/cypress/component/MenuItem.cy.tsx +++ b/cypress/component/MenuItem.cy.tsx @@ -26,7 +26,7 @@ import { expect } from 'chai' import 'cypress-real-events' import '../support/component' -import { MenuItem } from '@instructure/ui' +import { MenuItem } from '@instructure/ui/latest' describe('', () => { it('should call onSelect after SPACE key is pressed', () => { diff --git a/cypress/component/MenuItemGroup.cy.tsx b/cypress/component/MenuItemGroup.cy.tsx index c6fdfa1c8e..7d8f52c5d9 100644 --- a/cypress/component/MenuItemGroup.cy.tsx +++ b/cypress/component/MenuItemGroup.cy.tsx @@ -26,7 +26,11 @@ import { expect } from 'chai' import 'cypress-real-events' import '../support/component' -import { MenuItem, MenuItemGroup, MenuItemSeparator } from '@instructure/ui' +import { + MenuItem, + MenuItemGroup, + MenuItemSeparator +} from '@instructure/ui/latest' describe('', () => { it('updates the selected items when allowMultiple is true', () => { diff --git a/cypress/component/Modal-test-1.cy.tsx b/cypress/component/Modal-test-1.cy.tsx index 1c029aac78..e448e5f97b 100644 --- a/cypress/component/Modal-test-1.cy.tsx +++ b/cypress/component/Modal-test-1.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import { useState } from 'react' -import { Tooltip, Modal, Button, CloseButton } from '@instructure/ui' +import { Tooltip, Modal, Button, CloseButton } from '@instructure/ui/latest' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/Modal-test-2.cy.tsx b/cypress/component/Modal-test-2.cy.tsx index 48748bab06..a78d7a614a 100644 --- a/cypress/component/Modal-test-2.cy.tsx +++ b/cypress/component/Modal-test-2.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import { useState } from 'react' -import { Modal, View } from '@instructure/ui' +import { Modal, View } from '@instructure/ui/latest' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/NumberInput.cy.tsx b/cypress/component/NumberInput.cy.tsx index 45d769bdb9..4f6fbcf3d6 100644 --- a/cypress/component/NumberInput.cy.tsx +++ b/cypress/component/NumberInput.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { NumberInput } from '@instructure/ui' +import { NumberInput } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/OptionsItem.cy.tsx b/cypress/component/OptionsItem.cy.tsx index 3250abef25..2d6cd9ff91 100644 --- a/cypress/component/OptionsItem.cy.tsx +++ b/cypress/component/OptionsItem.cy.tsx @@ -25,7 +25,7 @@ import '../support/component' import 'cypress-real-events' import { expect } from 'chai' -import { Options } from '@instructure/ui' +import { Options } from '@instructure/ui/latest' import { IconCheckSolid } from '@instructure/ui-icons' describe('', () => { diff --git a/cypress/component/Page.cy.tsx b/cypress/component/Page.cy.tsx index 13eb1a8ff5..a272c7aa33 100644 --- a/cypress/component/Page.cy.tsx +++ b/cypress/component/Page.cy.tsx @@ -22,8 +22,7 @@ * SOFTWARE. */ import { useState } from 'react' -import { Pages, PagesPage } from '@instructure/ui-pages' - +import { Pages, PagesPage } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Pagination.cy.tsx b/cypress/component/Pagination.cy.tsx index d83f497ca3..dbb8da3b3c 100644 --- a/cypress/component/Pagination.cy.tsx +++ b/cypress/component/Pagination.cy.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import { Pagination, ScreenReaderContent } from '@instructure/ui' +import { Pagination, ScreenReaderContent } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' @@ -99,7 +99,7 @@ describe('', () => { }) }) - cy.viewport(300, 800) + cy.viewport(100, 800) cy.get('[role="navigation"]').within(() => { cy.get('button').then(($items) => { diff --git a/cypress/component/PaginationArrowButton.cy.tsx b/cypress/component/PaginationArrowButton.cy.tsx index ed83892a8e..322d4ce535 100644 --- a/cypress/component/PaginationArrowButton.cy.tsx +++ b/cypress/component/PaginationArrowButton.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Pagination } from '@instructure/ui' +import { Pagination } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/PaginationPageInput.cy.tsx b/cypress/component/PaginationPageInput.cy.tsx index d6f9237ad1..e5bef7525b 100644 --- a/cypress/component/PaginationPageInput.cy.tsx +++ b/cypress/component/PaginationPageInput.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Pagination } from '@instructure/ui' +import { Pagination } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Pill.cy.tsx b/cypress/component/Pill.cy.tsx index 5cffd5dadf..c548fb3b4a 100644 --- a/cypress/component/Pill.cy.tsx +++ b/cypress/component/Pill.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Pill } from '@instructure/ui' +import { Pill } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Popover.cy.tsx b/cypress/component/Popover.cy.tsx index 97128f9979..ab9e773b11 100644 --- a/cypress/component/Popover.cy.tsx +++ b/cypress/component/Popover.cy.tsx @@ -29,7 +29,7 @@ import { CloseButton, FormFieldGroup, TextInput -} from '@instructure/ui' +} from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Position.cy.tsx b/cypress/component/Position.cy.tsx index 1017d1bdfa..8ac562cf8e 100644 --- a/cypress/component/Position.cy.tsx +++ b/cypress/component/Position.cy.tsx @@ -25,7 +25,7 @@ import 'cypress-real-events' import '../support/component' -import { Position } from '@instructure/ui' +import { Position } from '@instructure/ui/latest' import { within } from '@instructure/ui-utils' const parentDefaults = { diff --git a/cypress/component/RangeInput.cy.tsx b/cypress/component/RangeInput.cy.tsx index dca91d4150..2807dfbb45 100644 --- a/cypress/component/RangeInput.cy.tsx +++ b/cypress/component/RangeInput.cy.tsx @@ -23,7 +23,7 @@ */ import { expect } from 'chai' -import { RangeInput } from '@instructure/ui' +import { RangeInput } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Responsive.cy.tsx b/cypress/component/Responsive.cy.tsx index 6f02d90b55..343d52d560 100644 --- a/cypress/component/Responsive.cy.tsx +++ b/cypress/component/Responsive.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import React from 'react' -import { Responsive } from '@instructure/ui' +import { Responsive } from '@instructure/ui/latest' import { deepEqual } from '@instructure/ui-utils' import '../support/component' diff --git a/cypress/component/Select.cy.tsx b/cypress/component/Select.cy.tsx index 089b676e0b..4696562390 100644 --- a/cypress/component/Select.cy.tsx +++ b/cypress/component/Select.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Select } from '@instructure/ui' +import { Select } from '@instructure/ui/latest' import { IconCheckSolid, IconEyeSolid } from '@instructure/ui-icons' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/Selectable.cy.tsx b/cypress/component/Selectable.cy.tsx index ff5699b5f7..03d11b7546 100644 --- a/cypress/component/Selectable.cy.tsx +++ b/cypress/component/Selectable.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Selectable } from '@instructure/ui' +import { Selectable } from '@instructure/ui/latest' import { expect } from 'chai' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/SideNavBarItem.cy.tsx b/cypress/component/SideNavBarItem.cy.tsx index 811a5178c1..3d10361b84 100644 --- a/cypress/component/SideNavBarItem.cy.tsx +++ b/cypress/component/SideNavBarItem.cy.tsx @@ -24,8 +24,8 @@ import 'cypress-real-events' -import { SideNavBarItem } from '@instructure/ui-side-nav-bar' -import { IconAdminLine } from '@instructure/ui-icons' +import { SideNavBarItem } from '@instructure/ui/latest' +import { IconAdminLine } from '@instructure/ui/latest' import '../support/component' describe('', () => { diff --git a/cypress/component/SimpleSelect.cy.tsx b/cypress/component/SimpleSelect.cy.tsx index 10e5933afd..081e5854f9 100644 --- a/cypress/component/SimpleSelect.cy.tsx +++ b/cypress/component/SimpleSelect.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { SimpleSelect } from '@instructure/ui' +import { SimpleSelect } from '@instructure/ui/latest' import 'cypress-real-events' import '../support/component' diff --git a/cypress/component/SourceCodeEditor.cy.tsx b/cypress/component/SourceCodeEditor.cy.tsx index 338dd175b8..8966d0c06c 100644 --- a/cypress/component/SourceCodeEditor.cy.tsx +++ b/cypress/component/SourceCodeEditor.cy.tsx @@ -23,7 +23,7 @@ */ import React from 'react' import { expect } from 'chai' -import { SourceCodeEditor } from '@instructure/ui' +import { SourceCodeEditor } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/Table.cy.tsx b/cypress/component/Table.cy.tsx index 38f4f7c191..72807ae638 100644 --- a/cypress/component/Table.cy.tsx +++ b/cypress/component/Table.cy.tsx @@ -25,7 +25,7 @@ import '../support/component' import 'cypress-real-events' -import { Table } from '@instructure/ui' +import { Table } from '@instructure/ui/latest' describe('', () => { const TestTable = ({ diff --git a/cypress/component/Tabs.cy.tsx b/cypress/component/Tabs.cy.tsx index c97c18e004..8c359bfbdf 100644 --- a/cypress/component/Tabs.cy.tsx +++ b/cypress/component/Tabs.cy.tsx @@ -23,7 +23,7 @@ */ import React from 'react' -import { Tabs } from '@instructure/ui' +import { Tabs } from '@instructure/ui/latest' import { expect } from 'chai' import '../support/component' diff --git a/cypress/component/TextArea.cy.tsx b/cypress/component/TextArea.cy.tsx index 6409d9ff9f..f8656de019 100644 --- a/cypress/component/TextArea.cy.tsx +++ b/cypress/component/TextArea.cy.tsx @@ -25,7 +25,7 @@ import '../support/component' import 'cypress-real-events' -import { TextArea } from '@instructure/ui' +import { TextArea } from '@instructure/ui/latest' it('should resize if autoGrow is true', () => { cy.mount( diff --git a/cypress/component/TextInput.cy.tsx b/cypress/component/TextInput.cy.tsx index 5870dcb1a0..1525cd72ce 100644 --- a/cypress/component/TextInput.cy.tsx +++ b/cypress/component/TextInput.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import { useState } from 'react' -import { TextInput } from '@instructure/ui' +import { TextInput } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/component/TimeSelect.cy.tsx b/cypress/component/TimeSelect.cy.tsx index c93b5e0bdb..4a4151083c 100644 --- a/cypress/component/TimeSelect.cy.tsx +++ b/cypress/component/TimeSelect.cy.tsx @@ -26,7 +26,7 @@ import moment from 'moment-timezone' import 'cypress-real-events' import '../support/component' -import { TimeSelect } from '@instructure/ui' +import { TimeSelect } from '@instructure/ui/latest' import { DateTime } from '@instructure/ui-i18n' describe('', () => { diff --git a/cypress/component/ToggleButton.cy.tsx b/cypress/component/ToggleButton.cy.tsx index 19cf3b96e7..f4efc25cec 100644 --- a/cypress/component/ToggleButton.cy.tsx +++ b/cypress/component/ToggleButton.cy.tsx @@ -25,7 +25,7 @@ import 'cypress-real-events' import '../support/component' -import { ToggleButton } from '@instructure/ui' +import { ToggleButton } from '@instructure/ui/latest' describe('', () => { const icon = ( diff --git a/cypress/component/Tooltip.cy.tsx b/cypress/component/Tooltip.cy.tsx index 1d400d0011..17c822af7c 100644 --- a/cypress/component/Tooltip.cy.tsx +++ b/cypress/component/Tooltip.cy.tsx @@ -24,7 +24,7 @@ import { useState } from 'react' import 'cypress-real-events' -import { Modal, Tooltip, Button } from '@instructure/ui' +import { Modal, Tooltip, Button } from '@instructure/ui/latest' import { IconInfoLine } from '@instructure/ui-icons' import '../support/component' @@ -245,11 +245,12 @@ describe('', () => { cy.get(tooltip).should('not.be.visible') - cy.get('[data-testid="trigger"]') - .realHover() - .then(() => { - cy.get(tooltip).should('be.visible') - }) + cy.get('[data-testid="trigger"]').realHover() + + // Verify tooltip is rendered and accessible (avoid Cypress's "covered by" check) + cy.get(tooltip).should('exist') + cy.get(tooltip).should('have.css', 'display', 'block') + cy.contains("Hello. I'm a tool tip").should('exist') cy.get(tooltip) .realPress('Escape') diff --git a/cypress/component/Tray.cy.tsx b/cypress/component/Tray.cy.tsx index 6697b3764b..277cd855c3 100644 --- a/cypress/component/Tray.cy.tsx +++ b/cypress/component/Tray.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { Button, Overlay, Text, Tray, View } from '@instructure/ui' +import { Button, Overlay, Text, Tray, View } from '@instructure/ui/latest' import React from 'react' import '../support/component' diff --git a/cypress/component/TreeBrowser.cy.tsx b/cypress/component/TreeBrowser.cy.tsx index bdcc468a16..b0ab109ba5 100644 --- a/cypress/component/TreeBrowser.cy.tsx +++ b/cypress/component/TreeBrowser.cy.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import { TreeBrowser, TreeNode } from '@instructure/ui' +import { TreeBrowser, TreeNode } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' @@ -244,7 +244,7 @@ describe('', () => { .focus() .should('be.focused') .realType('{leftarrow}') - + cy.contains('[role="treeitem"]', 'Root Directory').should('be.focused') cy.get('[role="treeitem"]').should('have.length', 1) }) diff --git a/cypress/component/Truncate.cy.tsx b/cypress/component/Truncate.cy.tsx index fda11a1733..9855a0692e 100644 --- a/cypress/component/Truncate.cy.tsx +++ b/cypress/component/Truncate.cy.tsx @@ -26,7 +26,7 @@ import { expect } from 'chai' import '../support/component' import { within } from '@instructure/ui-utils' -import truncate from '@instructure/ui-truncate-text/src/TruncateText/utils/truncate' +import truncate from '@instructure/ui-truncate-text/src/TruncateText/v1/utils/truncate' describe('truncate', () => { const defaultText = 'Hello world! This is a long string that should truncate' diff --git a/cypress/component/TruncateList.cy.tsx b/cypress/component/TruncateList.cy.tsx index 2f2942d4ce..b0bb7b42ce 100644 --- a/cypress/component/TruncateList.cy.tsx +++ b/cypress/component/TruncateList.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ import { useState } from 'react' -import { TruncateList } from '@instructure/ui' +import { TruncateList } from '@instructure/ui/latest' import { expect } from 'chai' import '../support/component' diff --git a/cypress/component/TruncateText.cy.tsx b/cypress/component/TruncateText.cy.tsx index 32ab433784..11346a1a99 100644 --- a/cypress/component/TruncateText.cy.tsx +++ b/cypress/component/TruncateText.cy.tsx @@ -22,7 +22,7 @@ * SOFTWARE. */ -import { TruncateText, Text } from '@instructure/ui' +import { TruncateText, Text } from '@instructure/ui/latest' import { expect } from 'chai' import '../support/component' diff --git a/cypress/component/View.cy.tsx b/cypress/component/View.cy.tsx index d22dcc26f6..5c0101e199 100644 --- a/cypress/component/View.cy.tsx +++ b/cypress/component/View.cy.tsx @@ -23,7 +23,7 @@ */ import '../support/component' -import { View } from '@instructure/ui' +import { View } from '@instructure/ui/latest' describe('', () => { it('should not pass all styles', () => { diff --git a/cypress/component/i18n.cy.tsx b/cypress/component/i18n.cy.tsx index dbabe3b37f..621bfa67a7 100644 --- a/cypress/component/i18n.cy.tsx +++ b/cypress/component/i18n.cy.tsx @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import { getTextDirection } from '@instructure/ui' +import { getTextDirection } from '@instructure/ui/latest' import '../support/component' import 'cypress-real-events' diff --git a/cypress/webpack.config.cjs b/cypress/webpack.config.cjs index 85cb3d2f39..7c78f70490 100644 --- a/cypress/webpack.config.cjs +++ b/cypress/webpack.config.cjs @@ -40,8 +40,30 @@ function getWorkspaceAliases() { const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')) const pkgName = pkgJson.name if (pkgName && pkgName.startsWith('@instructure/')) { - // Alias package name to its source directory - aliases[pkgName] = pkgPath + // Alias subpath exports (e.g., './latest', './v11_6') to source files. + // This is needed because the base alias below overrides package.json + // exports resolution, so we must handle subpaths explicitly. + if (pkgJson.exports) { + Object.entries(pkgJson.exports).forEach(([subpath, target]) => { + if (subpath.includes('*')) return + const importPath = (typeof target === 'object' ? target.import : target) || '' + // Map built output path (./es/X.js) back to source (./src/X.ts) + const srcPath = importPath.replace(/^\.\/es\//, './src/').replace(/\.js$/, '.ts') + const resolved = path.join(pkgPath, srcPath) + if (fs.existsSync(resolved)) { + if (subpath === '.') { + // Root export: alias the package name directly to the source entry + aliases[`${pkgName}$`] = resolved + } else { + aliases[`${pkgName}/${subpath.replace(/^\.\//, '')}`] = resolved + } + } + }) + } + // Fallback: alias package name to its directory (for non-exports packages) + if (!aliases[`${pkgName}$`]) { + aliases[pkgName] = pkgPath + } // Also alias deep imports (e.g., '@instructure/ui-button/src/...') aliases[`${pkgName}/src`] = path.join(pkgPath, 'src') } diff --git a/docs/contributor-docs/adding-icons.md b/docs/contributor-docs/adding-icons.md index a0ec2a0791..7c5c85b4d6 100644 --- a/docs/contributor-docs/adding-icons.md +++ b/docs/contributor-docs/adding-icons.md @@ -4,92 +4,52 @@ category: Contributor Guides order: 9 --- -## Adding and Modifying Icons +## Lucide Icons -- Use dashes in the name of the .svg files (e.g `calendar-month`). -- Use the same name for the "Line" and "Solid" variants, and save them in the respective folder, e.g. `instructure-ui/packages/ui-icons/svg/Line/calendar-month.svg` and `instructure-ui/packages/ui-icons/svg/Solid/calendar-month.svg`. -- Double-check that the SVG size is 1920x1920. +The bulk of the icon set comes from [Lucide](https://lucide.dev). These are not manually maintained +β€” they are automatically picked up from the `lucide-react` npm package at build time. -```js ---- -type: code ---- - - {...} - -``` - -- The files cannot contain [clipping paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)! Sadly, when the Designers export icons from Figma, most of the time they have a clipping path around the whole canvas. When an SVG includes clipping paths, the `Icon Font` variant may not render correctly. Specifically, the use of `` and `` elements can cause rendering issues. If the source code has them, manually refactor the code, e.g: - -```js ---- -type: code ---- +**To get a new or updated Lucide icon**, bump the `lucide-react` version in +`packages/ui-icons/package.json`. The index is regenerated automatically as part of +`pnpm run bootstrap` (via `build-icons`), so no manual step is needed. + +Every icon exported by `lucide-react` becomes `InstUIIcon` in `@instructure/ui-icons`, +**except** for icons that are shadowed by a custom icon of the same name (see below). + +If a Lucide icon is missing or looks wrong, check whether it exists in the installed version of +`lucide-react` first β€” if not, the only path is to add it as a custom icon. -// Before: - - - - - - - - - - +## Adding Custom Icons -// After: - - - -``` +Custom icons live in `packages/ui-icons/svg/Custom/` and are consumed directly by the build script (`ui-scripts/lib/generate-custom-index.ts`). -- If the icon has to be bidirectional (being mirrored in RTL mode, typically arrow icons), add the icon name to the bidirectional list in `packages/ui-icons/icons.config.js`. Deprecated icons are handled here as well. +- Use kebab-case filenames ending in `.svg`. The filename becomes the React export name: `ai-info.svg` β†’ `AiInfoInstUIIcon`, `canvas-logo.svg` β†’ `CanvasLogoInstUIIcon`. -- Run `pnpm run bootstrap`. +- For solid/filled icons, the filename must end in `-solid.svg` (e.g. `bell-solid.svg`). -- Finally, run `pnpm run dev` and verify that the icons are displayed correctly under [Icons](/#icons). Check all 3 versions (React, SVG and icon font). +- If a custom icon has the same name as a Lucide icon (e.g. `message-square-check.svg`), the custom version takes precedence and the Lucide one is hidden from the package. -(Note: The fonts are sometimes not rendered correctly, but we decided not to fix them, because they are not really used anywhere, and we might stop supporting icon fonts in the future in general.) +- After dropping the file into `svg/Custom/`, the index is regenerated automatically as part of + `pnpm run bootstrap` (via `build-icons`). -### Guidelines for Drawing Icons +- Run `pnpm run dev` and verify the icon looks correct in the Icons gallery. -- Draw your icons on the 1920 x 1920 art-boards. +### Drawing Guidelines -- Before you flatten shapes or vectorize strokes as described below, make a hidden copy of the original paths off - to the side so that you can more easily come back and make changes later. +- Uncheck "Clip content" on the frame before exporting. Otherwise Figma wraps every layer in `` and adds a `…` block, which can cause rendering issues -- Flatten your shapes. +- Use `currentColor` for all path fills and strokes. The build script reads the SVG as-is β€” no color replacement happens. If you exported with a hardcoded hex value, replace it manually before regenerating the index -- Export strokes to vector. +- Stroke icons: set `fill="none"` on every path, not just on the root ``. Select all shape layers and set Fill to None in the Design panel -- Don’t use borders on vectors, especially not inside/outside borders which aren’t supported in SVG. Do not use clipping paths. +- Remove `width` and `height` from the `` root β€” keep only `viewBox` and `xmlns`. Export at 1Γ— in Figma -- Make sure none of the paths go outside of the art-board. If so, the glyph in the icon font will be misaligned. - Draw inside the lines. +- Flatten all transforms before exporting (Object β†’ Flatten Selection) -- Fill the space edge-to-edge as much as possible. The build process will add margins as needed. +- Standard icons use `viewBox="0 0 24 24"`. Brand/logo icons can use any square viewBox (e.g. `0 0 1920 1920`) -- Don't use inline styles +- Mixed stroke + fill icons are supported. Paths with `fill="currentColor"` render filled; paths with `fill="none"` and a `stroke` render as outlines -- Don't use `class` or `for` attributes +- Do not use per-element `stroke-width`. The wrapper applies a uniform stroke width derived from the icon size -- Always have `` as the root tag +- Do not use `` or `` elements. These are not supported β€” flatten or redesign the layer diff --git a/docs/contributor-docs/docs-versioning.md b/docs/contributor-docs/docs-versioning.md new file mode 100644 index 0000000000..d3602540cc --- /dev/null +++ b/docs/contributor-docs/docs-versioning.md @@ -0,0 +1,60 @@ +--- +title: Docs Versioning +category: Contributor Guides +order: 7 +--- + +# Multi-Version Documentation + +The docs site supports showing documentation for multiple library minor versions (e.g. v11.5, v11.6). This allows users to switch between versions in the UI and see the correct component implementations and documentation for each. + +## How it works + +### Build pipeline + +1. `buildScripts/utils/buildVersionMap.mts` scans package `exports` fields to discover which library versions exist (e.g. `v11_5`, `v11_6`). +2. `buildScripts/build-docs.mts` processes all source/markdown files once, then filters them per library version using the version map. Each version gets its own output directory (`__build__/docs/v11_5/`, `__build__/docs/v11_6/`). +3. A `docs-versions.json` manifest is written with `libraryVersions` and `defaultVersion`. + +### Runtime (client) + +Versioning is driven by URL-path-based routing. Each page URL includes the version prefix (e.g. `/v11_7/PageName`). + +1. On load, the App fetches `docs-versions.json` to discover available minor versions and the default. +2. The Header renders a minor version dropdown when multiple versions exist. +3. When the user switches versions: + - `updateGlobalsForVersion(version)` in `globals.ts` re-populates the global scope with the correct component implementations (so interactive code examples use the right version). + - The App re-fetches `docs/{version}/markdown-and-sources-data.json` for the documentation data. +4. `getComponentsForVersion(version)` in `versioned-components.ts` returns the full component map for the selected version. + +### Versioned components + +`packages/__docs__/versioned-components.ts` is a **hand-maintained** file that maps each library version to its component set. It imports from the `@instructure/ui/v11_X` subpath exports and merges them with docs-only components (Guidelines, Figure, ToggleBlockquote, V12ChangelogTable). + +When adding a new version, add the import and a new entry in the `versions` record. + +## Adding a new library version (e.g. v11_23) + +1. **Ensure packages export the new subpath.** Each package that has version-specific code must have a `/v11_23` export in its `package.json` `exports` field. +2. **Update `versioned-components.ts`** β€” add the new `@instructure/ui/v11_23` import and a new entry in the `versions` map. +3. **Run `pnpm run dev` or `pnpm run build:docs`** β€” the version map is rebuilt automatically during the docs build. + +## Introducing a breaking change to a component + +If a component (e.g. `Checkbox`) needs a breaking change for v11_23: + +1. Add the new implementation under the package's versioned directory (e.g. `packages/ui-checkbox/src/Checkbox/v2/`, or `v3/` if `v2/` already exists). +2. Update the package's `exports` so `/v11_23` re-exports the new implementation. +3. Update `versioned-components.ts` if a new version entry is needed. +4. Run `pnpm run dev` to verify the docs build picks up the new version. + +## Key files + +| File | Purpose | +|------|---------| +| `packages/__docs__/versioned-components.ts` | Hand-maintained version β†’ component map + `getComponentsForVersion()` | +| `packages/__docs__/globals.ts` | Populates global scope for interactive examples | +| `packages/__docs__/src/App/index.tsx` | Docs app β€” handles version switching | +| `packages/__docs__/src/versionData.ts` | Fetches version manifest at runtime | +| `packages/__docs__/buildScripts/build-docs.mts` | Build pipeline β€” generates per-version JSON | +| `packages/__docs__/buildScripts/utils/buildVersionMap.mts` | Discovers versions from package exports + shared filtering utilities (`isDocIncludedInVersion`, `getPackageShortName`) | diff --git a/docs/contributor-docs/migrating-to-new-tokens.md b/docs/contributor-docs/migrating-to-new-tokens.md new file mode 100644 index 0000000000..7e295e7bae --- /dev/null +++ b/docs/contributor-docs/migrating-to-new-tokens.md @@ -0,0 +1,35 @@ +--- +title: Migrating components to new theming system +category: Contributor Guides +order: 01 +--- + +# Token migration + +The new token system consists of auto-generated tokens form design. They can be imported from `ui-themes`. + +## Changing tokens only + +The migration strategy with the least amount of effort is only changing tokens. This approach keeps the component as class-based and retains the `View` component. + +Changes needed: + +- Import token types from `@instructure/ui-themes` instead of `@instructure/shared-types` +- Update `generateStyle` function to use `NewComponentTypes['ComponentName']` for the theme parameter +- Replace old theme tokens with new token names from the design system +- Replace `@withStyleLegacy` decorator with `@withStyle` and remove `generateComponentTheme` +- delete `theme.ts` + +If tokens are from a different (usually parent) components, add the `componentID` of that component as second paramater of `@withStyle` and use that name in the `generateStyle` function in `style.ts`: `NewComponentTypes['ParentComponentNameWithTheTokens']` + +`generateStyle` accepts a third parameter as well, which are the `sharedTokens`. These provide tokens for shared behaviors such as focus rings, shadows or margins. `'@instructure/emotion'` has various util functions that uses these, such as `calcSpacingFromShorthand` and `calcFocusOutlineStyles`. + +## Removing View + +For some components it makes sense to remove the `View` component underneath the component structure. Most of the time, `View` only provides margins, focus rings, or minor visual aid. These can be replicated - in most cases - by the `sharedTokens` and their utils. + +Ideally all occurrences of `View` would be eliminated from the codebase. + +## Transforming class based components to functional + +The ultimate goal is to migrate all components to functional based ones. Currently it's not a priority and detailed migration guides will be available later. For the time being, `Avatar` or `RadioInput` can be used as starting reference points. diff --git a/docs/guides/form-errors.md b/docs/guides/form-errors.md index 250c63a685..e11b1e3010 100644 --- a/docs/guides/form-errors.md +++ b/docs/guides/form-errors.md @@ -15,7 +15,6 @@ type: code --- type FormMessages = { type: - | 'newError' | 'error' | 'hint' | 'success' @@ -33,7 +32,7 @@ type: example const PasswordExample = () => { const [password, setPassword] = useState('') const messages = password.length < 6 - ? [{type: 'newError', text: 'Password have to be at least 6 characters long!'}] + ? [{type: 'error', text: 'Password have to be at least 6 characters long!'}] : [] return ( { render() ``` -However you might have noticed from the type definition that a message can be `error` and `newError` type. This is due to compatibility reasons. `error` is the older type and does not meet accessibility requirements, `newError` (hance the name) is the newer and more accessible format. +The `error` type has been updated to meet accessibility requirements with proper icons and visual styling. Previously, there was a `newError` type that provided this enhanced behavior, but it has been consolidated into the standard `error` type for consistency. `newError` has been deprecated. -We wanted to allow users to start using the new format without making it mandatory, but after the introductory period `newError` will be deprecated and `error` type will be changed to look and behave the same way. - -With this update we also introduced the "required asterisk" which will display an `*` character next to field labels that are required. This update is not opt-in and will apply to **all** InstUI form components so if you were relying on a custom solution for this feature before, you need to remove that to avoid having double asterisks. +We also introduced the "required asterisk" which displays an `*` character next to field labels that are required. This update applies to **all** InstUI form components, so if you were relying on a custom solution for this feature before, you need to remove that to avoid having double asterisks. Here are examples with different form components: @@ -62,17 +59,15 @@ type: example --- const Example = () => { const [showError, setShowError] = useState(true) - const [showNewError, setShowNewError] = useState(true) const [showLongError, setShowLongError] = useState(false) const [isRequired, setIsRequired] = useState(true) const messages = showError - ? [{type: showNewError ? 'newError' : 'error', text: showLongError ? 'Long error. Lorem ipsum dolor sit amet consectetur adipisicing elit. Dignissimos voluptas, esse commodi eos facilis voluptatibus harum exercitationem. Et magni est consectetur, eveniet veniam unde! Molestiae labore libero sapiente ad ratione.' : 'Short error message'}] + ? [{type: 'error', text: showLongError ? 'Long error. Lorem ipsum dolor sit amet consectetur adipisicing elit. Dignissimos voluptas, esse commodi eos facilis voluptatibus harum exercitationem. Et magni est consectetur, eveniet veniam unde! Molestiae labore libero sapiente ad ratione.' : 'Short error message'}] : [] const handleSettingsChange = (v) => { setShowError(v.includes('showError')) - setShowNewError(v.includes('showNewError')) setShowLongError(v.includes('showLongError')) setIsRequired(v.includes('isRequired')) } @@ -83,10 +78,9 @@ const Example = () => { name="errorOptions" description="Error message options" onChange={handleSettingsChange} - defaultValue={['showError', 'showNewError', 'isRequired']} + defaultValue={['showError', 'isRequired']} > - diff --git a/docs/guides/upgrade-guide.md b/docs/guides/upgrade-guide.md index 1871478368..d0577101f9 100644 --- a/docs/guides/upgrade-guide.md +++ b/docs/guides/upgrade-guide.md @@ -1,86 +1,1303 @@ --- -title: Upgrade Guide for Version 11 +title: Upgrade Guide for Version 12 category: Guides order: 1 --- -# Upgrade Guide for Version 11 +# Upgrade Guide for Version 12 -## InstUI and React +## New theming system -> React 16 and 17 support was dropped with InstUI 11. Please upgrade to React 18 before upgrading to InstUI v11! +TODO add details -### React 19 +## New icons -InstUI v11 added support for React 19. But upgrading to React 19 might cause issues because InstUI needs to access the native DOM in some cases and React introduced a breaking change here by [removing `ReactDOM.findDOMNode()`](https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-reactdom-finddomnode). If you are upgrading to React 19, you will need to add `ref`s to some of your custom components that use InstUI utilities, see [this guide](accessing-the-dom). We suggest to check your code thoroughly for errors, especially places where you use your own components as some kind of popovers or its triggers (e.g. Menu, Popover, Tooltip, Drilldown,..). +InstUI has switched to a new icon set based on [Lucide](https://lucide.dev/icons/). We are still keeping some Instructure-specific icons, like product logos, also new custom icons are added. We have a codemod that will help you migrate your code to the new icon set. -If you are using React 18 you might just see error messages like (`Error: ${elName} doesn't have "ref" property.`), but your code should work the same as with InstUI v10. +### Lucide Icons Package +InstUI introduces new icons based on the [Lucide](https://lucide.dev/icons/) icon library, providing 1,900+ stroke-based icons with improved theming and RTL support. The icons are wrapped with `wrapLucideIcon` to integrate with InstUI's theming system while maintaining access to all native icon props. + +**Key differences from `SVGIcon`/`InlineSVG`:** + +| Property | Old API (SVGIcon) | New API | +| :-------------- | :---------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | +| **size** | `'x-small'` \| `'small'` \| `'medium'` \| `'large'` \| `'x-large'` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` \| `'2xl'` \| `number` (pixels) | +| **color** | Limited tokens: `'primary'` \| `'secondary'` \| `'success'` \| `'error'` \| `'warning'` \| etc. | 60+ theme tokens (`'baseColor'`, `'successColor'`, `'actionPrimaryBaseColor'`, etc.) or any CSS color | +| **children** | `React.ReactNode` | ❌ Removed | +| **focusable** | `boolean` | ❌ Removed | +| **description** | `string` (combined with title) | ❌ Removed (use `title` only) | +| **src** | `string` | ❌ Removed | + +The new icons automatically sync with theme changes, support all InstUI color tokens, and provide better TypeScript integration. All standard HTML and SVG attributes can be passed directly to Lucide icons and will be spread onto the nested SVG element. + +## Focus rings + +Focus rings are now styled in a central pseudo-component called `SharedTokens`. You can override here how it looks, for example: + +```js +--- +type: example +--- + + + +``` + +### Alert + +```js +--- +type: embed +--- + + +``` + +### Billboard + +```js +--- +type: embed +--- + + +``` + +### Breadcrumb + +```js +--- +type: embed +--- + + +``` + +### Checkbox + +`readOnly` checkboxes are now focusable, in line with WCAG accessibility requirements. Previously, `readOnly` checkboxes were treated the same as `disabled` and were excluded from the tab order. Clicking a `readOnly` checkbox still has no effect β€” neither `onClick` nor `onChange` will fire. + +#### Checkbox (simple variant) + +```js +--- +type: embed +--- + + +``` + +#### Checkbox (toggle variant) + +```js +--- +type: embed +--- + + +``` + +### ColorPicker + +```js +--- +type: embed +--- + + +``` + +### Flex + +Gap styling now uses `sharedTokens.legacySpacing`. + +```js +--- +type: embed +--- + + +``` + +### FormField + +#### FormFieldGroup + +`error` or `success` messages are no longer displayed when the component is `readOnly` or `disabled`. + +```js +--- +type: embed +--- + + +``` + +#### FormFieldLayout + +```js +--- +type: embed +--- + + +``` + +#### FormFieldMessage + +`newError` message type is now deprecated. Both `newError` and the original `error` type now behave identically (using the new implementation that was previously exclusive to `newError`). + +```js +--- +type: embed +--- + + +``` + +#### FormFieldMessages + +```js +--- +type: embed +--- + + +``` + +### Grid + +```js +--- +type: embed +--- + + +``` + +### Grid.Col + +```js +--- +type: embed +--- + + +``` + +### Grid.Row + +```js +--- +type: embed +--- + + +``` + +### Heading + +`color` prop has 2 new values: `primary-on` and `secondary-on`, these are used for colored surfaces. + +The default value of the `color` prop has changed from `'inherit'` to `'primary'`. If you need to preserve the previous behavior, explicitly set `color="inherit"`. + +```js +--- +type: embed +--- + + +``` + +### Link + +- `isWithinText` prop has been removed. + +#### New `size` prop + +A new `size` prop has been added to control the font size, line height, and icon size/gap. Available values are: + +- `'small'` +- `'medium'` (default) +- `'large'` + +#### Deprecated variant values + +The following variant values have been **deprecated** and will be removed in a future version (still supported but warn): + +- `'inline-small'` +- `'standalone-small'` + +```js +--- +type: embed +--- + + +``` + +### Menu + +Icons for checkbox and radio menu items are now positioned on the right side instead of the left. + +```js +--- +type: embed +--- + + +``` + +#### Menu.Item + +```js +--- +type: embed +--- + + +``` + +#### Menu.Group + +```js +--- +type: embed +--- + + +``` + +### Metric + +```js +--- +type: embed +--- + + +``` + +#### MetricGroup + +```js +--- +type: embed +--- + + +``` + +### Modal + +```js +--- +type: embed +--- + + +``` + +#### Modal Body + +```js +--- +type: embed --- + -## PropTypes Support Dropped +``` + +#### Modal Footer + +```js +--- +type: embed +--- + + +``` + +#### Modal Header + +```js +--- +type: embed +--- + + +``` + +### NumberInput + +`error` or `success` messages are no longer displayed when the component is `readOnly` or `disabled`. + +`newError` message type is now deprecated. Both `newError` and the original `error` type now behave identically (using the new implementation that was previously exclusive to `newError`). + +```js +--- +type: embed +--- + + +``` + +### Options + +```js +--- +type: embed +--- + + +``` + +#### Options.Item + +```js +--- +type: embed +--- + + +``` + +#### Options.Separator + +```js +--- +type: embed +--- + + +``` + +### Pill -With React 19, support for **PropTypes was dropped** from the core library. While it's still possible to use them with third-party libraries, InstUI has decided to no longer support them based on user feedback. +#### Deprecated color prop values -**Tip:** To see how the removal of `propTypes` might affect your application's business logic, you can use a Babel plugin like [babel-plugin-transform-react-remove-prop-types](https://github.com/oliviertassinari/babel-plugin-transform-react-remove-prop-types) to strip them out during your build process for testing. +The following `color` prop values have been **removed**: +- `'danger'` - use `'error'` instead +- `'alert'` - use `'info'` instead + +```js +--- +type: embed +--- + + +``` + +### ProgressBar + +```js +--- +type: embed +--- + + +``` + +### RadioInput + +- Setting `readonly` does not set the low level `` (note: it will only render anything to the DOM if you provide a value to the `dir` prop.). The provided codemod will remove this prop automatically (see below). +```js +--- +type: embed +--- + -| Removed | What to use instead? | -| :------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | -| `canvas.use()`, `canvasHighContrast.use()` | Wrap all your application roots in `` | -| `canvasThemeLocal`, `canvasHighContrastThemeLocal` | Use `canvas` and `canvasHighContrast` respectively, they are the same objects. | -| `variables` field on theme objects | Use `canvas.borders` instead of `canvas.variables.borders` (same for all other fields) | -| `@instructure/theme-registry` package | This added the removed functions above. Wrap all your application roots in `` | +``` -### CodeEditor +#### SideNavBarItem -The **`` component** from the `ui-code-editor` package has been **removed** due to significant accessibility issues. Please use the [SourceCodeEditor](SourceCodeEditor) component as a replacement. +```js +--- +type: embed +--- + -We have removed these utilities from the `ui-react-utils` package because we are phasing out parts from the codebase that use decorators. +``` -If you want to still use these we suggest to copy-paste their code from the latest v10 codebase (Note: they only work for class-based components!). +### SourceCodeEditor +```js --- +type: embed +--- + + +``` + +### Spinner + +- `as` prop has been removed, `Spinner` will always render as a `
` element. +- `elementRef` prop has been removed, use the `ref` prop instead. ### Table -[Table](Table) is now using [TableContext](TableContext) to pass down data to its child components, the following props are no longer passed down to their children (This should only affect you if you have custom table rows or cells): +#### Table.Cell + +```js +--- +type: embed +--- + + +``` + +#### Table.ColHeader + +```js +--- +type: embed +--- + + +``` + +#### Table.Row + +```js +--- +type: embed +--- + + +``` + +#### Table.RowHeader + +```js +--- +type: embed +--- + + +``` + +### Tabs + +#### Tabs.Panel + +```js +--- +type: embed +--- + + +``` + +#### Tabs.Tab + +```js +--- +type: embed +--- + + +``` + +### Tag + +```js +--- +type: embed +--- + + +``` + +### Text + +- `alert` color has been removed. Please use `primary` instead. +- Some prop values have been deprecated, see [Text](/Text) for more details. +- `color` has 2 new values: `primary-on` and `secondary-on`, these are used for colored surfaces. + +### TextArea -| Component | Prop removed | Substitute | -| :-------- | :----------- | :------------------------------ | -| `Row` | `isStacked` | is now stored in `TableContext` | -| `Body` | `isStacked` | is now stored in `TableContext` | -| `Body` | `hover` | is now stored in `TableContext` | -| `Body` | `headers` | is now stored in `TableContext` | +`error` or `success` messages are no longer displayed when the component is `readOnly` or `disabled`. -[Table](Table)'s `caption` prop is now required. +```js +--- +type: embed +--- + + +``` + +### TextInput + +`error` or `success` messages are no longer displayed when the component is `readOnly` or `disabled`. + +`newError` message type is now deprecated. Both `newError` and the original `error` type now behave identically (using the new implementation that was previously exclusive to `newError`). + +```js +--- +type: embed +--- + + +``` + +### ToggleDetails + +```js +--- +type: embed +--- + + +``` + +### Tray + +```js +--- +type: embed +--- + + +``` + +### TreeBrowser + +#### Icon system migration + +TreeBrowser now uses Lucide icons instead of the legacy icon system: + +- Default `collectionIcon` changed from `IconFolderLine` to `FolderClosedInstUIIcon` +- Default `collectionIconExpanded` changed from `IconFolderLine` to `FolderClosedInstUIIcon` +- Default `itemIcon` changed from `IconDocumentLine` to `FileTextInstUIIcon` + +#### Theme variable changes + +```js +--- +type: embed +--- + +``` +**TreeButton:** + +```js +--- +type: embed --- + -## API Changes +``` -`ui-dom-utils`/`getComputedStyle` can now return `undefined`: In previous versions it sometimes returned an empty object which could lead to runtime exceptions when one tried to call methods of `CSSStyleDeclaration` on it. +### View +```js +--- +type: embed --- + + +``` ## Codemods @@ -90,16 +1307,16 @@ To ease the upgrade, we provide codemods that will automate most of the changes. --- type: code --- -npm install @instructure/ui-codemods@11 -npx jscodeshift@17.3.0 -t node_modules/@instructure/ui-codemods/lib/instUIv11Codemods.ts --usePrettier=false +npm install @instructure/ui-codemods@12 +npx jscodeshift@17.3.0 -t node_modules/@instructure/ui-codemods/lib/instUIv12Codemods.ts --usePrettier=false ``` -This is a collection of the codemods that will do the following: +where `` is the path to the directory (and its subdirectories) to be transformed. + +The codemods that will do the following: -- Removes the `as` prop from `InstUISettingsProvider`. -- Renames `canvasThemeLocal` to `canvas` and `canvasHighContrastThemeLocal` to `canvasHighContrastTheme`, warns about deleted `ThemeRegistry` imports and the removed `canvas.use()`/`canvasHighContrast.use()` functions. -- Prints a warning if the `caption` prop is missing from a `
` -- Warns if `CodeEditor` is used +- TODO add details +- TODO Options for the codemod: diff --git a/docs/guides/using-theme-overrides.md b/docs/guides/using-theme-overrides.md index 81d63c0c70..0369b917c1 100644 --- a/docs/guides/using-theme-overrides.md +++ b/docs/guides/using-theme-overrides.md @@ -105,7 +105,7 @@ type: example ``` -### Overriding theme for a specific component in a subtree +### Overriding theme for all components in a subtree You can override the theme variables of specific components too with the `componentOverrides` key. You can do this for every theme or for just a given theme. @@ -180,7 +180,7 @@ type: code ``` -#### Override function +#### Override function for all instances The `InstUISettingsProvider` accepts a `function`. The override function's first parameter is the currently applied theme object. It should return a valid theme or override object. @@ -214,7 +214,7 @@ Themeable components (that implement the [withStyle](withStyle) decorator) accep The available theme variables are always displayed at the bottom of the component's page (e.g.: [Button component theme variables](/#Button/#ButtonTheme)). -#### Override object +#### Override object for a single component ```js --- @@ -227,14 +227,17 @@ type: example
@@ -253,7 +256,7 @@ type: example
``` -#### Override function +#### Override function for a single component The override function's first parameter is the component's own theme object, the second is the main theme object. @@ -345,6 +348,68 @@ const generateStyle = (componentTheme) => { } ``` +### Theme overrides for focus rings, border radii, shadows, spacing + +Certain visuals as of now styled via a central object called `SharedTokens`. You can override this too, for example: + +```js +--- +type: example +--- + + + + Here is a View with a blue shadow. and custom borders + + + +``` + ### Branding (user customizable theming) The `canvas` theme has specific theme variables that are meant as a basis to provide end users a customizability of this theme, e.g. a university can use their own colors throughout the UI. This is used by [Canvas's theme editor](https://community.canvaslms.com/t5/Admin-Guide/How-do-I-create-a-theme-for-an-account-using-the-Theme-Editor/ta-p/242). @@ -489,7 +554,7 @@ const Example = () => {

SideNavBar branding

- + setIcBrandGlobalNavBgd(v)}/> setIcGlobalNavLinkHover(v)}/> diff --git a/docs/patterns/UsingIcons.md b/docs/patterns/UsingIcons.md index 9ba9e57cc8..9be68308e1 100644 --- a/docs/patterns/UsingIcons.md +++ b/docs/patterns/UsingIcons.md @@ -7,6 +7,116 @@ relevantForAI: true ## Using Icons +Icons from `@instructure/ui-icons` are available as `InstUIIcon` components β€” browse them +in the [Icons gallery](#icons). Legacy class-component icons (`IconHeartLine`, `IconSearchLine`, +etc.) are still available for backwards compatibility but are deprecated β€” see +[Legacy Icons](#legacy-icons) below. Components with the new theming system only accept new icons. + +### Accessibility + +Without a `title` prop the icon is decorative: `aria-hidden="true"` and `role="presentation"` are +set automatically. When a `title` is provided, the icon becomes meaningful: `aria-label` is set to +the title value and `role="img"` is added. + +```js +--- +type: example +--- + + I New York + +``` + +### Size + +Use the `size` prop with a semantic size token. Stroke width scales automatically with the size. + +```js +--- +type: example +--- + + + xs + sm + md + lg + xl + 2xl + + +``` + +Illustration sizes (`illu-sm`, `illu-md`, `illu-lg`) are intended for larger decorative contexts. + +### Color + +Use the `color` prop with a semantic color token β€” a theme-aware named value (e.g. `errorColor`, +`successColor`) that automatically adapts to the active theme. `inherit` passes `currentColor` through from the parent element. +`ai` renders the icon with an AI gradient. + +```js +--- +type: example +--- + + + + + + + + + + + +``` + +### Rotate + +```js +--- +type: example +--- + + + + + + + + +``` + +### Stroke vs Filled + +Lucide icons are stroke-based. Several custom icons come in a filled variant, identified by the +`Solid` suffix in their export name (e.g. `BellInstUIIcon` vs `BellSolidInstUIIcon`). + +```js +--- +type: example +--- + + + Stroke + Filled + + +``` + +--- + +## Legacy Icons + +> **Deprecated.** The legacy icon set (`IconHeartLine`, `IconSearchLine`, etc.) is kept for +> backwards compatibility. Use the `InstUIIcon` components above for new code. +> To migrate existing usage run: +> +> ``` +> npx @instructure/ui-codemods migrateToNewIcons +> ``` + ### Accessibility By default, the icon's `role` is set to `presentation`. However, when the `title` prop is set, the role attribute is set to `img`. Include the `description` prop to further describe the icon. diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000000..ccf83bcafe --- /dev/null +++ b/dprint.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://dprint.dev/schemas/v0.json", + "typescript": { + "semiColons": "asi" + }, + "excludes": ["**/node_modules", "!packages/ui-themes/src/themes/newThemes/"], + "plugins": ["https://plugins.dprint.dev/typescript-0.95.11.wasm"] +} diff --git a/lerna.json b/lerna.json index 8ba6600a75..99af143168 100644 --- a/lerna.json +++ b/lerna.json @@ -7,4 +7,4 @@ } }, "$schema": "node_modules/lerna/schemas/lerna-schema.json" -} \ No newline at end of file +} diff --git a/package.json b/package.json index a97788e6cd..52ccccc2d2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "packages/*" ], "scripts": { + "build:themes": "ui-scripts build-themes", "prestart": "pnpm run bootstrap", "start": "pnpm --filter docs-app start", "start:watch": "pnpm --filter docs-app start:watch", @@ -133,5 +134,5 @@ "browserslist": [ "extends @instructure/browserslist-config-instui" ], - "packageManager": "pnpm@10.18.3+sha512.bbd16e6d7286fd7e01f6b3c0b3c932cda2965c06a908328f74663f10a9aea51f1129eea615134bf992831b009eabe167ecb7008b597f40ff9bc75946aadfb08d" + "packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b" } diff --git a/packages/__docs__/CHANGELOG.md b/packages/__docs__/CHANGELOG.md index 34879d0e7a..f7ddba3352 100644 --- a/packages/__docs__/CHANGELOG.md +++ b/packages/__docs__/CHANGELOG.md @@ -7,616 +7,336 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package docs-app - - - - # [11.5.0](https://github.com/instructure/instructure-ui/compare/v11.4.0...v11.5.0) (2026-02-03) **Note:** Version bump only for package docs-app - - - - # [11.4.0](https://github.com/instructure/instructure-ui/compare/v11.3.0...v11.4.0) (2026-01-20) **Note:** Version bump only for package docs-app - - - - # [11.3.0](https://github.com/instructure/instructure-ui/compare/v11.2.0...v11.3.0) (2026-01-12) **Note:** Version bump only for package docs-app - - - - # [11.2.0](https://github.com/instructure/instructure-ui/compare/v11.0.1...v11.2.0) (2025-11-06) - ### Bug Fixes -* fix themes for subcomponents displaying wrong in the docs app ([d0d821e](https://github.com/instructure/instructure-ui/commit/d0d821edbc9eb90b3a1217b20d412e1a86a93fd7)) - +- fix themes for subcomponents displaying wrong in the docs app ([d0d821e](https://github.com/instructure/instructure-ui/commit/d0d821edbc9eb90b3a1217b20d412e1a86a93fd7)) ### Features -* **many:** migrate from npm to pnpm ([f7bb16e](https://github.com/instructure/instructure-ui/commit/f7bb16e114df83984c67d5a6e07fb4d9c65efc53)) - - - - +- **many:** migrate from npm to pnpm ([f7bb16e](https://github.com/instructure/instructure-ui/commit/f7bb16e114df83984c67d5a6e07fb4d9c65efc53)) # [11.1.0](https://github.com/instructure/instructure-ui/compare/v11.0.1...v11.1.0) (2025-11-05) - ### Bug Fixes -* fix themes for subcomponents displaying wrong in the docs app ([d0d821e](https://github.com/instructure/instructure-ui/commit/d0d821edbc9eb90b3a1217b20d412e1a86a93fd7)) - +- fix themes for subcomponents displaying wrong in the docs app ([d0d821e](https://github.com/instructure/instructure-ui/commit/d0d821edbc9eb90b3a1217b20d412e1a86a93fd7)) ### Features -* **many:** migrate from npm to pnpm ([f7bb16e](https://github.com/instructure/instructure-ui/commit/f7bb16e114df83984c67d5a6e07fb4d9c65efc53)) - - - - +- **many:** migrate from npm to pnpm ([f7bb16e](https://github.com/instructure/instructure-ui/commit/f7bb16e114df83984c67d5a6e07fb4d9c65efc53)) ## [11.0.1](https://github.com/instructure/instructure-ui/compare/v11.0.0...v11.0.1) (2025-10-13) - ### Bug Fixes -* **ui-text:** fixed an issue where letterSpacingNormal theme variable previously showed an error because its value was 0 ([856218d](https://github.com/instructure/instructure-ui/commit/856218d9852dc8c8d44d20a600db23ba11a7621f)) - - - - +- **ui-text:** fixed an issue where letterSpacingNormal theme variable previously showed an error because its value was 0 ([856218d](https://github.com/instructure/instructure-ui/commit/856218d9852dc8c8d44d20a600db23ba11a7621f)) # [11.0.0](https://github.com/instructure/instructure-ui/compare/v10.26.0...v11.0.0) (2025-10-06) - ### Features -* **many:** instUI v11 release ([36f5438](https://github.com/instructure/instructure-ui/commit/36f54382669186227ba24798bbf7201ef2f5cd4c)) - +- **many:** instUI v11 release ([36f5438](https://github.com/instructure/instructure-ui/commit/36f54382669186227ba24798bbf7201ef2f5cd4c)) ### BREAKING CHANGES -* **many:** InstUI v11 contains the following breaking changes: -- React 16 and 17 are no longer supported -- remove `PropTypes` from all packages -- remove `CodeEditor` component -- remove `@instui/theme-registry` package -- remove `@testable`, `@experimental`, `@hack` decorators -- InstUISettingsProvider's `as` prop is removed -- `canvas.use()`, `canvasHighContrast.use()` functions are removed -- `canvasThemeLocal`, `canvasHighContrastThemeLocal` are removed -- `variables` field on theme objects are removed -- remove deprecated props from Table: Row's `isStacked`, Body's +- **many:** InstUI v11 contains the following breaking changes: + +* React 16 and 17 are no longer supported +* remove `PropTypes` from all packages +* remove `CodeEditor` component +* remove `@instui/theme-registry` package +* remove `@testable`, `@experimental`, `@hack` decorators +* InstUISettingsProvider's `as` prop is removed +* `canvas.use()`, `canvasHighContrast.use()` functions are removed +* `canvasThemeLocal`, `canvasHighContrastThemeLocal` are removed +* `variables` field on theme objects are removed +* remove deprecated props from Table: Row's `isStacked`, Body's `isStacked`, `hover`, and `headers` -- `Table`'s `caption` prop is now required -- `ui-dom-utils`'s `getComputedStyle` can now return `undefined` - - - - +* `Table`'s `caption` prop is now required +* `ui-dom-utils`'s `getComputedStyle` can now return `undefined` # [10.26.0](https://github.com/instructure/instructure-ui/compare/v10.25.0...v10.26.0) (2025-10-01) **Note:** Version bump only for package docs-app - - - - # [10.25.0](https://github.com/instructure/instructure-ui/compare/v10.24.2...v10.25.0) (2025-09-09) **Note:** Version bump only for package docs-app - - - - ## [10.24.2](https://github.com/instructure/instructure-ui/compare/v10.24.1...v10.24.2) (2025-08-11) **Note:** Version bump only for package docs-app - - - - ## [10.24.1](https://github.com/instructure/instructure-ui/compare/v10.24.0...v10.24.1) (2025-07-30) **Note:** Version bump only for package docs-app - - - - # [10.24.0](https://github.com/instructure/instructure-ui/compare/v10.23.0...v10.24.0) (2025-07-18) - ### Bug Fixes -* **many:** fix focus ring not respecting theme overrides in Button and FileDrop ([8fffc5d](https://github.com/instructure/instructure-ui/commit/8fffc5db8f41249277283b0ad05be0d158d6d7d7)) - - - - +- **many:** fix focus ring not respecting theme overrides in Button and FileDrop ([8fffc5d](https://github.com/instructure/instructure-ui/commit/8fffc5db8f41249277283b0ad05be0d158d6d7d7)) # [10.23.0](https://github.com/instructure/instructure-ui/compare/v10.22.0...v10.23.0) (2025-07-09) **Note:** Version bump only for package docs-app - - - - # [10.22.0](https://github.com/instructure/instructure-ui/compare/v10.21.0...v10.22.0) (2025-07-04) - ### Features -* **many:** add new package (instructure) and three new components: AiInformation, NutritionFacts and DataPermissionLevels ([073be7b](https://github.com/instructure/instructure-ui/commit/073be7b50893e9ab77158ee8a83506eddfbd4113)) - - - - +- **many:** add new package (instructure) and three new components: AiInformation, NutritionFacts and DataPermissionLevels ([073be7b](https://github.com/instructure/instructure-ui/commit/073be7b50893e9ab77158ee8a83506eddfbd4113)) # [10.21.0](https://github.com/instructure/instructure-ui/compare/v10.20.1...v10.21.0) (2025-06-27) **Note:** Version bump only for package docs-app - - - - ## [10.20.1](https://github.com/instructure/instructure-ui/compare/v10.20.0...v10.20.1) (2025-06-17) **Note:** Version bump only for package docs-app - - - - # [10.20.0](https://github.com/instructure/instructure-ui/compare/v10.19.1...v10.20.0) (2025-06-13) - ### Bug Fixes -* **many:** update dependencies, browsersdb and moment timezone database ([3813636](https://github.com/instructure/instructure-ui/commit/3813636458c901ad4bc74a4d5ae015cb55defcb2)) - - - - +- **many:** update dependencies, browsersdb and moment timezone database ([3813636](https://github.com/instructure/instructure-ui/commit/3813636458c901ad4bc74a4d5ae015cb55defcb2)) ## [10.19.1](https://github.com/instructure/instructure-ui/compare/v10.19.0...v10.19.1) (2025-06-05) **Note:** Version bump only for package docs-app - - - - # [10.19.0](https://github.com/instructure/instructure-ui/compare/v10.18.1...v10.19.0) (2025-06-03) **Note:** Version bump only for package docs-app - - - - ## [10.18.1](https://github.com/instructure/instructure-ui/compare/v10.18.0...v10.18.1) (2025-05-29) **Note:** Version bump only for package docs-app - - - - # [10.18.0](https://github.com/instructure/instructure-ui/compare/v10.17.0...v10.18.0) (2025-05-26) - ### Features -* **emotion:** [InstUISettingsProvider] should be able to access the current theme ([d13b6c1](https://github.com/instructure/instructure-ui/commit/d13b6c1449d5ae7c2fa6d917c1a5db8d676df5b2)) - - - - +- **emotion:** [InstUISettingsProvider] should be able to access the current theme ([d13b6c1](https://github.com/instructure/instructure-ui/commit/d13b6c1449d5ae7c2fa6d917c1a5db8d676df5b2)) # [10.17.0](https://github.com/instructure/instructure-ui/compare/v10.16.4...v10.17.0) (2025-05-20) **Note:** Version bump only for package docs-app - - - - ## [10.16.4](https://github.com/instructure/instructure-ui/compare/v10.16.3...v10.16.4) (2025-05-09) **Note:** Version bump only for package docs-app - - - - ## [10.16.3](https://github.com/instructure/instructure-ui/compare/v10.16.1...v10.16.3) (2025-04-30) - ### Bug Fixes -* **ui-table:** fix table crashing in stacked layout when using falsy children ([cb1b2ae](https://github.com/instructure/instructure-ui/commit/cb1b2ae4c24f00f6ba37f414f52fd4a3fe444edf)) -* **ui-time-select,ui-simple-select,ui-select:** make Select accessible for iOS VoiceOver ([b501a7b](https://github.com/instructure/instructure-ui/commit/b501a7b38bfa491298085a173a49a1baa0a19b29)) - - - - +- **ui-table:** fix table crashing in stacked layout when using falsy children ([cb1b2ae](https://github.com/instructure/instructure-ui/commit/cb1b2ae4c24f00f6ba37f414f52fd4a3fe444edf)) +- **ui-time-select,ui-simple-select,ui-select:** make Select accessible for iOS VoiceOver ([b501a7b](https://github.com/instructure/instructure-ui/commit/b501a7b38bfa491298085a173a49a1baa0a19b29)) ## [10.16.2](https://github.com/instructure/instructure-ui/compare/v10.16.1...v10.16.2) (2025-04-22) **Note:** Version bump only for package docs-app - - - - ## [10.16.1](https://github.com/instructure/instructure-ui/compare/v10.16.0...v10.16.1) (2025-04-22) **Note:** Version bump only for package docs-app - - - - # [10.16.0](https://github.com/instructure/instructure-ui/compare/v10.15.2...v10.16.0) (2025-04-11) **Note:** Version bump only for package docs-app - - - - ## [10.15.2](https://github.com/instructure/instructure-ui/compare/v10.15.1...v10.15.2) (2025-04-07) - ### Bug Fixes -* update PropTypes to align with the new spacing tokens ([223d55b](https://github.com/instructure/instructure-ui/commit/223d55bad95e2a3a8b298d622e5b1d0fbab6b289)) - - - - +- update PropTypes to align with the new spacing tokens ([223d55b](https://github.com/instructure/instructure-ui/commit/223d55bad95e2a3a8b298d622e5b1d0fbab6b289)) ## [10.15.1](https://github.com/instructure/instructure-ui/compare/v10.15.0...v10.15.1) (2025-04-03) **Note:** Version bump only for package docs-app - - - - # [10.15.0](https://github.com/instructure/instructure-ui/compare/v10.14.0...v10.15.0) (2025-03-31) **Note:** Version bump only for package docs-app - - - - # [10.14.0](https://github.com/instructure/instructure-ui/compare/v10.13.0...v10.14.0) (2025-03-17) **Note:** Version bump only for package docs-app - - - - # [10.13.0](https://github.com/instructure/instructure-ui/compare/v10.12.0...v10.13.0) (2025-03-06) **Note:** Version bump only for package docs-app - - - - # [10.12.0](https://github.com/instructure/instructure-ui/compare/v10.11.0...v10.12.0) (2025-02-24) - ### Features -* **many:** introduce new spacing tokens; add margin prop for more components ([048c902](https://github.com/instructure/instructure-ui/commit/048c902406c00611cd117fb2fb8164a6eba62fb8)) - - - - +- **many:** introduce new spacing tokens; add margin prop for more components ([048c902](https://github.com/instructure/instructure-ui/commit/048c902406c00611cd117fb2fb8164a6eba62fb8)) # [10.11.0](https://github.com/instructure/instructure-ui/compare/v10.10.0...v10.11.0) (2025-02-03) **Note:** Version bump only for package docs-app - - - - # [10.10.0](https://github.com/instructure/instructure-ui/compare/v10.9.0...v10.10.0) (2024-12-18) **Note:** Version bump only for package docs-app - - - - # [10.9.0](https://github.com/instructure/instructure-ui/compare/v10.8.0...v10.9.0) (2024-12-12) **Note:** Version bump only for package docs-app - - - - # [10.8.0](https://github.com/instructure/instructure-ui/compare/v10.7.0...v10.8.0) (2024-12-09) **Note:** Version bump only for package docs-app - - - - # [10.7.0](https://github.com/instructure/instructure-ui/compare/v10.6.1...v10.7.0) (2024-12-03) **Note:** Version bump only for package docs-app - - - - ## [10.6.1](https://github.com/instructure/instructure-ui/compare/v10.6.0...v10.6.1) (2024-11-26) - ### Bug Fixes -* **emotion,shared-types:** better TS types for theme objects and their overrides ([c790958](https://github.com/instructure/instructure-ui/commit/c7909580b283ab6808f7c9d0f53b49630bf713d9)) - - - - +- **emotion,shared-types:** better TS types for theme objects and their overrides ([c790958](https://github.com/instructure/instructure-ui/commit/c7909580b283ab6808f7c9d0f53b49630bf713d9)) # [10.6.0](https://github.com/instructure/instructure-ui/compare/v10.5.0...v10.6.0) (2024-11-18) **Note:** Version bump only for package docs-app - - - - # [10.5.0](https://github.com/instructure/instructure-ui/compare/v10.4.1...v10.5.0) (2024-11-07) - ### Bug Fixes -* do not lose focus when opening the side menu in the docs app ([0b4434d](https://github.com/instructure/instructure-ui/commit/0b4434df712df83f4a6d5e30bdea37b7be544d83)) -* docs Github corner has focus ring ([cc742d1](https://github.com/instructure/instructure-ui/commit/cc742d16c6c2a1ac8de9defae1eb53d5db4fc0bd)) - - - - +- do not lose focus when opening the side menu in the docs app ([0b4434d](https://github.com/instructure/instructure-ui/commit/0b4434df712df83f4a6d5e30bdea37b7be544d83)) +- docs Github corner has focus ring ([cc742d1](https://github.com/instructure/instructure-ui/commit/cc742d16c6c2a1ac8de9defae1eb53d5db4fc0bd)) ## [10.4.1](https://github.com/instructure/instructure-ui/compare/v10.4.0...v10.4.1) (2024-10-28) - ### Bug Fixes -* docs screenreader alerts are no longer screendeader focusable ([c225853](https://github.com/instructure/instructure-ui/commit/c2258531aa377b698fe932012112704f1879b501)) - - - - +- docs screenreader alerts are no longer screendeader focusable ([c225853](https://github.com/instructure/instructure-ui/commit/c2258531aa377b698fe932012112704f1879b501)) # [10.4.0](https://github.com/instructure/instructure-ui/compare/v10.3.0...v10.4.0) (2024-10-16) **Note:** Version bump only for package docs-app - - - - # [10.3.0](https://github.com/instructure/instructure-ui/compare/v10.2.2...v10.3.0) (2024-10-03) **Note:** Version bump only for package docs-app - - - - ## [10.2.2](https://github.com/instructure/instructure-ui/compare/v10.2.1...v10.2.2) (2024-09-13) - ### Bug Fixes -* **docs:** ensure page scrolls to anchor on load when linked ([6928c97](https://github.com/instructure/instructure-ui/commit/6928c972bbbed0073c37c93b4434f4505e80e374)) -* **docs:** fix compileMarkdown heading id generation ([ef97c7c](https://github.com/instructure/instructure-ui/commit/ef97c7c034ed712085c69d2a4b575da1da6e2c66)) - - - - +- **docs:** ensure page scrolls to anchor on load when linked ([6928c97](https://github.com/instructure/instructure-ui/commit/6928c972bbbed0073c37c93b4434f4505e80e374)) +- **docs:** fix compileMarkdown heading id generation ([ef97c7c](https://github.com/instructure/instructure-ui/commit/ef97c7c034ed712085c69d2a4b575da1da6e2c66)) ## [10.2.1](https://github.com/instructure/instructure-ui/compare/v10.2.0...v10.2.1) (2024-08-30) **Note:** Version bump only for package docs-app - - - - # [10.2.0](https://github.com/instructure/instructure-ui/compare/v10.0.0...v10.2.0) (2024-08-23) - ### Features -* **many:** add data visualization colors, refactor theme code ([c395e17](https://github.com/instructure/instructure-ui/commit/c395e17a43be9fd7ec9d6854f28ae8584c3667bc)) - - - - +- **many:** add data visualization colors, refactor theme code ([c395e17](https://github.com/instructure/instructure-ui/commit/c395e17a43be9fd7ec9d6854f28ae8584c3667bc)) # [10.1.0](https://github.com/instructure/instructure-ui/compare/v10.0.0...v10.1.0) (2024-08-23) - ### Features -* **many:** add data visualization colors, refactor theme code ([c395e17](https://github.com/instructure/instructure-ui/commit/c395e17a43be9fd7ec9d6854f28ae8584c3667bc)) - - - - +- **many:** add data visualization colors, refactor theme code ([c395e17](https://github.com/instructure/instructure-ui/commit/c395e17a43be9fd7ec9d6854f28ae8584c3667bc)) # [10.0.0](https://github.com/instructure/instructure-ui/compare/v9.5.1...v10.0.0) (2024-07-31) - ### Features -* **many:** rewrite color system ([1e5809e](https://github.com/instructure/instructure-ui/commit/1e5809e28dee8c2a71703a429609b8d2f95d76e6)) - +- **many:** rewrite color system ([1e5809e](https://github.com/instructure/instructure-ui/commit/1e5809e28dee8c2a71703a429609b8d2f95d76e6)) ### BREAKING CHANGES -* **many:** Breaks color overrides in certain cases - - - - +- **many:** Breaks color overrides in certain cases ## [9.5.1](https://github.com/instructure/instructure-ui/compare/v9.5.0...v9.5.1) (2024-07-30) **Note:** Version bump only for package docs-app - - - - # [9.5.0](https://github.com/instructure/instructure-ui/compare/v9.3.0...v9.5.0) (2024-07-26) **Note:** Version bump only for package docs-app - - - - # [9.4.0](https://github.com/instructure/instructure-ui/compare/v9.3.0...v9.4.0) (2024-07-26) **Note:** Version bump only for package docs-app - - - - # [9.3.0](https://github.com/instructure/instructure-ui/compare/v9.2.0...v9.3.0) (2024-07-17) - ### Features -* **ui,ui-date-input:** add new DateInput2 component ([9c893fc](https://github.com/instructure/instructure-ui/commit/9c893fc6ac1ae5ef4648f573b648cad78997ac86)) - - - - +- **ui,ui-date-input:** add new DateInput2 component ([9c893fc](https://github.com/instructure/instructure-ui/commit/9c893fc6ac1ae5ef4648f573b648cad78997ac86)) # [9.2.0](https://github.com/instructure/instructure-ui/compare/v9.1.0...v9.2.0) (2024-07-09) **Note:** Version bump only for package docs-app - - - - # [9.1.0](https://github.com/instructure/instructure-ui/compare/v9.0.1...v9.1.0) (2024-06-14) **Note:** Version bump only for package docs-app - - - - ## [9.0.1](https://github.com/instructure/instructure-ui/compare/v9.0.0...v9.0.1) (2024-05-09) **Note:** Version bump only for package docs-app - - - - # [9.0.0](https://github.com/instructure/instructure-ui/compare/v8.56.0...v9.0.0) (2024-05-09) - ### Features -* **instui-config,ui-codemods:** remove instui-cli and template packages ([17a4442](https://github.com/instructure/instructure-ui/commit/17a4442b917d0516d6977ab8bc845dd609a84e49)) -* **shared-types,ui,ui-navigation:** remove deprecated component Navigation ([0173c76](https://github.com/instructure/instructure-ui/commit/0173c761f050d801f4191b823d423e6e29abedd5)) - +- **instui-config,ui-codemods:** remove instui-cli and template packages ([17a4442](https://github.com/instructure/instructure-ui/commit/17a4442b917d0516d6977ab8bc845dd609a84e49)) +- **shared-types,ui,ui-navigation:** remove deprecated component Navigation ([0173c76](https://github.com/instructure/instructure-ui/commit/0173c761f050d801f4191b823d423e6e29abedd5)) ### BREAKING CHANGES -* **instui-config,ui-codemods:** instui-cli and template packages has been removed - - - - +- **instui-config,ui-codemods:** instui-cli and template packages has been removed # [8.56.0](https://github.com/instructure/instructure-ui/compare/v8.55.1...v8.56.0) (2024-05-06) **Note:** Version bump only for package docs-app - - - - ## [8.55.1](https://github.com/instructure/instructure-ui/compare/v8.55.0...v8.55.1) (2024-04-30) **Note:** Version bump only for package docs-app - - - - # [8.55.0](https://github.com/instructure/instructure-ui/compare/v8.54.0...v8.55.0) (2024-04-09) **Note:** Version bump only for package docs-app - - - - # [8.54.0](https://github.com/instructure/instructure-ui/compare/v8.53.2...v8.54.0) (2024-03-21) **Note:** Version bump only for package docs-app - - - - ## [8.53.2](https://github.com/instructure/instructure-ui/compare/v8.53.1...v8.53.2) (2024-02-15) **Note:** Version bump only for package docs-app - - - - ## [8.53.1](https://github.com/instructure/instructure-ui/compare/v8.53.0...v8.53.1) (2024-02-09) **Note:** Version bump only for package docs-app - - - - # [8.53.0](https://github.com/instructure/instructure-ui/compare/v8.52.0...v8.53.0) (2024-02-08) **Note:** Version bump only for package docs-app diff --git a/packages/__docs__/buildScripts/DataTypes.mts b/packages/__docs__/buildScripts/DataTypes.mts index f180e0bc78..c0cedeac3d 100644 --- a/packages/__docs__/buildScripts/DataTypes.mts +++ b/packages/__docs__/buildScripts/DataTypes.mts @@ -23,7 +23,7 @@ */ // This is the format of the saved JSON files -import { Documentation } from 'react-docgen' +import type { Documentation } from 'react-docgen' import type { Theme } from '@instructure/ui-themes' type ProcessedFile = @@ -31,7 +31,7 @@ type ProcessedFile = YamlMetaInfo & JsDocResult & PackagePathData & - { title: string, id:string } + { title: string, id:string, componentVersion?: string } type PackagePathData = { extension: string @@ -137,15 +137,29 @@ type Glyph = { glyphName: string } -type MainIconsData = { - glyphs: Glyph[] -} +type LegacyIconsData = Glyph[] type MainDocsData = { - themes: Record + themes: Record library: LibraryOptions } & ParsedDoc +type VersionMapEntry = { + exportLetter: string + componentVersion: string +} + +type VersionMap = { + libraryVersions: string[] + defaultVersion: string + mapping: Record> +} + +type MinorVersionData = { + libraryVersions: string[] + defaultVersion: string +} + export type { ProcessedFile, PackagePathData, @@ -154,6 +168,10 @@ export type { LibraryOptions, Glyph, MainDocsData, - MainIconsData, - JsDocResult + LegacyIconsData, + JsDocResult, + MinorVersionData, + Section, + VersionMapEntry, + VersionMap } diff --git a/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json b/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json index 0fe03b9bed..5a3b891137 100644 --- a/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json +++ b/packages/__docs__/buildScripts/ai-accessible-documentation/summaries-for-llms-file.json @@ -434,7 +434,7 @@ }, { "title": "form-errors", - "summary": "InstUI form components use a `messages` prop for error/hint/success messages. Supports both `error` (legacy) and `newError` (accessible) types. Required fields now show an asterisk automatically. Examples provided for various form components like TextInput, Checkbox, and DateTimeInput." + "summary": "InstUI form components use a `messages` prop for error/hint/success messages. Required fields now show an asterisk automatically. Examples provided for various form components like TextInput, Checkbox, and DateTimeInput." }, { "title": "layout-spacing", diff --git a/packages/__docs__/buildScripts/build-docs.mts b/packages/__docs__/buildScripts/build-docs.mts index d74a711599..b56085b5cb 100644 --- a/packages/__docs__/buildScripts/build-docs.mts +++ b/packages/__docs__/buildScripts/build-docs.mts @@ -27,14 +27,20 @@ import path from 'path' import { getClientProps } from './utils/getClientProps.mjs' import { processFile } from './processFile.mjs' import fs from 'fs' -import { theme as canvasTheme } from '@instructure/canvas-theme' -import { theme as canvasHighContrastTheme } from '@instructure/canvas-high-contrast-theme' +import { + canvas, + canvasHighContrast, + rebrandDark, + rebrandLight +} from '@instructure/ui-themes' import type { LibraryOptions, MainDocsData, - ProcessedFile + ProcessedFile, + VersionMap } from './DataTypes.mjs' import { getFrontMatter } from './utils/getFrontMatter.mjs' +import { buildVersionMap, getPackageShortName, isDocIncludedInVersion } from './utils/buildVersionMap.mjs' import { createRequire } from 'module' import { fileURLToPath, pathToFileURL } from 'url' import { generateAIAccessibleMarkdowns } from './ai-accessible-documentation/generate-ai-accessible-markdowns.mjs' @@ -44,7 +50,7 @@ const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const require = createRequire(import.meta.url) -// This needs to be required otherwise TSC will mess up the directory structure +// This needs to be required, otherwise TSC will mess up the directory structure // in the output directory // eslint-disable-next-line @instructure/no-relative-imports const rootPackage = require('../../../package.json') // root package.json @@ -61,16 +67,21 @@ const library: LibraryOptions = { scope: '@instructure' } +// TODO this misses: +// ui-react-utils/src/DeterministicIDContext.ts and some others const pathsToProcess = [ // these can be commented out for faster debugging 'CHANGELOG.md', - '**/packages/**/*.md', // package READMEs + 'CODE_OF_CONDUCT.md', + 'LICENSE.md', '**/docs/**/*.md', // general docs '**/src/*.{ts,tsx}', // util src files - '**/src/*/*.{ts,tsx}', // component src files - '**/src/*/*/*.{ts,tsx}', // child component src files - 'CODE_OF_CONDUCT.md', - 'LICENSE.md' + '**/src/*/*.md', // non-versioned component READMEs + '**/src/*/*.{ts,tsx}', // non-versioned component src files + '**/src/*/*/*.{ts,tsx}', // non-versioned child component src files + '**/src/*/v*/*.md', // versioned component READMEs + '**/src/*/v*/*.{ts,tsx}', // versioned component src files + '**/src/*/v*/*/*.{ts,tsx}' // versioned child component src files ] const pathsToIgnore = [ @@ -93,6 +104,11 @@ const pathsToIgnore = [ // ignore index files that just re-export '**/src/index.ts', + // version export mapping files (e.g. src/exports/a.ts, b.ts) + '**/src/exports/**', + // shared theme token files + '**/src/sharedThemeTokens/**', + // packages to ignore: '**/canvas-theme/**', '**/canvas-high-contrast-theme/**', @@ -100,6 +116,9 @@ const pathsToIgnore = [ '**/ui-test-*/src/**', '**/ui-scripts/src/**', + // large generated files: + '**/lucide/**', // Lucide icons directory (large generated file) + // deprecated packages and modules: '**/InputModeListener.ts', // regression testing app: @@ -112,7 +131,26 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) { buildDocs() } -function buildDocs() { +/** + * Filters processed docs for a specific library version using the version map. + * Delegates per-doc inclusion logic to the shared `isDocIncludedInVersion`. + */ +function filterDocsForVersion( + allDocs: ProcessedFile[], + libVersion: string, + versionMap: VersionMap +): ProcessedFile[] { + return allDocs.filter((doc) => + isDocIncludedInVersion( + versionMap, + libVersion, + doc.componentVersion, + getPackageShortName(doc.relativePath) + ) + ) +} + +async function buildDocs() { // eslint-disable-next-line no-console console.log('Start building application data') console.time('docs build time') @@ -120,86 +158,155 @@ function buildDocs() { const { COPY_VERSIONS_JSON = '1' } = process.env const shouldDoTheVersionCopy = Boolean(parseInt(COPY_VERSIONS_JSON)) - // globby needs the posix format - const files = pathsToProcess.map((file) => path.posix.join(packagesDir, file)) - const ignore = pathsToIgnore.map((file) => path.posix.join(packagesDir, file)) - globby(files, { ignore }) - .then((matches) => { - fs.mkdirSync(buildDir + 'docs/', { recursive: true }) + try { + // Build the version map first + // eslint-disable-next-line no-console + console.log('Building version map...') + const versionMap = await buildVersionMap(projectRoot) + // eslint-disable-next-line no-console + console.log( + `Found library versions: ${versionMap.libraryVersions.join(', ')}` + ) + + // globby needs the posix format + const files = pathsToProcess.map((file) => + path.posix.join(packagesDir, file) + ) + const ignore = pathsToIgnore.map((file) => + path.posix.join(packagesDir, file) + ) + const matches = await globby(files, { ignore }) + + fs.mkdirSync(buildDir + 'docs/', { recursive: true }) + // eslint-disable-next-line no-console + console.log( + 'Parsing markdown and source files... (' + matches.length + ' files)' + ) + const allDocs = matches + .map((relativePath) => parseSingleFile(path.resolve(relativePath))) + .filter(Boolean) as ProcessedFile[] + + const themes = parseThemes() + const { defaultVersion } = versionMap + + // Build per-version output, caching the default version result + let defaultMainDocsData: MainDocsData | undefined + for (const libVersion of versionMap.libraryVersions) { + const versionBuildDir = buildDir + 'docs/' + libVersion + '/' + fs.mkdirSync(versionBuildDir, { recursive: true }) + + const versionDocs = filterDocsForVersion(allDocs, libVersion, versionMap) // eslint-disable-next-line no-console console.log( - 'Parsing markdown and source files... (' + matches.length + ' files)' + `Building docs for ${libVersion}: ${versionDocs.length} docs` ) - let docs = matches.map((relativePath) => { - // loop trough every source and Readme file - return processSingleFile(path.resolve(relativePath)) - }) - docs = docs.filter(Boolean) // filter out undefined - const themes = parseThemes() - const clientProps = getClientProps(docs as ProcessedFile[], library) - const props: MainDocsData = { + + const clientProps = getClientProps(versionDocs, library) + const mainDocsData: MainDocsData = { ...clientProps, - themes: themes, + themes, library } - const markdownsAndSources = JSON.stringify(props) + + if (libVersion === defaultVersion) { + defaultMainDocsData = mainDocsData + } + + // Write markdown-and-sources-data.json for this version fs.writeFileSync( - buildDir + 'markdown-and-sources-data.json', - markdownsAndSources + versionBuildDir + 'markdown-and-sources-data.json', + JSON.stringify(mainDocsData) ) - generateAIAccessibleMarkdowns( - buildDir + 'docs/', - buildDir + 'markdowns/' - ) + // Write individual doc JSONs for this version + for (const doc of versionDocs) { + fs.writeFileSync( + versionBuildDir + doc.id + '.json', + JSON.stringify(doc) + ) + } + } - const parentOfDocs = path.dirname(buildDir + 'docs/') + // Backward-compatible root output (uses default version matching "." export) + fs.writeFileSync( + buildDir + 'markdown-and-sources-data.json', + JSON.stringify(defaultMainDocsData) + ) - generateAIAccessibleLlmsFile( - buildDir + 'markdown-and-sources-data.json', - { - outputFilePath: path.join(parentOfDocs, 'llms.txt'), - baseUrl: 'https://instructure.design/markdowns/', - summariesFilePath: path.join(__dirname, '../buildScripts/ai-accessible-documentation/summaries-for-llms-file.json') - } + // Write default version's per-doc JSONs to root docs/ as a backward-compatible + // fallback (no version prefix in the path). + const defaultVersionDocs = filterDocsForVersion(allDocs, defaultVersion, versionMap) + for (const doc of defaultVersionDocs) { + fs.writeFileSync( + buildDir + 'docs/' + doc.id + '.json', + JSON.stringify(doc) ) + } - // eslint-disable-next-line no-console - console.log('Copying icons data...') - fs.copyFileSync( - projectRoot + '/packages/ui-icons/__build__/icons-data.json', - buildDir + 'icons-data.json' - ) + // Write version manifest (client only needs versions + default, not the full map) + const docsVersionsManifest = { + libraryVersions: versionMap.libraryVersions, + defaultVersion + } + fs.writeFileSync( + buildDir + 'docs-versions.json', + JSON.stringify(docsVersionsManifest) + ) + // eslint-disable-next-line no-console + console.log('Wrote docs-versions.json') - // eslint-disable-next-line no-console - console.log('Finished building documentation data') - }) - .then(() => { - console.timeEnd('docs build time') - if (shouldDoTheVersionCopy) { - // eslint-disable-next-line no-console - console.log('Copying versions.json into __build__ folder') - const versionFilePath = path.resolve(__dirname, '..', 'versions.json') - const buildDirPath = path.resolve(__dirname, '..', '__build__') - - return fs.promises.copyFile( - versionFilePath, - `${buildDirPath}/versions.json` + // Generate AI accessible documentation from default version + const defaultVersionDocsDir = buildDir + 'docs/' + defaultVersion + '/' + generateAIAccessibleMarkdowns(defaultVersionDocsDir, buildDir + 'markdowns/') + + generateAIAccessibleLlmsFile( + buildDir + 'markdown-and-sources-data.json', + { + outputFilePath: path.join(buildDir, 'llms.txt'), + baseUrl: 'https://instructure.design/markdowns/', + summariesFilePath: path.join( + __dirname, + '../buildScripts/ai-accessible-documentation/summaries-for-llms-file.json' ) } - return undefined - }) - .catch((error: Error) => { - throw Error( - `Error when generating documentation data: ${error}\n${error.stack}` + ) + + // eslint-disable-next-line no-console + console.log('Copying legacy icons data...') + fs.copyFileSync( + projectRoot + '/packages/ui-icons/src/generated/legacy/legacy-icons-data.json', + buildDir + 'legacy-icons-data.json' + ) + + // eslint-disable-next-line no-console + console.log('Finished building documentation data') + + console.timeEnd('docs build time') + + if (shouldDoTheVersionCopy) { + // eslint-disable-next-line no-console + console.log('Copying versions.json into __build__ folder') + const versionFilePath = path.resolve(__dirname, '..', 'versions.json') + const buildDirPath = path.resolve(__dirname, '..', '__build__') + + await fs.promises.copyFile( + versionFilePath, + `${buildDirPath}/versions.json` ) - }) + } + } catch (error: unknown) { + const err = error instanceof Error ? error : new Error(String(error)) + throw new Error( + `Error when generating documentation data: ${err.message}\n${err.stack}` + ) + } } -// This function is also called by Webpack if a file changes -// TODO this parses some files twice, its needed for the Webpack watcher but not -// for the full build. -function processSingleFile(fullPath: string) { +/** + * Parses a single file and returns a ProcessedFile, or undefined if it + * should be skipped. Pure parsing β€” no file writes. + */ +function parseSingleFile(fullPath: string) { let docObject const dirName = path.dirname(fullPath) const fileName = path.parse(fullPath).name @@ -219,7 +326,7 @@ function processSingleFile(fullPath: string) { let componentIndexFile: string | undefined if (fs.existsSync(path.join(dirName, 'index.tsx'))) { componentIndexFile = path.join(dirName, 'index.tsx') - } else if (fs.existsSync(dirName + 'index.ts')) { + } else if (fs.existsSync(path.join(dirName, 'index.ts'))) { componentIndexFile = path.join(dirName, 'index.ts') } if (componentIndexFile) { @@ -234,6 +341,15 @@ function processSingleFile(fullPath: string) { // documentation .md files, utils ts and tsx files docObject = processFile(fullPath, projectRoot, library) } + return docObject +} + +/** + * Parses a file and writes its JSON to the root build dir. + * Used by the Webpack watcher for incremental rebuilds. + */ +function processSingleFile(fullPath: string) { + const docObject = parseSingleFile(fullPath) if (!docObject) { return } @@ -243,7 +359,7 @@ function processSingleFile(fullPath: string) { } function tryParseReadme(dirName: string) { - const readme = path.join(dirName + '/README.md') + const readme = path.join(dirName, 'README.md') if (fs.existsSync(readme)) { const data = fs.readFileSync(readme) const frontMatter = getFrontMatter(data) @@ -255,15 +371,18 @@ function tryParseReadme(dirName: string) { function parseThemes() { const parsed: MainDocsData['themes'] = {} - parsed[canvasTheme.key] = { - resource: canvasTheme, - requirePath: '@instructure/canvas-theme' - } - parsed[canvasHighContrastTheme.key] = { - resource: canvasHighContrastTheme, - requirePath: '@instructure/canvas-high-contrast-theme' - } + parsed[canvas.key] = { resource: canvas } + parsed[canvasHighContrast.key] = { resource: canvasHighContrast } + parsed[rebrandLight.key] = { resource: rebrandLight } + parsed[rebrandDark.key] = { resource: rebrandDark } return parsed } -export { pathsToProcess, pathsToIgnore, processSingleFile, buildDocs } +export { + pathsToProcess, + pathsToIgnore, + parseSingleFile, + processSingleFile, + buildDocs, + filterDocsForVersion +} diff --git a/packages/__docs__/buildScripts/processFile.mts b/packages/__docs__/buildScripts/processFile.mts index 6d9cd6c415..3986944b20 100644 --- a/packages/__docs__/buildScripts/processFile.mts +++ b/packages/__docs__/buildScripts/processFile.mts @@ -49,13 +49,17 @@ export function processFile( let docId: string const lowerPath = docData.relativePath.toLowerCase() if (docData.id) { - // exist if it was in the description at the top + // exist if it was in the YAML description at the top docId = docData.id - } else if ( - lowerPath.includes(path.sep + 'index.js') || - lowerPath.includes(path.sep + 'index.tsx') - ) { - docId = path.basename(dirName) // return its folder name + } else if (lowerPath.includes(path.sep + 'index.tsx')) { + const fallbackId = path.basename(path.dirname(fullPath)) + if (!docData.displayName && /^v\d+$/.test(fallbackId)) { + // eslint-disable-next-line no-console + console.warn( + `[processFile] Suspicious docId "${fallbackId}" derived from path: ${fullPath}` + ) + } + docId = docData.displayName ?? fallbackId } else if (lowerPath.includes('readme.md')) { const folder = path.basename(dirName) docId = docData.describes ? folder + '__README' : folder @@ -66,5 +70,15 @@ export function processFile( if (!docData.title) { docData.title = docData.id } + + // Extract component version from the file path (e.g. /v1/ or /v2/) + const pathSegments = fullPath.split(path.sep) + const srcIndex = pathSegments.indexOf('src') + const segmentsAfterSrc = srcIndex >= 0 ? pathSegments.slice(srcIndex + 1) : pathSegments + const versionSegment = segmentsAfterSrc.find((seg) => /^v\d+$/.test(seg)) + if (versionSegment) { + docData.componentVersion = versionSegment + } + return docData } diff --git a/packages/__docs__/buildScripts/utils/buildVersionMap.mts b/packages/__docs__/buildScripts/utils/buildVersionMap.mts new file mode 100644 index 0000000000..50168a9945 --- /dev/null +++ b/packages/__docs__/buildScripts/utils/buildVersionMap.mts @@ -0,0 +1,241 @@ +/* + * 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 fs from 'fs' +import path from 'path' +import { globby } from 'globby' +import type { VersionMap } from '../DataTypes.mjs' + +/** + * Scans all packages/ui-* /package.json files to build a version map that + * describes which library version (e.g. v11_5, v11_6) maps to which + * export letter (a, b) and component version directory (v1, v2) per package. + */ +export async function buildVersionMap( + projectRoot: string +): Promise { + const packagesDir = path.join(projectRoot, 'packages') + const packageJsonPaths = await globby('ui-*/package.json', { + cwd: packagesDir, + absolute: true + }) + + const libraryVersionsSet = new Set() + const mapping: VersionMap['mapping'] = {} + // Track the export letter used by each package's "." export + const dotExportLetters = new Map() + + for (const pkgJsonPath of packageJsonPaths) { + const pkgDir = path.dirname(pkgJsonPath) + const pkgShortName = path.basename(pkgDir) // e.g. 'ui-avatar' + const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')) + const exports = pkgJson.exports + + if (!exports || typeof exports !== 'object') { + continue + } + + // Extract the export letter from the "." (default) export + const dotExport = exports['.'] + if (dotExport && typeof dotExport === 'object') { + const dotImportPath = dotExport.import || dotExport.default + if (dotImportPath) { + dotExportLetters.set(pkgShortName, path.parse(dotImportPath).name) + } + } + + for (const exportKey of Object.keys(exports)) { + // Match keys like ./v11_5, ./v11_6 + const versionMatch = exportKey.match(/^\.\/v(\d+_\d+)$/) + if (!versionMatch) { + continue + } + + const libVersion = `v${versionMatch[1]}` // e.g. 'v11_5' + libraryVersionsSet.add(libVersion) + + const exportValue = exports[exportKey] + if (!exportValue || typeof exportValue !== 'object') { + continue + } + + // Extract export letter from the import path + // e.g. "./es/exports/a.js" -> "a" + const importPath = exportValue.import || exportValue.default + if (!importPath) { + continue + } + + const exportLetter = path.parse(importPath).name + if (!exportLetter) { + continue + } + + // Resolve the component version from the source export file + const componentVersion = resolveComponentVersion( + pkgDir, + exportLetter, + pkgShortName + ) + + if (!mapping[libVersion]) { + mapping[libVersion] = {} + } + mapping[libVersion][pkgShortName] = { + exportLetter, + componentVersion + } + } + } + + const libraryVersions = Array.from(libraryVersionsSet).sort((a, b) => { + const [aMaj, aMin] = a.replace('v', '').split('_').map(Number) + const [bMaj, bMin] = b.replace('v', '').split('_').map(Number) + return aMaj - bMaj || aMin - bMin + }) + + // Determine the default version: the library version whose export letters + // match the "." (default) export of each package. This is the version + // that consumers get when they install packages normally. + const defaultVersion = resolveDefaultVersion( + libraryVersions, + mapping, + dotExportLetters + ) + + return { libraryVersions, defaultVersion, mapping } +} + +/** + * Extracts the package short name (e.g. 'ui-avatar') from a file path. + * Returns undefined for files that are not inside a ui-* package. + */ +export function getPackageShortName(filePath: string): string | undefined { + const match = filePath.match(/packages\/(ui-[^/]+)\//) + return match ? match[1] : undefined +} + +/** + * Returns true if a doc with the given componentVersion and package should + * be included in the specified library version. + * + * Rules: + * - No componentVersion (general docs, utils, CHANGELOG) β†’ included in all versions + * - No pkgShortName (not a ui-* package file) β†’ included in all versions + * - Package not in version map (no multi-version exports) β†’ included only if v1 + * - Otherwise β†’ included only if componentVersion matches the version map entry + */ +export function isDocIncludedInVersion( + versionMap: VersionMap, + libVersion: string, + componentVersion: string | undefined, + pkgShortName: string | undefined +): boolean { + if (!componentVersion || !pkgShortName) { + return true + } + + const entry = versionMap.mapping[libVersion]?.[pkgShortName] + if (!entry) { + // Package has no multi-version exports; include only the default (v1) + return componentVersion === 'v1' + } + + return componentVersion === entry.componentVersion +} + +/** + * Reads the source export file (e.g. src/exports/a.ts) and parses imports + * to determine which component version directory it maps to (v1 or v2). + */ +function resolveComponentVersion( + pkgDir: string, + exportLetter: string, + pkgShortName: string +): string { + const exportFilePath = path.join( + pkgDir, + 'src', + 'exports', + `${exportLetter}.ts` + ) + + if (!fs.existsSync(exportFilePath)) { + throw new Error( + `[buildVersionMap] Export file not found: ${exportFilePath} (${pkgShortName}). ` + + `The package.json exports field references a file that does not exist on disk.` + ) + } + + const content = fs.readFileSync(exportFilePath, 'utf-8') + + // Match patterns like: + // from '../ComponentName/v2' + // from '../ComponentName/v1/SubComponent' + const versionMatch = content.match( + /from\s+['"]\.\.\/[^/]+\/(v\d+)(?:\/|['"])/ + ) + if (versionMatch) { + return versionMatch[1] + } + + throw new Error( + `[buildVersionMap] Could not resolve component version from ${exportFilePath} (${pkgShortName}). ` + + `Ensure the file has an import like: from '../ComponentName/v1'` + ) +} + +/** + * Finds the library version whose export letters best match the "." export + * of each package. The version with the most matches is the default. + * Falls back to the first library version if no "." exports were found. + */ +function resolveDefaultVersion( + libraryVersions: string[], + mapping: VersionMap['mapping'], + dotExportLetters: Map +): string { + if (dotExportLetters.size === 0 || libraryVersions.length === 0) { + return libraryVersions[0] ?? '' + } + + let bestVersion = libraryVersions[0] + let bestCount = -1 + + for (const libVersion of libraryVersions) { + let matchCount = 0 + for (const [pkgShortName, dotLetter] of dotExportLetters) { + const entry = mapping[libVersion]?.[pkgShortName] + if (entry && entry.exportLetter === dotLetter) { + matchCount++ + } + } + if (matchCount > bestCount) { + bestCount = matchCount + bestVersion = libVersion + } + } + + return bestVersion +} diff --git a/packages/__docs__/buildScripts/utils/getPathInfo.mts b/packages/__docs__/buildScripts/utils/getPathInfo.mts index 544147c904..d1acc263f5 100644 --- a/packages/__docs__/buildScripts/utils/getPathInfo.mts +++ b/packages/__docs__/buildScripts/utils/getPathInfo.mts @@ -45,8 +45,8 @@ export function getPathInfo( const themePath = resourcePath.replace('index.tsx', 'theme.ts') if (fs.existsSync(themePath)) { - pathInfo.themePath = pathInfo.srcPath.replace('index.tsx', 'theme.ts') - pathInfo.themeUrl = pathInfo.srcUrl.replace('index.tsx', 'theme.ts') + pathInfo.themePath = pathInfo.srcPath.replace('index.tsx', 'styles.ts') + pathInfo.themeUrl = pathInfo.srcUrl.replace('index.tsx', 'styles.ts') } return pathInfo } diff --git a/packages/__docs__/buildScripts/watch-markdown.mjs b/packages/__docs__/buildScripts/watch-markdown.mjs index 3019c2d797..ba2d979f18 100644 --- a/packages/__docs__/buildScripts/watch-markdown.mjs +++ b/packages/__docs__/buildScripts/watch-markdown.mjs @@ -27,15 +27,51 @@ import { globby } from 'globby' import { resolve as resolvePath } from 'path' import { fileURLToPath } from 'url' import { dirname } from 'path' +import fs from 'fs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) -// Dynamically import the processSingleFile function +// Dynamically import build functions and shared version utilities const { processSingleFile } = await import('../lib/build-docs.mjs') +const { buildVersionMap, getPackageShortName, isDocIncludedInVersion } = await import('../lib/utils/buildVersionMap.mjs') + +const projectRoot = resolvePath(__dirname, '../../../') +const buildDir = resolvePath(__dirname, '../__build__/') console.log('[MARKDOWN WATCHER] Starting markdown file watcher...') +// Load the version map once at startup +let versionMap = null +try { + versionMap = await buildVersionMap(projectRoot) + console.log( + `[MARKDOWN WATCHER] Loaded version map with versions: ${versionMap.libraryVersions.join(', ')}` + ) +} catch (error) { + console.warn('[MARKDOWN WATCHER] Could not load version map, falling back to root-only writes:', error.message) +} + +/** + * Given a file path and a docObject, determines which version subdirectories + * the doc JSON should be written to. + */ +function getTargetVersionDirs(filePath, docObject) { + if (!versionMap || versionMap.libraryVersions.length === 0) { + return [] + } + + const pkgShortName = getPackageShortName(filePath) + return versionMap.libraryVersions.filter((libVersion) => + isDocIncludedInVersion( + versionMap, + libVersion, + docObject.componentVersion, + pkgShortName + ) + ) +} + // Find all markdown files to watch const patterns = ['packages/**/*.md', 'docs/**/*.md'] const ignore = [ @@ -58,33 +94,43 @@ const paths = await globby(patterns, { cwd, absolute: true, ignore }) console.log(`[MARKDOWN WATCHER] Found ${paths.length} markdown files to watch`) // Debounce file changes to avoid processing the same file multiple times -const processedFiles = new Map() +const processedFilesMap = new Map() const DEBOUNCE_MS = 300 -// Cleanup old entries from the Map every 5 minutes to prevent memory leak -const CLEANUP_INTERVAL_MS = 5 * 60 * 1000 // 5 minutes -setInterval(() => { - const now = Date.now() - for (const [filePath, timestamp] of processedFiles.entries()) { - if (now - timestamp > DEBOUNCE_MS) { - processedFiles.delete(filePath) - } - } -}, CLEANUP_INTERVAL_MS) - function debouncedProcess(filePath) { const now = Date.now() - const lastProcessed = processedFiles.get(filePath) + const lastProcessed = processedFilesMap.get(filePath) if (lastProcessed && now - lastProcessed < DEBOUNCE_MS) { return // Skip if file was processed recently } - processedFiles.set(filePath, now) + processedFilesMap.set(filePath, now) try { console.log(`[MARKDOWN WATCHER] File changed: ${filePath}`) - processSingleFile(filePath) + const docObject = processSingleFile(filePath) + if (!docObject) { + console.log(`[MARKDOWN WATCHER] No doc output for: ${filePath}`) + return + } + + // Write to version subdirectories + const targetVersionDirs = getTargetVersionDirs(filePath, docObject) + const docJSON = JSON.stringify(docObject) + + for (const libVersion of targetVersionDirs) { + const versionDocsDir = `${buildDir}/docs/${libVersion}/` + fs.mkdirSync(versionDocsDir, { recursive: true }) + fs.writeFileSync(versionDocsDir + docObject.id + '.json', docJSON) + } + + if (targetVersionDirs.length > 0) { + console.log( + `[MARKDOWN WATCHER] Wrote to version dirs: ${targetVersionDirs.join(', ')}` + ) + } + console.log(`[MARKDOWN WATCHER] Successfully processed: ${filePath}`) } catch (error) { console.error(`[MARKDOWN WATCHER] Error processing file: ${filePath}`, error) diff --git a/packages/__docs__/components.ts b/packages/__docs__/components.ts deleted file mode 100644 index 4145d9c9b4..0000000000 --- a/packages/__docs__/components.ts +++ /dev/null @@ -1,137 +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. - */ - -export { - AccessibleContent, - PresentationContent, - ScreenReaderContent -} from '@instructure/ui-a11y-content' -export { Alert } from '@instructure/ui-alerts' -export { Avatar } from '@instructure/ui-avatar' -export { Badge } from '@instructure/ui-badge' -export { Billboard } from '@instructure/ui-billboard' -export { - BaseButton, - Button, - CloseButton, - CondensedButton, - IconButton, - ToggleButton -} from '@instructure/ui-buttons' -export { Byline } from '@instructure/ui-byline' -export { Calendar } from '@instructure/ui-calendar' -export { - ColorPicker, - ColorMixer, - ColorPreset, - ColorContrast, - ColorIndicator -} from '@instructure/ui-color-picker' -export { Dialog } from '@instructure/ui-dialog' -export { Editable, InPlaceEdit } from '@instructure/ui-editable' -export { Expandable } from '@instructure/ui-expandable' -export { Focusable } from '@instructure/ui-focusable' -export { Img } from '@instructure/ui-img' -export { - NutritionFacts, - DataPermissionLevels, - AiInformation -} from '@instructure/ui-instructure' -export { NumberInput } from '@instructure/ui-number-input' -export { DateInput, DateInput2 } from '@instructure/ui-date-input' -export { DateTimeInput } from '@instructure/ui-date-time-input' -export { Pill } from '@instructure/ui-pill' -export { TextInput } from '@instructure/ui-text-input' -export { - FormField, - FormFieldLabel, - FormFieldMessage, - FormFieldMessages, - FormFieldLayout, - FormFieldGroup -} from '@instructure/ui-form-field' -export { Table, TableContext } from '@instructure/ui-table' -export { TruncateText } from '@instructure/ui-truncate-text' -export { ApplyLocale, TextDirectionContext } from '@instructure/ui-i18n' -export { MetricGroup, Metric } from '@instructure/ui-metric' -export { Modal } from '@instructure/ui-modal' -export { Transition } from '@instructure/ui-motion' -export { Mask, Overlay } from '@instructure/ui-overlays' -export { Position } from '@instructure/ui-position' -export { Popover } from '@instructure/ui-popover' -export { RadioInput, RadioInputGroup } from '@instructure/ui-radio-input' -export { Tooltip } from '@instructure/ui-tooltip' -export { Breadcrumb } from '@instructure/ui-breadcrumb' -export { DrawerLayout } from '@instructure/ui-drawer-layout' -export { Grid } from '@instructure/ui-grid' -export { - Checkbox, - CheckboxGroup, - CheckboxFacade, - ToggleFacade -} from '@instructure/ui-checkbox' -export { AppNav } from '@instructure/ui-navigation' -export { List, InlineList } from '@instructure/ui-list' -export { Menu } from '@instructure/ui-menu' -export { Options } from '@instructure/ui-options' -export { Pagination } from '@instructure/ui-pagination' -export { Pages } from '@instructure/ui-pages' -export { Portal } from '@instructure/ui-portal' -export { RangeInput } from '@instructure/ui-range-input' -export { Rating } from '@instructure/ui-rating' -export { Responsive } from '@instructure/ui-responsive' -export { Select } from '@instructure/ui-select' -export { SideNavBar } from '@instructure/ui-side-nav-bar' -export { SimpleSelect } from '@instructure/ui-simple-select' -export { Selectable } from '@instructure/ui-selectable' -export { InlineSVG, SVGIcon } from '@instructure/ui-svg-images' -export { Tabs } from '@instructure/ui-tabs' -export { Text } from '@instructure/ui-text' -export { TimeSelect } from '@instructure/ui-time-select' -export { ToggleDetails, ToggleGroup } from '@instructure/ui-toggle-details' -export { TextArea } from '@instructure/ui-text-area' -export { TreeBrowser } from '@instructure/ui-tree-browser' -export { Flex } from '@instructure/ui-flex' -export { FileDrop } from '@instructure/ui-file-drop' -export { Link } from '@instructure/ui-link' -export { Heading } from '@instructure/ui-heading' -export { ProgressBar } from '@instructure/ui-progress' -export { ProgressCircle } from '@instructure/ui-progress' -export { Tag } from '@instructure/ui-tag' -export { View, ContextView } from '@instructure/ui-view' -export { Tray } from '@instructure/ui-tray' -export { Spinner } from '@instructure/ui-spinner' -export * from '@instructure/ui-icons' -// eslint-disable-next-line no-restricted-imports -export { Guidelines } from './src/Guidelines' -// eslint-disable-next-line no-restricted-imports -export { Figure } from './src/Figure' -// eslint-disable-next-line no-restricted-imports -export { ToggleBlockquote } from './src/ToggleBlockquote' -export { InstUISettingsProvider } from '@instructure/emotion' -export { Drilldown } from '@instructure/ui-drilldown' -export { SourceCodeEditor } from '@instructure/ui-source-code-editor' -export { TopNavBar } from '@instructure/ui-top-nav-bar' -export { TruncateList } from '@instructure/ui-truncate-list' -export { canvas, canvasHighContrast } from '@instructure/ui-themes' diff --git a/packages/__docs__/functionalComponentThemes.ts b/packages/__docs__/functionalComponentThemes.ts deleted file mode 100644 index 9494e55607..0000000000 --- a/packages/__docs__/functionalComponentThemes.ts +++ /dev/null @@ -1,35 +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. - */ - -import type { Theme } from '@instructure/ui-themes' -// eslint-disable-next-line no-restricted-imports -import type { ThemeFunctionsFunctional } from './src/App/props' - -const themes: ThemeFunctionsFunctional = { - // TODO figure out subcomponents e.g.: Table.Cell - Avatar: async (theme: Theme) => - (await import('../ui-avatar/src/Avatar/theme')).default(theme) -} - -export default themes diff --git a/packages/__docs__/globals.ts b/packages/__docs__/globals.ts index 0ec28f6bbf..5b2914209d 100644 --- a/packages/__docs__/globals.ts +++ b/packages/__docs__/globals.ts @@ -38,15 +38,14 @@ import 'moment/min/locales' import { mirrorHorizontalPlacement } from '@instructure/ui-position' -// eslint-plugin-import doesn't like 'import * as Components' here -const Components = require('./components') - +import { getComponentsForVersion } from './versioned-components' +import { rebrandDark, rebrandLight } from '@instructure/ui-themes' import { debounce } from '@instructure/debounce' -// eslint-disable-next-line no-restricted-imports -import '@instructure/ui-icons/es/icon-font/Solid/InstructureIcons-Solid.css' -// eslint-disable-next-line no-restricted-imports -import '@instructure/ui-icons/es/icon-font/Line/InstructureIcons-Line.css' +// eslint-disable-next-line @instructure/no-relative-imports +import '../ui-icons/src/generated/icon-font/Solid/InstructureIcons-Solid.css' +// eslint-disable-next-line @instructure/no-relative-imports +import '../ui-icons/src/generated/icon-font/Line/InstructureIcons-Line.css' import { DateTime } from '@instructure/ui-i18n' // @ts-ignore webpack import @@ -74,9 +73,13 @@ const lorem = new LoremIpsum({ } }) -const globals = { +const Components = getComponentsForVersion() + +const globals: Record = { ...Components, debounce, + rebrandLight, + rebrandDark, moment, avatarSquare, avatarPortrait, @@ -109,4 +112,16 @@ Object.keys(globals).forEach((key) => { ;(global as any)[key] = globals[key] }) +/** + * Re-populates global component references with version-specific components. + * Called when the user switches minor versions in the docs UI. + */ +function updateGlobalsForVersion(version: string) { + const versionComponents = getComponentsForVersion(version) + Object.keys(versionComponents).forEach((key) => { + ;(global as any)[key] = versionComponents[key] + }) +} + export default globals +export { updateGlobalsForVersion } diff --git a/packages/__docs__/package.json b/packages/__docs__/package.json index 8ae4c54b33..dc26426034 100644 --- a/packages/__docs__/package.json +++ b/packages/__docs__/package.json @@ -50,6 +50,7 @@ "@instructure/ui-color-utils": "workspace:*", "@instructure/ui-date-input": "workspace:*", "@instructure/ui-date-time-input": "workspace:*", + "@instructure/ui-decorator": "workspace:*", "@instructure/ui-dialog": "workspace:*", "@instructure/ui-dom-utils": "workspace:*", "@instructure/ui-drawer-layout": "workspace:*", diff --git a/packages/__docs__/resolve.mjs b/packages/__docs__/resolve.mjs deleted file mode 100644 index 9150930094..0000000000 --- a/packages/__docs__/resolve.mjs +++ /dev/null @@ -1,225 +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. - */ - -import path from 'path' - -const alias = { - // set up aliases to get webpack to rebuild when we make changes to these packages - '@instructure/debounce$:': path.resolve(import.meta.dirname, '../debounce/src/'), - '@instructure/ui-a11y-content$': path.resolve( - import.meta.dirname, - '../ui-a11y-content/src/' - ), - '@instructure/ui-alerts$': path.resolve(import.meta.dirname, '../ui-alerts/src/'), - '@instructure/ui-avatar$': path.resolve(import.meta.dirname, '../ui-avatar/src/'), - '@instructure/ui-badge$': path.resolve(import.meta.dirname, '../ui-badge/src/'), - '@instructure/ui-billboard$': path.resolve( - import.meta.dirname, - '../ui-billboard/src/' - ), - '@instructure/ui-breadcrumb$': path.resolve( - import.meta.dirname, - '../ui-breadcrumb/src/' - ), - '@instructure/ui-buttons$': path.resolve(import.meta.dirname, '../ui-buttons/src/'), - '@instructure/ui-byline$': path.resolve(import.meta.dirname, '../ui-byline/src/'), - '@instructure/ui-calendar$': path.resolve(import.meta.dirname, '../ui-calendar/src/'), - '@instructure/ui-checkbox$': path.resolve(import.meta.dirname, '../ui-checkbox/src/'), - '@instructure/ui-color-picker$': path.resolve( - import.meta.dirname, - '../ui-color-picker/src/' - ), - '@instructure/ui-date-input$': path.resolve( - import.meta.dirname, - '../ui-date-input/src/' - ), - '@instructure/ui-date-time-input$': path.resolve( - import.meta.dirname, - '../ui-date-time-input/src/' - ), - '@instructure/ui-dialog$': path.resolve(import.meta.dirname, '../ui-dialog/src/'), - '@instructure/ui-docs-client$': path.resolve( - import.meta.dirname, - '../ui-docs-client/src/' - ), - '@instructure/ui-drawer-layout$': path.resolve( - import.meta.dirname, - '../ui-drawer-layout/src/' - ), - '@instructure/ui-drilldown$': path.resolve( - import.meta.dirname, - '../ui-drilldown/src/' - ), - '@instructure/ui-editable$': path.resolve(import.meta.dirname, '../ui-editable/src/'), - '@instructure/ui-expandable$': path.resolve( - import.meta.dirname, - '../ui-expandable/src/' - ), - '@instructure/ui-flex$': path.resolve(import.meta.dirname, '../ui-flex/src'), - '@instructure/ui-focusable$': path.resolve( - import.meta.dirname, - '../ui-focusable/src/' - ), - '@instructure/ui-a11y-utils$': path.resolve( - import.meta.dirname, - '../ui-a11y-utils/src/' - ), - '@instructure/ui-form-field$': path.resolve( - import.meta.dirname, - '../ui-form-field/src/' - ), - '@instructure/ui-grid$': path.resolve(import.meta.dirname, '../ui-grid/src/'), - '@instructure/ui-i18n$': path.resolve(import.meta.dirname, '../ui-i18n/src/'), - '@instructure/ui-img$': path.resolve(import.meta.dirname, '../ui-img/src/'), - '@instructure/ui-instructure$': path.resolve(import.meta.dirname, '../ui-instructure/src/'), - '@instructure/ui-link$': path.resolve(import.meta.dirname, '../ui-link/src/'), - '@instructure/ui-list$': path.resolve(import.meta.dirname, '../ui-list/src/'), - '@instructure/ui-menu$': path.resolve(import.meta.dirname, '../ui-menu/src/'), - '@instructure/ui-metric$': path.resolve(import.meta.dirname, '../ui-metric/src/'), - '@instructure/ui-modal$': path.resolve(import.meta.dirname, '../ui-modal/src/'), - '@instructure/ui-motion$': path.resolve(import.meta.dirname, '../ui-motion/src/'), - '@instructure/ui-navigation$': path.resolve( - import.meta.dirname, - '../ui-navigation/src/' - ), - '@instructure/ui-number-input$': path.resolve( - import.meta.dirname, - '../ui-number-input/src/' - ), - '@instructure/ui-text-area$': path.resolve( - import.meta.dirname, - '../ui-text-area/src/' - ), - '@instructure/ui-text-input$': path.resolve( - import.meta.dirname, - '../ui-text-input/src/' - ), - '@instructure/ui-options$': path.resolve(import.meta.dirname, '../ui-options/src/'), - '@instructure/ui-overlays$': path.resolve(import.meta.dirname, '../ui-overlays/src/'), - '@instructure/ui-pagination$': path.resolve( - import.meta.dirname, - '../ui-pagination/src/' - ), - '@instructure/ui-pages$': path.resolve(import.meta.dirname, '../ui-pages/src/'), - '@instructure/ui-pill$': path.resolve(import.meta.dirname, '../ui-pill/src/'), - '@instructure/ui-popover$': path.resolve(import.meta.dirname, '../ui-popover/src/'), - '@instructure/ui-position$': path.resolve(import.meta.dirname, '../ui-position/src/'), - '@instructure/ui-portal$': path.resolve(import.meta.dirname, '../ui-portal/src/'), - '@instructure/ui-progress$': path.resolve(import.meta.dirname, '../ui-progress/src'), - '@instructure/ui-radio-input$': path.resolve( - import.meta.dirname, - '../ui-radio-input/src/' - ), - '@instructure/ui-range-input$': path.resolve( - import.meta.dirname, - '../ui-range-input/src/' - ), - '@instructure/ui-rating$': path.resolve(import.meta.dirname, '../ui-rating/src/'), - '@instructure/ui-responsive$': path.resolve( - import.meta.dirname, - '../ui-responsive/src/' - ), - '@instructure/ui-select$': path.resolve(import.meta.dirname, '../ui-select/src/'), - '@instructure/ui-selectable$': path.resolve( - import.meta.dirname, - '../ui-selectable/src/' - ), - '@instructure/ui-side-nav-bar$': path.resolve( - import.meta.dirname, - '../ui-side-nav-bar/src/' - ), - '@instructure/ui-simple-select$': path.resolve( - import.meta.dirname, - '../ui-simple-select/src/' - ), - '@instructure/ui-source-code-editor$': path.resolve( - import.meta.dirname, - '../ui-source-code-editor/src/' - ), - '@instructure/ui-spinner$': path.resolve(import.meta.dirname, '../ui-spinner/src/'), - '@instructure/ui-svg-images$': path.resolve( - import.meta.dirname, - '../ui-svg-images/src/' - ), - '@instructure/ui-table$': path.resolve(import.meta.dirname, '../ui-table/src/'), - '@instructure/ui-tabs$': path.resolve(import.meta.dirname, '../ui-tabs/src/'), - '@instructure/ui-tag$': path.resolve(import.meta.dirname, '../ui-tag/src/'), - '@instructure/ui-text$': path.resolve(import.meta.dirname, '../ui-text/src/'), - '@instructure/ui-time-select$': path.resolve( - import.meta.dirname, - '../ui-time-select/src/' - ), - '@instructure/ui-toggle-details$': path.resolve( - import.meta.dirname, - '../ui-toggle-details/src/' - ), - '@instructure/ui-tooltip$': path.resolve(import.meta.dirname, '../ui-tooltip/src/'), - '@instructure/ui-top-nav-bar$': path.resolve( - import.meta.dirname, - '../ui-top-nav-bar/src/' - ), - '@instructure/ui-tray$': path.resolve(import.meta.dirname, '../ui-tray/src/'), - '@instructure/ui-tree-browser$': path.resolve( - import.meta.dirname, - '../ui-tree-browser/src/' - ), - '@instructure/ui-truncate-list$': path.resolve( - import.meta.dirname, - '../ui-truncate-list/src/' - ), - '@instructure/ui-truncate-text$': path.resolve( - import.meta.dirname, - '../ui-truncate-text/src/' - ), - '@instructure/ui-utils$': path.resolve(import.meta.dirname, '../ui-utils/src/'), - '@instructure/ui-view$': path.resolve(import.meta.dirname, '../ui-view/src/'), - '@instructure/canvas-theme$': path.resolve( - import.meta.dirname, - '../canvas-theme/src/' - ), - '@instructure/canvas-high-contrast-theme$': path.resolve( - import.meta.dirname, - '../canvas-high-contrast-theme/src/' - ), - '@instructure/ui-react-utils$': path.resolve( - import.meta.dirname, - '../ui-react-utils/src/' - ), - '@instructure/ui-dom-utils$': path.resolve( - import.meta.dirname, - '../ui-dom-utils/src/' - ), - '@instructure/ui-color-utils$': path.resolve( - import.meta.dirname, - '../ui-color-utils/src/' - ), - '@instructure/ui-file-drop$': path.resolve( - import.meta.dirname, - '../ui-file-drop/src/' - ), - '@instructure/ui-heading$': path.resolve(import.meta.dirname, '../ui-heading/src/'), - '@instructure/emotion$': path.resolve(import.meta.dirname, '../emotion/src/') -} - -export default { alias } diff --git a/packages/__docs__/src/App/index.tsx b/packages/__docs__/src/App/index.tsx index 6c427c57a3..10fa3be4a9 100644 --- a/packages/__docs__/src/App/index.tsx +++ b/packages/__docs__/src/App/index.tsx @@ -32,7 +32,8 @@ import { } from 'react' import { Alert } from '@instructure/ui-alerts' -import { InstUISettingsProvider, withStyle, Global } from '@instructure/emotion' +import { InstUISettingsProvider, Global } from '@instructure/emotion' +import { withStyleForDocs as withStyle } from '../withStyleForDocs' import { Flex } from '@instructure/ui-flex' import { Text } from '@instructure/ui-text' import { View } from '@instructure/ui-view' @@ -59,14 +60,26 @@ import { Theme } from '../Theme' import { Select } from '../Select' import { Section } from '../Section' import IconsPage from '../Icons' +import LegacyIconsPage from '../LegacyIcons' import { compileMarkdown } from '../compileMarkdown' -import { fetchVersionData, versionInPath } from '../versionData' +import { + fetchVersionData, + fetchMinorVersionData, + versionInPath +} from '../versionData' +import { + parseCurrentUrl, + navigateToVersion, + getAssetBasePath, + MINOR_VERSION_REGEX +} from '../navigationUtils' import generateStyle from './styles' import generateComponentTheme from './theme' import { LoadingScreen } from '../LoadingScreen' -import * as EveryComponent from '../../components' +import { getComponentsForVersion } from '../../versioned-components' +import { updateGlobalsForVersion } from '../../globals' import type { AppProps, AppState, DocData, LayoutSize } from './props' import { allowedProps } from './props' import type { @@ -76,9 +89,13 @@ import type { } from '../../buildScripts/DataTypes.mjs' import { logError } from '@instructure/console' import type { Spacing } from '@instructure/emotion' +import type { NewComponentTypes } from '@instructure/ui-themes' import { FocusRegion } from '@instructure/ui-a11y-utils' type AppContextType = { + /** + * The ID of the currently selected theme. + */ themeKey: keyof MainDocsData['themes'] themes: MainDocsData['themes'] library?: LibraryOptions @@ -128,26 +145,43 @@ class App extends Component { layout: 'large', docsData: null, versionsData: undefined, - iconsData: null + legacyIconsData: null } this._heroRef = createRef() this._navRef = createRef() } + getDocsBasePath = () => { + const base = getAssetBasePath() + const { selectedMinorVersion } = this.state + if (selectedMinorVersion) { + return `${base}/docs/${selectedMinorVersion}/` + } + return `${base}/docs/` + } + + getComponentsForCurrentVersion = (): Record => { + const { selectedMinorVersion } = this.state + return getComponentsForVersion(selectedMinorVersion) + } + fetchDocumentData = async (docId: string) => { - const result = await fetch('docs/' + docId + '.json', { + const basePath = this.getDocsBasePath() + const result = await fetch(basePath + docId + '.json', { signal: this._controller?.signal }) + if (!result.ok) { + throw new Error(`Failed to fetch ${docId}: ${result.status}`) + } const docData: DocData = await result.json() + const everyComp = this.getComponentsForCurrentVersion() if (docId.includes('.')) { // e.g. 'Calendar.Day', first get 'Calendar' then 'Day' const components = docId.split('.') - const everyComp = EveryComponent as Record docData.componentInstance = everyComp[components[0]][components[1]] } else { - docData.componentInstance = - EveryComponent[docId as keyof typeof EveryComponent] + docData.componentInstance = everyComp[docId] } return docData } @@ -157,6 +191,60 @@ class App extends Component { return this.setState({ versionsData }) } + fetchMainDocsData = (url: string, signal: AbortSignal) => { + return fetch(url, { signal }) + .then((response) => response.json()) + .then((docsData) => { + this.setState({ + docsData, + themeKey: Object.keys(docsData.themes)[0] + }) + }) + } + + handleMinorVersionChange = (newVersion: string) => { + // Abort current fetches + this._controller?.abort() + this._controller = new AbortController() + const signal = this._controller.signal + + const errorHandler = (error: Error) => { + if (error.name !== 'AbortError') { + logError(false, error.message) + } + } + + // Update globals so code examples render with the correct component references + updateGlobalsForVersion(newVersion) + + // Clear current data to show loading screen, update selected version + this.setState({ + docsData: null, + currentDocData: undefined, + changelogData: undefined, + selectedMinorVersion: newVersion, + showMinorVersionSelector: true + }) + + // Update URL to reflect new version + navigateToVersion(newVersion) + + this.fetchMainDocsData( + `${getAssetBasePath()}/docs/${newVersion}/markdown-and-sources-data.json`, + signal + ).catch(errorHandler) + + // Icons are not version-specific; only re-fetch if not already loaded + if (!this.state.legacyIconsData) { + fetch(`${getAssetBasePath()}/legacy-icons-data.json`, { signal }) + .then((response) => response.json()) + .then((legacyIconsData) => { + this.setState({ legacyIconsData }) + }) + .catch(errorHandler) + } + } + mainContentRef = (el: Element | null) => { this._mainContentRef = el as HTMLElement } @@ -203,26 +291,48 @@ class App extends Component { this._controller = new AbortController() const signal = this._controller.signal - this.fetchVersionData(signal) - const errorHandler = (error: Error) => { - logError(error.name === 'AbortError', error.message) + if (error.name !== 'AbortError') { + logError(false, error.message) + } } + + this.fetchVersionData(signal).catch(errorHandler) document.addEventListener('keydown', this.handleTabKey) - fetch('icons-data.json', { signal }) + fetch(`${getAssetBasePath()}/legacy-icons-data.json`, { signal }) .then((response) => response.json()) - .then((iconsData) => { - this.setState({ iconsData: iconsData }) - }) + .then((iconsData) => this.setState({ legacyIconsData: iconsData })) .catch(errorHandler) - fetch('markdown-and-sources-data.json', { signal }) - .then((response) => response.json()) - .then((docsData) => { - this.setState({ - docsData, - themeKey: Object.keys(docsData.themes)[0] - }) + + // Detect minor version from URL (e.g. /v11_7/Menu) + const { minorVersion: urlMinorVersion } = parseCurrentUrl() + + // Always fetch minor version data to enable the version selector + fetchMinorVersionData(signal) + .then((minorVersionsData) => { + if (minorVersionsData && minorVersionsData.libraryVersions.length > 0) { + // If URL has a version, use it; otherwise use default + const selectedMinorVersion = + urlMinorVersion ?? minorVersionsData.defaultVersion + // Update globals before fetching docs so renders use correct components + updateGlobalsForVersion(selectedMinorVersion) + this.setState({ + minorVersionsData, + selectedMinorVersion, + // Only show version selector when URL has explicit version + showMinorVersionSelector: !!urlMinorVersion + }) + return this.fetchMainDocsData( + `${getAssetBasePath()}/docs/${selectedMinorVersion}/markdown-and-sources-data.json`, + signal + ) + } + // No minor versions available, fetch from root path + return this.fetchMainDocsData( + `${getAssetBasePath()}/markdown-and-sources-data.json`, + signal + ) }) .catch(errorHandler) @@ -270,24 +380,28 @@ class App extends Component { getPathInfo = () => { const { hash, pathname } = window.location - // Case 1: Old hash-based routing (hash contains the main content) const cleanPath = pathname.replace(/^\/+|\/+$/g, '') - const pathSegments = cleanPath.split('/') + const segments = cleanPath.split('/').filter(Boolean) - // Check if the pathname is just a base path (ends with slash or has no meaningful final segment) - const hasSubstantialPathname = - pathSegments.length > 0 && - pathSegments[pathSegments.length - 1] !== '' && - !pathSegments.every((seg) => seg === '') + // Skip PR preview prefix + let idx = 0 + if ( + segments.length >= 2 && + segments[0] === 'pr-preview' && + segments[1].startsWith('pr-') + ) { + idx = 2 + } - // If it's just a base path with no hash, treat as homepage - if ((!hasSubstantialPathname || pathname.endsWith('/')) && !hash) { - return ['index'] // homepage + // Skip minor version prefix (e.g. v11_7) + if (idx < segments.length && MINOR_VERSION_REGEX.test(segments[idx])) { + idx++ } + // Hash-based routing (legacy): /#PageName if ( + idx >= segments.length && hash && - (!hasSubstantialPathname || pathname.endsWith('/')) && hash.startsWith('#') && !hash.startsWith('##') ) { @@ -299,22 +413,18 @@ class App extends Component { return [page, id] } } - // Case 2: New clean URL routing (pathname contains the main content) - else { - if (pathSegments.length > 0 && pathSegments[0] !== '') { - // Get the page from the last segment of the path - const page = pathSegments[pathSegments.length - 1] - // If there's a hash that's not at the beginning (like #Guidelines), use it as ID - let id = undefined - if (hash && hash.startsWith('##')) { - id = decodeURI(hash.replace('##', '')) - } else if (hash && !hash.startsWith('#/')) { - id = decodeURI(hash.replace('#', '')) - } - return [page, id] - } + + // Clean URL routing: page is next segment after prefixes + const page = idx < segments.length ? segments[idx] : 'index' + + let id: string | undefined + if (hash && hash.startsWith('##')) { + id = decodeURI(hash.replace('##', '')) + } else if (hash && !hash.startsWith('#/')) { + id = decodeURI(hash.replace('#', '')) } - return [] + + return [page, id] } updateLayout = (matches: QueriesMatching) => { @@ -334,11 +444,14 @@ class App extends Component { updateKey = () => { const [page, _id] = this.getPathInfo() + const { minorVersion } = parseCurrentUrl() + if (page) { this.setState( ({ key, showMenu }) => ({ key: page || 'index', - showMenu: this.handleShowTrayOnURLChange(key, showMenu) + showMenu: this.handleShowTrayOnURLChange(key, showMenu), + showMinorVersionSelector: !!minorVersion }), this.scrollToElement ) @@ -451,7 +564,13 @@ class App extends Component { } renderThemeSelect() { - const themeKeys = Object.keys(this.state.docsData!.themes) + const allThemeKeys = Object.keys(this.state.docsData!.themes) + const showRebrandThemes = + this.state.showMinorVersionSelector && + this.state.selectedMinorVersion !== 'v11_6' + const themeKeys = showRebrandThemes + ? allThemeKeys + : allThemeKeys.filter((key) => !key.startsWith('rebrand')) const smallScreen = this.state.layout === 'small' return themeKeys.length > 1 ? ( @@ -494,11 +613,7 @@ class App extends Component { Theme: {themeKey} - + ) return ( @@ -507,7 +622,6 @@ class App extends Component { } renderIcons(key: string) { - const { iconsData } = this.state const { layout } = this.state const smallerScreens = layout === 'small' || layout === 'medium' @@ -521,34 +635,63 @@ class App extends Component { Icons - + ) return
{this.renderWrappedContent(iconContent)}
} - renderDocument(docId: string, repository: string) { + renderLegacyIcons(key: string) { + const { layout } = this.state + const smallerScreens = layout === 'small' || layout === 'medium' + + const iconContent = ( + + + + ) + + return
{this.renderWrappedContent(iconContent)}
+ } + + renderDocument(docId: keyof NewComponentTypes, repository: string) { const { parents } = this.state.docsData! const children: any[] = [] const currentData = this.state.currentDocData if (!currentData || currentData.id !== docId) { // load all children and the main doc - this.fetchDocumentData(docId).then(async (data) => { - if (parents[docId]) { - for (const childId of parents[docId].children) { - children.push(await this.fetchDocumentData(childId)) + this.fetchDocumentData(docId) + .then(async (data) => { + if (parents[docId]) { + for (const childId of parents[docId].children) { + children.push(await this.fetchDocumentData(childId)) + } } - } - // eslint-disable-next-line no-param-reassign - data.children = children - this.setState( - { - currentDocData: data - }, - this.scrollToElement - ) - }) + // Guard: check if we are still on the same page + if (this.state.key !== docId) return + // eslint-disable-next-line no-param-reassign + data.children = children + this.setState( + { + currentDocData: data + }, + this.scrollToElement + ) + }) + .catch((error: Error) => { + if (error.name !== 'AbortError') { + logError( + false, + `Failed to fetch document ${docId}: ${error.message}` + ) + } + }) return ( @@ -563,7 +706,9 @@ class App extends Component { if (olderVersionsGitBranchMap && versionInPath) { legacyGitBranch = olderVersionsGitBranchMap[versionInPath] } - + // Always pass the full theme so old-style generateComponentTheme + // can access colors, typography, spacing etc. + // New-theme components are looked up via themeVariables.newTheme.components const themeVariables = themes[themeKey!].resource const heading = currentData.extension !== '.md' ? currentData.title : '' const documentContent = ( @@ -584,6 +729,7 @@ class App extends Component { themeVariables={themeVariables} repository={repository} layout={layout} + selectedMinorVersion={this.state.selectedMinorVersion} /> @@ -603,7 +749,7 @@ class App extends Component { renderHero() { const { library, docs, themes } = this.state.docsData! - const { layout } = this.state + const { layout, selectedMinorVersion } = this.state const themeDocs: ParsedDocSummary = {} @@ -619,7 +765,11 @@ class App extends Component { name={library.name} docs={{ ...docs, ...themeDocs }} repository={library.repository} - version={library.version} + version={ + selectedMinorVersion + ? selectedMinorVersion.replace('v', '').replace('_', '.') + : library.version + } layout={layout} ref={this._heroRef} /> @@ -629,9 +779,15 @@ class App extends Component { renderChangeLog() { if (!this.state.changelogData) { - this.fetchDocumentData('CHANGELOG').then((data) => { - this.setState({ changelogData: data }) - }) + this.fetchDocumentData('CHANGELOG') + .then((data) => { + this.setState({ changelogData: data }) + }) + .catch((error: Error) => { + if (error.name !== 'AbortError') { + logError(false, `Failed to fetch CHANGELOG: ${error.message}`) + } + }) return ( @@ -707,6 +863,17 @@ class App extends Component { {this.renderIcons(key)} ) + } else if (key === 'legacy-icons') { + return ( + + {this.renderLegacyIcons(key)} + + ) } else if (theme) { return ( { ) } else if (doc) { - return this.renderDocument(key!, repository) + return this.renderDocument((key as keyof NewComponentTypes)!, repository) } else { return ( { name={name === 'instructure-ui' ? 'v' : name} version={version} versionsData={versionsData} + minorVersionsData={ + this.state.showMinorVersionSelector + ? this.state.minorVersionsData + : undefined + } + selectedMinorVersion={this.state.selectedMinorVersion} + onMinorVersionChange={this.handleMinorVersionChange} />