From b278dfd33faeb5f1cafaf1231ab215a67f7253b3 Mon Sep 17 00:00:00 2001 From: Boba Date: Mon, 9 Mar 2026 19:24:01 -0500 Subject: [PATCH] feat: example migration skill --- .../SKILL.md | 131 ++++++++++++++++++ .../examples/asset.after.tsx | 43 ++++++ .../examples/asset.before.tsx | 43 ++++++ .../examples/basic.after.tsx | 23 +++ .../examples/basic.before.tsx | 21 +++ .../references/api-mapping.md | 85 ++++++++++++ 6 files changed, 346 insertions(+) create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/SKILL.md create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/asset.after.tsx create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/asset.before.tsx create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/basic.after.tsx create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/basic.before.tsx create mode 100644 .claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/references/api-mapping.md diff --git a/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/SKILL.md b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/SKILL.md new file mode 100644 index 000000000..70664b0f7 --- /dev/null +++ b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/SKILL.md @@ -0,0 +1,131 @@ +--- +name: cds-deprecation-v9-to-v10-select-to-alpha-select-web +description: Migrate deprecated web Select usage from `packages/web/src/controls/Select.tsx` to the alpha Select in `packages/web/src/alpha/select/Select.tsx`. Use this whenever a repo is cleaning up CDS v9 deprecations before v10 and the code imports the old web Select or `SelectOption`, uses child-based Select options, or needs help converting `valueLabel`, `SelectOption` children, and legacy Select props into the alpha options-array API. +--- + +# Web Select To Alpha Select + +Use this skill when migrating deprecated web `Select` usage to the alpha web `Select`. + +This migration is not just an import rename. The old component is a trigger plus `SelectOption` children model. The alpha component is an options-driven API with richer customization and slightly different value semantics. + +## What Changed + +- Old web `Select` takes `children` and usually renders many `SelectOption` elements. +- Alpha web `Select` takes an `options` array. +- Old `SelectOption.title` becomes alpha option `label`. +- Old `SelectOption.description`, `media`, `accessory`, `end`, and `disabled` map naturally onto alpha option objects. +- Old `valueLabel` usually disappears because alpha `Select` can derive the displayed label from the selected option object. +- Old `value` is commonly `string | undefined`; alpha `Select` prefers `string | null` for single select. +- Old `width` and `disablePortal` are not alpha `Select` props. + +If you need exact mappings or examples, read `references/api-mapping.md` and the files in `examples/`. + +## Migration Workflow + +1. Find imports of the deprecated web `Select` and `SelectOption`. +2. Replace them with the alpha `Select` import. +3. Convert child `SelectOption` nodes into an `options` array. +4. Normalize state from `string | undefined` to `string | null` when needed. +5. Remove props that no longer exist, especially `valueLabel`, `width`, `disablePortal`, and `onClick`. +6. Preserve supported props that still exist, such as `label`, `helperText`, `placeholder`, `compact`, `labelVariant`, `startNode`, `endNode`, `variant`, and `disabled`. +7. If layout depended on `width`, move that concern to `style`, `className`, or a parent layout component. +8. Re-check behavior for empty selection, focus/open behavior, and accessibility labels. + +## Import Rewrite + +Old pattern: + +```tsx +import { Select } from '@coinbase/cds-web/controls'; +import { SelectOption } from '@coinbase/cds-web/controls'; +``` + +Preferred new pattern: + +```tsx +import { Select } from '@coinbase/cds-web/alpha/select'; +``` + +If the local import style is relative, keep it consistent with the surrounding file. + +## Core Rewrite Rule + +Convert this: + +```tsx + +``` + +Into this: + +```tsx +const options = [ + { value: '1', label: 'Option 1' }, + { value: '2', label: 'Option 2', description: 'BTC' }, +]; + + + + + + + } + value={value} + /> + ); +} diff --git a/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/asset.before.tsx b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/asset.before.tsx new file mode 100644 index 000000000..c56e42f2d --- /dev/null +++ b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/asset.before.tsx @@ -0,0 +1,43 @@ +import { useState } from 'react'; + +import { DotSymbol } from '../../dots'; +import { Box } from '../../layout/Box'; +import { RemoteImage } from '../../media'; +import { Select } from '../../controls/Select'; +import { SelectOption } from '../../controls/SelectOption'; + +const assets = [ + { value: 'btc', label: 'Bitcoin', imageUrl: '/btc.png' }, + { value: 'eth', label: 'Ethereum', imageUrl: '/eth.png' }, +]; + +export function AssetExample() { + const [value, setValue] = useState('btc'); + const selectedAsset = assets.find((asset) => asset.value === value) ?? assets[0]; + + return ( + + ); +} diff --git a/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/basic.after.tsx b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/basic.after.tsx new file mode 100644 index 000000000..67a8d4233 --- /dev/null +++ b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/examples/basic.after.tsx @@ -0,0 +1,23 @@ +import { useState } from 'react'; + +import { Select } from '../../alpha/select'; + +const options = [ + { value: 'option-1', label: 'Option 1', description: 'BTC' }, + { value: 'option-2', label: 'Option 2', description: 'ETH' }, +]; + +export function Example() { + const [value, setValue] = useState(null); + + return ( + + + + + ); +} diff --git a/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/references/api-mapping.md b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/references/api-mapping.md new file mode 100644 index 000000000..62263952b --- /dev/null +++ b/.claude/skills/cds-deprecation-v9-to-v10-select-to-alpha-select-web/references/api-mapping.md @@ -0,0 +1,85 @@ +# API Mapping + +This reference supports the `cds-deprecation-v9-to-v10-select-to-alpha-select-web` skill. + +## Component Model + +Legacy web `Select`: + +- Import path is in the old `controls` area. +- Renders options as `children`. +- Commonly uses `SelectOption` elements. +- Stores the displayed string on the control with `value` and sometimes `valueLabel`. + +Alpha web `Select`: + +- Import path is in `alpha/select`. +- Renders from an `options` array. +- Supports single and multi select. +- Derives selected display content from the selected option and the default control renderer. + +## Prop Mapping + +| Legacy | Alpha | Notes | +| --------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------- | +| `children` | `options` | Convert `SelectOption` nodes to plain objects | +| `value?: string` | `value: string \| null` | Prefer `null` for empty value | +| `onChange?: (newValue: string) => void` | `onChange: (newValue: string \| null) => void` | Widen state when empty selection is possible | +| `valueLabel` | remove | Usually redundant once `options` has the correct `label` | +| `label` | `label` | Same concept | +| `helperText` | `helperText` | Same concept | +| `placeholder` | `placeholder` | Same concept | +| `compact` | `compact` | Same concept | +| `labelVariant` | `labelVariant` | Same concept | +| `startNode` | `startNode` | Same concept | +| `variant` | `variant` | Same concept | +| `disabled` | `disabled` | Same concept | +| `width` | `style` or parent layout | Alpha `Select` does not expose `width` directly | +| `disablePortal` | remove | No direct alpha equivalent | +| `onClick` | `open` / `setOpen` if needed | Only keep if product logic actually needs controlled open state | +| `accessibilityLabel` | `accessibilityLabel` | Still supported | +| `accessibilityLabelledBy` | manual review | No direct alpha prop | +| `accessibilityHint` | manual review | No direct alpha prop | + +## SelectOption Mapping + +| Legacy `SelectOption` prop | Alpha option field | +| -------------------------- | ------------------ | +| `title` | `label` | +| `description` | `description` | +| `value` | `value` | +| `disabled` | `disabled` | +| `media` | `media` | +| `accessory` | `accessory` | +| `end` | `end` | + +Ignore old option-only behavior that was tied to legacy dropdown internals unless the product still needs it. + +## Common Rewrite Pattern + +Before: + +```tsx + +``` + +After: + +```tsx +const options = [ + { value: '1', label: 'Option 1' }, + { value: '2', label: 'Option 2', description: 'BTC' }, +]; + +