docs: types style guide and POC refactor of Badge types#6058
docs: types style guide and POC refactor of Badge types#6058caseyisonit wants to merge 3 commits intomainfrom
Conversation
|
📚 Branch Preview Links🔍 First Generation Visual Regression Test ResultsWhen a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:
Deployed to Azure Blob Storage: If the changes are expected, update the |
nikkimk
left a comment
There was a problem hiding this comment.
Thanks for putting this together. Definitely lots of discussion points here.
| **Renaming existing merged prefixes:** If a component's types file in core already uses a merged prefix (e.g., `STATUSLIGHT_`), rename the constants to the underscore-separated form (`STATUS_LIGHT_`) in core. To avoid a breaking change in 1st-gen, re-export the old name as a deprecated alias from the 1st-gen package: | ||
|
|
||
| ```typescript | ||
| // 1st-gen/packages/status-light/src/StatusLight.ts | ||
| import { STATUS_LIGHT_VARIANTS_S1 } from '@spectrum-web-components/core/components/status-light'; | ||
|
|
||
| /** | ||
| * @deprecated Use `STATUS_LIGHT_VARIANTS_S1` instead. | ||
| */ | ||
| export const STATUSLIGHT_VARIANTS_S1 = STATUS_LIGHT_VARIANTS_S1; | ||
| ``` | ||
|
|
||
| This keeps the canonical name correct in core while giving 1st-gen consumers a migration path. |
| 1. **Copyright header** | ||
| 2. **Imports** (typically only `ElementSize` from core mixins) | ||
| 3. **Shared constants** — sizes, canonical base arrays (semantic variants, color variants), non-variant constants | ||
| 4. **S1-only constants** — subset arrays exclusive to 1st-gen, validated against canonical types via `satisfies` | ||
| 5. **Canonical constants** — composed arrays that spread from shared bases (e.g., `BADGE_VARIANTS`) | ||
| 6. **Types** — shared types, then S1-only types, then canonical types |
| export type BadgeColorVariantS1 = (typeof BADGE_VARIANTS_COLOR_S1)[number]; // remove with 1st-gen | ||
| export type BadgeVariantS1 = (typeof BADGE_VARIANTS_S1)[number]; // remove with 1st-gen |
There was a problem hiding this comment.
nit/question: Should the inline comment have a @todo?
| export type BadgeColorVariantS1 = (typeof BADGE_VARIANTS_COLOR_S1)[number]; // remove with 1st-gen | |
| export type BadgeVariantS1 = (typeof BADGE_VARIANTS_S1)[number]; // remove with 1st-gen | |
| export type BadgeColorVariantS1 = (typeof BADGE_VARIANTS_COLOR_S1)[number]; // @todo remove with 1st-gen | |
| export type BadgeVariantS1 = (typeof BADGE_VARIANTS_S1)[number]; // @todo remove with 1st-gen |
| ### Dependency direction | ||
|
|
||
| The dependency always flows toward S1: | ||
|
|
||
| ```text | ||
| Shared constants (base arrays + canonical types) | ||
| ←── Canonical composed arrays spread from shared | ||
| ←── S1 arrays validate against shared types via satisfies | ||
| ``` | ||
|
|
||
| This means: | ||
|
|
||
| - **Canonical base arrays and types live in SHARED** — `BADGE_VARIANTS_COLOR` and `BadgeColorVariant` are defined before S1-ONLY | ||
| - **S1 arrays validate against canonical types** — `BADGE_VARIANTS_COLOR_S1` uses `satisfies readonly BadgeColorVariant[]` | ||
| - **Canonical composed arrays never reference S1 arrays** — `BADGE_VARIANTS` does not spread `BADGE_VARIANTS_S1` | ||
| - **S1 arrays may spread from shared arrays** — `BADGE_VARIANTS_S1` spreads `BADGE_VARIANTS_SEMANTIC` (shared) | ||
| - **Deleting S1 content never breaks shared or canonical content** | ||
|
|
||
| ### Removing S1 | ||
|
|
||
| When 1st-gen is retired, the removal process for each types file is: | ||
|
|
||
| 1. Delete the `S1-ONLY` section (constants and types) | ||
| 2. Delete the `S1-ONLY` section separator | ||
| 3. Remove the `@todo` comment about S1 removal | ||
| 4. Optionally, if a shared constant was only shared for S1's benefit and is identical to the canonical array, inline it | ||
|
|
||
| No renames. No changes to canonical type names. No breaking changes for 2nd-gen consumers. |
|
|
||
| ### Event patterns | ||
|
|
||
| Standard events use `bubbles: true` and `composed: true` to cross shadow DOM boundaries: |
There was a problem hiding this comment.
nit
| Standard events use `bubbles: true` and `composed: true` to cross shadow DOM boundaries: | |
| Standard events use `bubbles: true` to allow the event to traverse up the DOM and `composed: true` to cross shadow DOM boundaries: |
| 5. **Help text controller** — convert `ManageHelpText` mixin to controller pattern | ||
| 6. **Platform utilities** — port `platform.ts` detection functions to `core/utils/` | ||
| 7. **Focus utilities** — port `getActiveElement()`, `firstFocusableIn()`, focusable selectors to `core/utils/` | ||
| 8. **Random ID utility** — port `randomID()` to `core/utils/` for ARIA ID generation |
There was a problem hiding this comment.
Not sure how ARIA factors into this when IDREFs don't cross shadow roots, but auto-generated IDREFs for other purposes may be useful.
|
|
||
| **Pattern**: Interactive/semantic components (Progress Circle, Divider) set `role` in `firstUpdated()` and manage dynamic ARIA attributes in `updated()`. Non-interactive display components (Badge, Status Light) don't set roles. Asset handles accessibility within its SVG templates. | ||
|
|
||
| **Inconsistency**: Divider always sets `role="separator"` unconditionally, ignoring any user-supplied role. Progress Circle checks for an existing role before setting it. Divider should follow Progress Circle's pattern to allow role overrides. |
There was a problem hiding this comment.
We should be opinionated on divider's role. Honestly we should be on progress circle's role as well. If the consumer is overriding the role of these components, then they should likely be using a different component.
| 1. **Legacy namespace carryover**: Asset still uses `spectrum-*` class names while the rest of 2nd-gen uses `swc-*`. | ||
| 2. **Dead code in render**: Status Light generates size classes (`swc-StatusLight--sizeS`) that aren't referenced in CSS. | ||
| 3. **Underdocumented advanced CSS behavior**: Progress Circle forced-colors uses a track variable name (`--swc-progress-circle-track-color`) that appears inconsistent with base variable usage (`--swc-progress-circle-track-border-color`). | ||
| 4. **Unconditional role assignment**: Divider sets `role="separator"` unconditionally in `firstUpdated()`, overriding any user-supplied role. |
There was a problem hiding this comment.
I believe this should be the desired pattern, and conditional an anti-pattern. If consumers need a different role, we have a feature request process where we can determine if the role is appropriate for the component or if a new component is needed.
| ### Missing patterns (what should be added) | ||
|
|
||
| 1. **Explicit forced-colors decision records** for Badge and Asset (validate browser defaults vs required overrides). | ||
| 2. **Accessible name validation** for Badge (icon-only variants) and Status Light. |
There was a problem hiding this comment.
Status light on its own does not have an accessible name. It is up to the consumer include text with it.
| ### Priority 5: Standardize accessibility patterns | ||
|
|
||
| 1. **Add accessible name validation** to Badge and Status Light base classes. | ||
| 2. **Use conditional role assignment** (check before set) everywhere, following Progress Circle's pattern. |
There was a problem hiding this comment.
Again, I believe this should be unconditional.
|
Closing as this is cover in #6067 and all feedback comments have been addressed there |
Description
Introduces a contributor style guide for
*.types.tsfiles and demonstrates the recommended patterns by refactoring the Badge component's types as a proof of concept.The style guide defines conventions for constant naming, type derivation, file structure, S1/S2 coexistence, and clean S1 removal. The Badge types file is updated to serve as the canonical reference implementation of these patterns.
Motivation and context
Component types files lacked consistent conventions for naming, organization, and multi-generation coexistence. Different components used different approaches for defining constants and types (e.g.,
_S2suffixes on canonical names, S2 arrays spreading from S1, union types instead of derived types). This guide codifies a single pattern so all components follow the same structure, making future S1 removal straightforward.Related issue(s)
Key changes
Style guide (
CONTRIBUTOR-DOCS/02_style-guide/03_component-types.md)SCREAMING_SNAKE_CASEconstants,PascalCasetypes, component-prefixed namesS1suffix(typeof ARRAY)[number]Badge types POC refactor (
Badge.types.ts)BADGE_VARIANTS_COLOR_S2→BADGE_VARIANTS_COLOR(canonical, unsuffixed)BADGE_VARIANTS_S2→BADGE_VARIANTS(canonical, unsuffixed)BadgeColorVariantS2→BadgeColorVariant(canonical type)BadgeVariantS2→BadgeVariant(canonical type, no longer a union)BADGE_VARIANTS_COLORas the canonical color array;BADGE_VARIANTS_COLOR_S1nowsatisfies readonly BadgeColorVariant[]as const satisfiesto usereadonly ElementSize[]for correctnessSized mixin refactor (
sized-mixin.ts)ElementSizesrecord withELEMENT_SIZESconst array (followsas constpattern)DEFAULT_ELEMENT_SIZESconst array for the common['s', 'm', 'l', 'xl']setVALID_SIZESandvalidSizesparameter types toreadonly ElementSize[]1st-gen/tools/base/src/sizedMixin.tsConsuming components updated
Badge.ts,badge.stories.ts,badge.test.ts— updated all imports to use canonical (unsuffixed) namesResearch docs
SWC-1419_research.md— summarized research findings and recommendationsSWC-1419_research-full-patterns.md— comprehensive pattern analysis across the codebaseStorybook
preview.tsScreenshots (if appropriate)
N/A — no visual changes; types and documentation only.
Author's checklist
Reviewer's checklist
patch,minor, ormajorfeaturesManual review test cases
Verify Badge types follow the style guide patterns [@cdransf]
2nd-gen/packages/core/components/badge/Badge.types.tsBADGE_VARIANTS,BadgeVariant) and only S1-only names carry theS1suffixVerify no regressions in Badge component [@cdransf]
yarn testfor the badge packageVerify sized mixin backward compatibility [@cdransf]
1st-gen/tools/base/src/sizedMixin.tsre-exportsElementSizeswith a deprecation noticeELEMENT_SIZESandDEFAULT_ELEMENT_SIZESare exported fromcore/mixins/index.tsReview style guide completeness [@cdransf]
CONTRIBUTOR-DOCS/02_style-guide/03_component-types.mdDevice review