@atlaskit/ads-mcp 0.13.4 → 0.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/ads-mcp
2
2
 
3
+ ## 0.13.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [`a51be92772dd1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a51be92772dd1) -
8
+ Improve Spotlight migration guides.
9
+
10
+ ## 0.13.5
11
+
12
+ ### Patch Changes
13
+
14
+ - [`6da0cd3dc1b0f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/6da0cd3dc1b0f) -
15
+ Improve i18n mcp for better detection on existing patterns and save tokens
16
+
3
17
  ## 0.13.4
4
18
 
5
19
  ### Patch Changes
@@ -9,7 +9,7 @@ exports.tokenStructuredContent = void 0;
9
9
  *
10
10
  * Structured content for tokens generated from token-metadata.
11
11
  *
12
- * @codegen <<SignedSource::71826c7e9b24620099bd0316f7a0a305>>
12
+ * @codegen <<SignedSource::44592b6377caa433142db7aab9f15940>>
13
13
  * @codegenCommand yarn build structured-docs
14
14
  */
15
15
 
@@ -2304,44 +2304,44 @@ var tokenStructuredContent = exports.tokenStructuredContent = [{
2304
2304
  description: 'For our brand body text. Uses Charlie Text.',
2305
2305
  exampleValue: '"Charlie Text", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif'
2306
2306
  }, {
2307
- content: '# radius.xsmall\n\nUsed for small containers such as badges.\n\nExample Value: `0.125rem`\n',
2307
+ content: '# radius.xsmall\n\nUse for small detail elements: badges, checkboxes, avatar labels, keyboard shortcuts.\n\nExample Value: `0.125rem`\n',
2308
2308
  name: 'radius.xsmall',
2309
- description: 'Used for small containers such as badges.',
2309
+ description: 'Use for small detail elements: badges, checkboxes, avatar labels, keyboard shortcuts.',
2310
2310
  exampleValue: '0.125rem'
2311
2311
  }, {
2312
- content: '# radius.small\n\nUsed for labels.\n\nExample Value: `0.25rem`\n',
2312
+ content: '# radius.small\n\nUse for supporting elements: labels, lozenges, timestamps, tags, dates, tooltip containers, imagery inside a table, compact buttons.\n\nExample Value: `0.25rem`\n',
2313
2313
  name: 'radius.small',
2314
- description: 'Used for labels.',
2314
+ description: 'Use for supporting elements: labels, lozenges, timestamps, tags, dates, tooltip containers, imagery inside a table, compact buttons.',
2315
2315
  exampleValue: '0.25rem'
2316
2316
  }, {
2317
- content: '# radius.medium\n\nUsed for buttons and inputs.\n\nExample Value: `0.375rem`\n',
2317
+ content: '# radius.medium\n\nUse for interactive elements: buttons, inputs, text areas, selects, navigation items, smart links.\n\nExample Value: `0.375rem`\n',
2318
2318
  name: 'radius.medium',
2319
- description: 'Used for buttons and inputs.',
2319
+ description: 'Use for interactive elements: buttons, inputs, text areas, selects, navigation items, smart links.',
2320
2320
  exampleValue: '0.375rem'
2321
2321
  }, {
2322
- content: '# radius.large\n\nUsed for cards and small containers.\n\nExample Value: `0.5rem`\n',
2322
+ content: '# radius.large\n\nUse for containment elements: cards, in-page containers, floating UI, dropdown menus.\n\nExample Value: `0.5rem`\n',
2323
2323
  name: 'radius.large',
2324
- description: 'Used for cards and small containers.',
2324
+ description: 'Use for containment elements: cards, in-page containers, floating UI, dropdown menus.',
2325
2325
  exampleValue: '0.5rem'
2326
2326
  }, {
2327
- content: '# radius.xlarge\n\nUsed for modals and large containers.\n\nExample Value: `0.75rem`\n',
2327
+ content: '# radius.xlarge\n\nUse for large page elements: full-page containers, large containers, modals, Kanban columns, tables.\n\nExample Value: `0.75rem`\n',
2328
2328
  name: 'radius.xlarge',
2329
- description: 'Used for modals and large containers.',
2329
+ description: 'Use for large page elements: full-page containers, large containers, modals, Kanban columns, tables.',
2330
2330
  exampleValue: '0.75rem'
2331
2331
  }, {
2332
- content: '# radius.xxlarge\n\nUsed for the largest containers.\n\nExample Value: `1rem`\n',
2332
+ content: '# radius.xxlarge\n\nUse for video player containers.\n\nExample Value: `1rem`\n',
2333
2333
  name: 'radius.xxlarge',
2334
- description: 'Used for the largest containers.',
2334
+ description: 'Use for video player containers.',
2335
2335
  exampleValue: '1rem'
2336
2336
  }, {
2337
- content: '# radius.full\n\nUsed for circular containers, like a rounded button.\n\nExample Value: `624.9375rem`\n',
2337
+ content: '# radius.full\n\nUse for circular elements (user/people related): avatars, names, user-related UI, emoji reactions.\n\nExample Value: `624.9375rem`\n',
2338
2338
  name: 'radius.full',
2339
- description: 'Used for circular containers, like a rounded button.',
2339
+ description: 'Use for circular elements (user/people related): avatars, names, user-related UI, emoji reactions.',
2340
2340
  exampleValue: '624.9375rem'
2341
2341
  }, {
2342
- content: '# radius.tile\n\nUsed for tiles only.\n\nExample Value: `25%`\n',
2342
+ content: '# radius.tile\n\nUse this specific radius token exclusively for the tile component system.\n\nExample Value: `25%`\n',
2343
2343
  name: 'radius.tile',
2344
- description: 'Used for tiles only.',
2344
+ description: 'Use this specific radius token exclusively for the tile component system.',
2345
2345
  exampleValue: '25%'
2346
2346
  }, {
2347
2347
  content: '# border.width\n\nThe default width for all standard component borders and dividers.\n\nExample Value: `0.0625rem`\n',
@@ -11,42 +11,48 @@ exports.i18nConversionGuide = void 0;
11
11
  var i18nConversionGuide = exports.i18nConversionGuide = {
12
12
  id: 'hardcoded-string-to-formatmessage',
13
13
  title: 'Hardcoded String to formatMessage Conversion Guide',
14
- description: 'Comprehensive guide for converting hardcoded strings to use formatMessage from @atlassian/jira-intl or react-intl-next',
15
- purpose: 'This guide instructs LLM agents to convert hardcoded strings to use formatMessage. The import source depends on the file path: use @atlassian/jira-intl for Jira files (path contains "jira"), and react-intl-next for any non-Jira files. The goal is to find all hardcoded strings in JSX and convert them to internationalized messages.',
16
- scope: "**CRITICAL SCOPE**: This process should **ONLY** focus on converting hardcoded strings (literal strings in JSX, eslint-disable comments for no-literal-string-in-jsx, etc.) to use formatMessage. Do NOT modify pre-existing messages that were already in the codebase, even if they have poor descriptions, incorrect placeholder names, or other quality issues. Only convert NEW hardcoded strings.\n\n**PATH SCOPE LIMITATION**: If a specific file, package name, or path is provided, **ONLY** find and convert hardcoded strings within that specified path. Do NOT modify files outside the provided scope.\n\n**CRITICAL: FIX ALL VIOLATIONS IN FILE**: When a specific file is mentioned (even with line numbers like `@file.tsx:139-142`), you MUST find and fix **ALL** @atlassian/i18n/no-literal-string-in-jsx violations in that entire file, not just the specific lines mentioned. Scan the entire file for hardcoded strings and convert them all. The line numbers are just a reference point - the goal is to fix all i18n violations in the provided file.\n\n**CRITICAL: STRING FILTERING**: When finding eslint-disable comments, you MUST examine the actual string content. Only convert strings that contain **user-facing English text**. Many English strings are technical/non-user-facing and should NOT be converted (e.g., product names like \"Jira\", URLs like \"https://example.com\", technical IDs, symbols). Use the ESLint ignore patterns to identify which English strings to skip - strings matching ignore patterns should be LEFT AS-IS with their eslint-disable comments intact.",
17
- implementationChecklist: ['Create message constants using defineMessage (singular) at the top of the file - use defineMessage for each individual message, NOT defineMessages', 'Import: Use "@atlassian/jira-intl" for Jira files or "react-intl-next" for non-Jira files', '**CRITICAL: i18n ID Format**: For files in `/next/packages/`, i18n ids MUST start with the package name (with dashes). Format: `{package-name}.{component-or-feature}.{message-key}`. Example: For package `comment-extension-handlers`, use `comment-extension-handlers.legacy-content-modal.close-button`. For package `rovo-ai-search`, use `rovo-ai-search.view-profile-text` or `rovo-ai-search.knowledge-cards.copy-email-address`.', '**CRITICAL: ai-non-final Suffix**: ALL new message IDs MUST end with `.ai-non-final` suffix. This applies to ALL newly created messages, regardless of whether existing messages in the file have this suffix. Format: `{message-key}.ai-non-final`. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. This suffix indicates the message is AI-generated and may need review before finalization.', 'Add useIntl hook: const { formatMessage } = useIntl();', 'Replace hardcoded strings with formatMessage(messageKey)', 'Add descriptions (40+ chars, unique from defaultMessage) and descriptive placeholder names', 'Remove eslint-disable for no-literal-string-in-jsx ONLY after converting', '**VERIFICATION**: To verify all hardcoded strings are fixed, search for `@atlassian/i18n/no-literal-string-in-jsx` in the file. Do NOT run eslint - just search for the eslint-disable comments. If no matches are found, all hardcoded strings have been converted.'],
14
+ description: 'Comprehensive guide for converting hardcoded strings to use formatMessage or FormattedMessage from @atlassian/jira-intl or react-intl-next',
15
+ purpose: 'This guide instructs LLM agents to convert hardcoded strings to use formatMessage or FormattedMessage. The import source depends on the file path: use @atlassian/jira-intl for Jira files (path contains "jira"), and react-intl-next for any non-Jira files. **CRITICAL: Check for existing imports** - If the file already imports from react-intl-next or react-intl, reuse that import. If the file already uses FormattedMessage, use FormattedMessage for consistency instead of useIntl + formatMessage. The goal is to find all hardcoded strings in JSX and convert them to internationalized messages.',
16
+ scope: "**CRITICAL SCOPE**: This process should **ONLY** focus on converting hardcoded strings (literal strings in JSX, eslint-disable comments for no-literal-string-in-jsx, etc.) to use formatMessage. Do NOT modify pre-existing messages that were already in the codebase, even if they have poor descriptions, incorrect placeholder names, or other quality issues. Only convert NEW hardcoded strings.\n\n**PATH SCOPE LIMITATION**: If a specific file, package name, or path is provided, **ONLY** find and convert hardcoded strings within that specified path. Do NOT modify files outside the provided scope.\n\n**CRITICAL: ONLY CONVERT STRINGS WITH ESLINT-DISABLE**: You MUST **ONLY** convert strings that have eslint-disable comments for @atlassian/i18n/no-literal-string-in-jsx. Do NOT proactively convert strings that don't have these comments. For example, field labels like `label=\"Space Name\"` that don't have eslint-disable comments should be LEFT AS-IS - they may be acceptable as-is or intentionally not internationalized. Only convert strings that explicitly have the eslint-disable comment indicating they need to be fixed.\n\n**CRITICAL: FIX ALL VIOLATIONS IN FILE**: When a specific file is mentioned (even with line numbers like `@file.tsx:139-142`), you MUST find and fix **ALL** @atlassian/i18n/no-literal-string-in-jsx violations in that entire file, not just the specific lines mentioned. Scan the entire file for hardcoded strings WITH eslint-disable comments and convert them all. The line numbers are just a reference point - the goal is to fix all i18n violations (those with eslint-disable comments) in the provided file.\n\n**CRITICAL: STRING FILTERING**: When finding eslint-disable comments, you MUST examine the actual string content. Only convert strings that contain **user-facing English text**. Many English strings are technical/non-user-facing and should NOT be converted (e.g., product names like \"Jira\", URLs like \"https://example.com\", technical IDs, symbols). Use the ESLint ignore patterns to identify which English strings to skip - strings matching ignore patterns should be LEFT AS-IS with their eslint-disable comments intact.",
17
+ implementationChecklist: ['**CRITICAL: Only convert strings with eslint-disable** - ONLY convert strings that have eslint-disable comments for @atlassian/i18n/no-literal-string-in-jsx. Do NOT convert strings without these comments (e.g., field labels like label="Space Name" without eslint-disable should remain as-is).', '**CRITICAL: Check for existing imports** - If the file already imports from react-intl-next or react-intl, REUSE that import. Only add a new import if none exists.', '**CRITICAL: Match existing pattern** - If the file already uses FormattedMessage, use FormattedMessage with inline props. If it uses useIntl + formatMessage, use defineMessage + formatMessage. Match the existing code style.', "**CRITICAL: When to use formatMessage() vs <FormattedMessage>** - Use formatMessage() for prop values (labels, placeholders, aria-labels), computed values, event handlers, and non-JSX contexts. Use <FormattedMessage> for JSX content where you'd otherwise write {formatMessage(...)}.", 'Import: Use "@atlassian/jira-intl" for Jira files or "react-intl-next" for non-Jira files. Note: "react-intl" and "react-intl-next" are treated the same way - both use the same API. When adding to existing imports, maintain alphabetical order: import { defineMessage, FormattedMessage } from "react-intl-next";', '**CRITICAL: FormattedMessage pattern** - When using FormattedMessage, define message details directly inline: <FormattedMessage id="..." defaultMessage="..." description="..." />. NO need to use defineMessage - FormattedMessage accepts these props directly.', '**CRITICAL: formatMessage pattern** - When using useIntl + formatMessage, create message constants using defineMessage (singular) at the top of the file - use defineMessage for each individual message, NOT defineMessages. Then use: const { formatMessage } = useIntl(); formatMessage(messageKey)', '**CRITICAL: i18n ID Format**: For files in `/next/packages/`, i18n ids MUST start with the package name (with dashes). Format: `{package-name}.{component-or-feature}.{message-key}`. Example: For package `comment-extension-handlers`, use `comment-extension-handlers.legacy-content-modal.close-button`. For package `rovo-ai-search`, use `rovo-ai-search.view-profile-text` or `rovo-ai-search.knowledge-cards.copy-email-address`.', '**CRITICAL: ai-non-final Suffix**: ALL new message IDs MUST end with `.ai-non-final` suffix. This applies to ALL newly created messages, regardless of whether existing messages in the file have this suffix. Format: `{message-key}.ai-non-final`. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. This suffix indicates the message is AI-generated and may need review before finalization.', 'Replace hardcoded strings with either formatMessage(messageKey) or <FormattedMessage id="..." defaultMessage="..." description="..." /> based on existing pattern', '**CRITICAL: Write comprehensive descriptions** - Descriptions are instructions for translators. Include: (1) Where text is shown and the UI element (e.g., "on a button", "as an error message", "as a title", "for a link", "as a drop-down item"), (2) What it does, triggers, or prompts, (3) If there is a placeholder, explain how it will be substituted and with what, (4) If there is an ICU MessageFormat statement (not a placeholder!), provide instruction on the resolved ICU message, not on the ICU structure unless there are more than 2 plural statements, (5) Explain the purpose of the sentence in defaultMessage, (6) Propose an alternative text in Plain English only if the text contains an idiom. Descriptions must be 40+ chars and unique from defaultMessage.', 'Remove eslint-disable for no-literal-string-in-jsx ONLY after converting', '**VERIFICATION**: To verify all hardcoded strings are fixed, search for `@atlassian/i18n/no-literal-string-in-jsx` in the file. Do NOT run eslint - just search for the eslint-disable comments. If no matches are found, all hardcoded strings have been converted.'],
18
18
  patterns: [{
19
19
  title: 'Button Text and Action Labels',
20
20
  description: 'Converting hardcoded button text with variables',
21
21
  before: "<Button style={styles} onClick={onClearVersion}>\n Exit {description.join(' ')}\n</Button>",
22
- after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst exitButton = defineMessage({\n id: 'rovo-ai-search.exit-button.ai-non-final',\n defaultMessage: 'Exit {version}',\n description: 'The text is shown as a button when the user needs to exit the version view. The placeholder {version} will be substituted with the version name. Used in the version header to return to the main view.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Button style={styles} onClick={onClearVersion}>\n {formatMessage(exitButton, { version: description.join(' ') })}\n </Button>\n );\n}",
22
+ after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst exitButton = defineMessage({\n id: 'rovo-ai-search.exit-button.ai-non-final',\n defaultMessage: 'Exit {version}',\n\tdescription: 'This is the text on a button. The placeholder {version} will be substituted with the version name (e.g., \"Exit v2.0\"). After a user clicks on the button, it will exit the version view and return to the main view. Appears in the version header.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Button style={styles} onClick={onClearVersion}>\n {formatMessage(exitButton, { version: description.join(' ') })}\n </Button>\n );\n}",
23
23
  explanation: 'Convert button text with variables to use placeholders in the message. Use descriptive placeholder names that match the variable context.'
24
- }, {
25
- title: 'Loading States and Status Messages',
26
- description: 'Converting hardcoded loading and status messages',
27
- before: "return <Box>Loading...</Box>;",
28
- after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst loading = defineMessage({\n id: 'rovo-ai-search.loading.ai-non-final',\n defaultMessage: 'Loading...',\n description: 'The text is shown as a loading indicator when work item data is being fetched from the server. Appears in the work item tab to inform users that content is loading.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box>\n {formatMessage(loading)}\n </Box>\n );\n}",
29
- explanation: 'Convert simple hardcoded status messages to use defineMessage (singular). Use defineMessage for each individual message, NOT defineMessages. Ensure descriptions explain when and where the message appears.'
30
24
  }, {
31
25
  title: 'Partial String Translation (Prefixes and Labels)',
32
26
  description: 'Translating only part of a string while preserving UI structure',
33
27
  before: "<Box xcss={styles}>\n <span>Status: {selectedOptions[0]?.label}</span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n</Box>",
34
- after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst statusLabel = defineMessage({\n id: 'filter.status-label.ai-non-final',\n defaultMessage: 'Status: {status}',\n description: 'The text is shown as a button label prefix for the status filter. The placeholder {status} will be substituted with the selected status label. Appears in the filter button to show the current filter state.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box xcss={styles}>\n <span>\n {formatMessage(statusLabel, {\n status: selectedOptions[0]?.label,\n })}\n </span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n </Box>\n );\n}",
28
+ after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst statusLabel = defineMessage({\n id: 'filter.status-label.ai-non-final',\n defaultMessage: 'Status: {status}',\n\tdescription: 'This is the text shown as a button label prefix for the status filter. The placeholder {status} will be substituted with the selected status label (e.g., \"Status: In Progress\"). Appears in the filter button to show the current filter state.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Box xcss={styles}>\n <span>\n {formatMessage(statusLabel, {\n status: selectedOptions[0]?.label,\n })}\n </span>\n {selectedOptions.length > 1 && (\n <Badge appearance=\"primary\" max={99}>\n +{selectedOptions.length - 1}\n </Badge>\n )}\n </Box>\n );\n}",
35
29
  explanation: 'When only part of a string needs translation (like a prefix "Status: " or label), create a message with a placeholder for the dynamic part. Preserve the existing UI structure (components, conditional rendering, etc.) and only translate the literal string portion.'
36
30
  }, {
37
31
  title: 'Dynamic Text Content',
38
32
  description: 'Converting conditional hardcoded strings',
39
33
  before: "<Text>{state.hasMore ? 'Show more' : 'Show less'}</Text>",
40
- after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst showMore = defineMessage({\n id: 'work-item.comments.show-more.ai-non-final',\n defaultMessage: 'Show more',\n description: 'The text is shown as a button when there are more items available to display. Used in the comments section footer to expand and show additional content.',\n});\n\nconst showLess = defineMessage({\n id: 'work-item.comments.show-less.ai-non-final',\n defaultMessage: 'Show less',\n description: 'The text is shown as a button when the user wants to collapse the expanded view. Used in the comments section footer to hide additional content.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Text>\n {state.hasMore\n ? formatMessage(showMore)\n : formatMessage(showLess)}\n </Text>\n );\n}",
34
+ after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst showMore = defineMessage({\n id: 'work-item.comments.show-more.ai-non-final',\n defaultMessage: 'Show more',\n\tdescription: 'This is the text on a button. It appears when there are more items available to display. After a user clicks on the button, it will expand the view to show additional content. Used in the comments section footer.',\n});\n\nconst showLess = defineMessage({\n id: 'work-item.comments.show-less.ai-non-final',\n defaultMessage: 'Show less',\n\tdescription: 'This is the text on a button. It appears when the user wants to collapse the expanded view. After a user clicks on the button, it will hide the additional content that was previously expanded. Used in the comments section footer.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <Text>\n {state.hasMore\n ? formatMessage(showMore)\n : formatMessage(showLess)}\n </Text>\n );\n}",
41
35
  explanation: 'Create separate message constants for each conditional string. Use descriptive names that reflect the context.'
42
36
  }, {
43
37
  title: 'ICU Format for Numeric Values',
44
38
  description: 'Converting numeric placeholders to ICU plural format',
45
39
  before: "const moreMessage = `+ ${count} more`;",
46
- after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst moreProjectsMessage = defineMessage({\n id: 'app.more-projects.ai-non-final',\n defaultMessage: '{count, plural, one {+ # more} other {+ # more}}',\n description: 'The text is shown when there are additional projects available. The placeholder {count} will be substituted with the number of remaining projects. Uses ICU plural format for proper localization.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n const count = 5;\n\n return (\n <Text>\n {formatMessage(moreProjectsMessage, { count })}\n </Text>\n );\n}",
40
+ after: "import { useIntl, defineMessage } from '@atlassian/jira-intl';\n\nconst moreProjectsMessage = defineMessage({\n id: 'app.more-projects.ai-non-final',\n defaultMessage: '{count, plural, one {+ # more} other {+ # more}}',\n\tdescription: 'This is the text shown when there are additional projects available. The variable {count} will be substituted with the number of remaining projects, and the message will display accordingly (e.g., \"+ 5 more\" or \"+ 1 more\"). Appears to indicate how many more items can be viewed.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n const count = 5;\n\n return (\n <Text>\n {formatMessage(moreProjectsMessage, { count })}\n </Text>\n );\n}",
47
41
  explanation: 'Use ICU plural format for numeric values that may need pluralization. The # symbol represents the numeric value in ICU format.'
42
+ }, {
43
+ title: 'Using FormattedMessage',
44
+ description: 'Reusing existing FormattedMessage pattern and using with placeholders',
45
+ before: "import { FormattedMessage } from 'react-intl-next';\n\nexport function MyComponent() {\n const version = 'v2.0';\n return (\n <Box>\n <FormattedMessage {...existingMessage} />\n <Button>Save changes</Button>\n <Button>\n Exit {version}\n </Button>\n </Box>\n );\n}",
46
+ after: "import { FormattedMessage } from 'react-intl-next';\n\nexport function MyComponent() {\n const version = 'v2.0';\n return (\n <Box>\n <FormattedMessage {...existingMessage} />\n <Button>\n <FormattedMessage\n id=\"my-component.save-button.ai-non-final\"\n defaultMessage=\"Save changes\"\n description=\"This is the text on a button. After a user clicks on the button, it will initiate the form being submitted. Appears in the form footer to save changes.\"\n />\n </Button>\n <Button>\n <FormattedMessage\n id=\"my-component.exit-button.ai-non-final\"\n defaultMessage=\"Exit {version}\"\n description=\"This is the text on a button. The placeholder {version} will be substituted with the version name (e.g., \"Exit v2.0\"). After a user clicks on the button, it will exit the version view and return to the main view.\"\n values={{ version }}\n />\n </Button>\n </Box>\n );\n}",
47
+ explanation: 'When FormattedMessage is already used in the file, reuse the existing import. Define message details directly inline in FormattedMessage component props (id, defaultMessage, description). No need to use defineMessage - FormattedMessage accepts these props directly. For dynamic values, use the values prop - the placeholder names in defaultMessage must match the keys in the values object.'
48
+ }, {
49
+ title: 'formatMessage() vs <FormattedMessage> - When to Use Each',
50
+ description: 'Choosing between formatMessage() and FormattedMessage based on context',
51
+ before: "<Field name=\"spaceName\" label=\"Space Name\" placeholder=\"Enter space name\" />\n<Button aria-label=\"Create space\">Create space</Button>\n<Box>Welcome message</Box>",
52
+ after: "import { defineMessage, FormattedMessage, useIntl } from 'react-intl-next';\n\nconst spaceNameLabel = defineMessage({\n id: 'my-component.space-name-label.ai-non-final',\n defaultMessage: 'Space Name',\n description: 'This is the text shown as a label for the space name input field. Appears above the input field to identify what information should be entered.',\n});\n\nconst spaceNamePlaceholder = defineMessage({\n id: 'my-component.space-name-placeholder.ai-non-final',\n defaultMessage: 'Enter space name',\n description: 'This is the placeholder text shown inside the space name input field when it is empty. Provides guidance to users on what to enter in the field.',\n});\n\nconst createSpaceAriaLabel = defineMessage({\n id: 'my-component.create-space-aria-label.ai-non-final',\n defaultMessage: 'Create space',\n description: 'This is the accessible label for the create space button. Used by screen readers to announce the button purpose. The button will create a new space when clicked.',\n});\n\nexport function MyComponent() {\n const { formatMessage } = useIntl();\n\n return (\n <>\n <Field\n name=\"spaceName\"\n label={formatMessage(spaceNameLabel)}\n placeholder={formatMessage(spaceNamePlaceholder)}\n />\n <Button aria-label={formatMessage(createSpaceAriaLabel)}>\n <FormattedMessage\n id=\"my-component.create-space-button.ai-non-final\"\n defaultMessage=\"Create space\"\n description=\"This is the text on a button. After a user clicks on the button, it will create a new space. Appears as the primary action button in the form.\"\n />\n </Button>\n <Box>\n <FormattedMessage\n id=\"my-component.welcome-message.ai-non-final\"\n defaultMessage=\"Welcome message\"\n description=\"This is the text shown as a welcome message. Appears in the main content area to greet users when they first visit the page.\"\n />\n </Box>\n </>\n );\n}",
53
+ explanation: "Use formatMessage() for prop values (labels, placeholders, aria-labels), computed values, event handlers, and non-JSX contexts. Use <FormattedMessage> for JSX content where you'd otherwise write {formatMessage(...)}. In this example, label and placeholder props use formatMessage(), while button text and content use FormattedMessage."
48
54
  }],
49
- bestPractices: ['Only convert NEW hardcoded strings - do NOT modify pre-existing messages', '**CRITICAL: Use defineMessage (singular)** - Always use defineMessage for each individual message, NOT defineMessages (plural). Each message should be defined separately: `const messageKey = defineMessage({ ... })`', '**CRITICAL: Always use .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Format: `{message-key}.ai-non-final`. This indicates the message is AI-generated and may need review.', 'Use descriptive names (userNameLabel not label) and placeholders (userName not value)', 'Add descriptions (40+ chars, unique from defaultMessage)', 'Use ICU plural format for numeric values: {count, plural, one {...} other {...}}', 'Place message constants at top of file, use formatMessage consistently', 'Convert arrow functions to body form if needed for hooks'],
50
- commonPitfalls: ['**CRITICAL**: Converting/removing eslint-disable for strings matching ignore patterns - technical/non-user-facing strings should remain hardcoded', '**CRITICAL: Invalid i18n ID format** - For files in `/next/packages/`, must follow `{package-name}.{component-or-feature}.{message-key}`. Common errors: (1) Not starting with package name (with dashes), (2) Missing component/feature segment, (3) Wrong message key format (must be kebab-case, not camelCase). Example correct: `comment-extension-handlers.legacy-content-modal.close-button`.', '**CRITICAL: Missing .ai-non-final suffix** - ALL new message IDs MUST end with `.ai-non-final`. This applies even if existing messages in the file don\'t have this suffix. Example: `applinks.administration.list.applinks-table.system-label.ai-non-final`. Do NOT omit this suffix even when existing messages don\'t have it.', '**CRITICAL: Using defineMessages instead of defineMessage** - Always use defineMessage (singular) for individual messages, NOT defineMessages (plural). Each message should be defined separately using defineMessage.', 'Using wrong import path - "jira" path needs @atlassian/jira-intl (with eslint-disable), others use react-intl-next', 'Adding eslint-disable for defineMessage in non-Jira files or forgetting it in Jira files', 'Missing useIntl hook, generic placeholder names, or descriptions < 40 chars', 'Modifying pre-existing messages or working outside specified path scope'],
55
+ bestPractices: ['Use descriptive names (userNameLabel not label) and placeholders (userName not value)', 'Use ICU plural format for numeric values: {count, plural, one {...} other {...}}', 'When using formatMessage pattern: Place message constants at top of file. When using FormattedMessage pattern: Define messages inline in JSX.', "Use formatMessage() for prop values (labels, placeholders, aria-labels), computed values, event handlers, and non-JSX contexts. Use <FormattedMessage> for JSX content where you'd otherwise write {formatMessage(...)}.", 'Convert arrow functions to body form if needed for hooks'],
56
+ commonPitfalls: ["**CRITICAL**: Converting strings that don't have eslint-disable comments - only convert strings with @atlassian/i18n/no-literal-string-in-jsx eslint-disable comments. Field labels and other strings without these comments should remain as-is.", '**CRITICAL**: Converting/removing eslint-disable for strings matching ignore patterns - technical/non-user-facing strings should remain hardcoded', '**CRITICAL**: Forgetting to remove eslint-disable comments after conversion - once a string is converted to use formatMessage/FormattedMessage, the eslint-disable comment must be removed', 'Using wrong import path - "jira" path needs @atlassian/jira-intl (with eslint-disable), others use react-intl-next', 'Adding eslint-disable for defineMessage in non-Jira files or forgetting it in Jira files', 'Missing useIntl hook when using formatMessage pattern, generic placeholder names, descriptions < 40 chars, or descriptions that do not follow the translator guidelines (missing UI element location, missing action explanation, etc.)', 'Using placeholder names that don\'t match variable names - placeholder names in defaultMessage must exactly match the keys in the values object or formatMessage parameters', 'Modifying pre-existing messages or working outside specified path scope'],
51
57
  additionalResources: ['ESLint rule @atlassian/i18n/no-literal-string-in-jsx defines ignore patterns for technical strings (URLs, product names, symbols, etc.)']
52
58
  };
@@ -3,8 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.onboardingWithMotion = exports.onboardingSingleStep = exports.onboardingMultiStep = void 0;
7
- var additionalResources = "Visit https://hello.atlassian.net/wiki/spaces/DST/pages/6069774593 or https://atlassian.design/components/spotlight for more context";
6
+ exports.onboardingWithMotion = exports.onboardingSingleStep = exports.onboardingMultiStep = exports.onboardingJiraSpotlight = void 0;
7
+ var additionalResources = 'Visit https://hello.atlassian.net/wiki/spaces/DST/pages/6069774593 or https://atlassian.design/components/spotlight for more context';
8
8
  var onboardingSingleStep = exports.onboardingSingleStep = {
9
9
  id: 'onboarding-single-step',
10
10
  title: 'Single Step Spotlight Migration',
@@ -16,9 +16,25 @@ var onboardingSingleStep = exports.onboardingSingleStep = {
16
16
  description: 'Replace SpotlightManager, SpotlightTarget, SpotlightTransition, and Spotlight with the new compositional @atlaskit/spotlight components',
17
17
  before: "import React, { useState } from 'react';\nimport Button from '@atlaskit/button/new';\nimport {\n Spotlight,\n SpotlightManager,\n SpotlightTarget,\n SpotlightTransition,\n} from '@atlaskit/onboarding';\n\nconst OnboardingSpotlight = () => {\n const [isSpotlightActive, setIsSpotlightActive] = useState(false);\n const start = () => setIsSpotlightActive(true);\n const end = () => setIsSpotlightActive(false);\n\n return (\n <SpotlightManager>\n <SpotlightTarget name=\"my-target\">\n <Button>Target Element</Button>\n </SpotlightTarget>\n <div>\n <Button appearance=\"primary\" onClick={start}>\n Show spotlight\n </Button>\n </div>\n <SpotlightTransition>\n {isSpotlightActive && (\n <Spotlight\n actions={[\n {\n onClick: end,\n text: 'Got it',\n },\n ]}\n heading=\"Feature Heading\"\n target=\"my-target\"\n key=\"my-target\"\n >\n This is the spotlight body content describing the feature.\n </Spotlight>\n )}\n </SpotlightTransition>\n </SpotlightManager>\n );\n};",
18
18
  after: "import React, { useState } from 'react';\nimport Button from '@atlaskit/button/new';\nimport { Text } from '@atlaskit/primitives/compiled';\nimport {\n PopoverContent,\n PopoverProvider,\n PopoverTarget,\n SpotlightActions,\n SpotlightBody,\n SpotlightCard,\n SpotlightControls,\n SpotlightDismissControl,\n SpotlightFooter,\n SpotlightHeader,\n SpotlightHeadline,\n SpotlightPrimaryAction,\n} from '@atlaskit/spotlight';\n\nconst Spotlight = () => {\n const [isVisible, setIsVisible] = useState(false);\n const dismiss = () => setIsVisible(false);\n const done = () => setIsVisible(false);\n\n return (\n <PopoverProvider>\n <PopoverTarget>\n <Button onClick={() => setIsVisible(true)}>Target Element</Button>\n </PopoverTarget>\n <PopoverContent dismiss={dismiss} placement=\"bottom-start\" isVisible={isVisible}>\n <SpotlightCard>\n <SpotlightHeader>\n <SpotlightHeadline>Feature Heading</SpotlightHeadline>\n <SpotlightControls>\n <SpotlightDismissControl />\n </SpotlightControls>\n </SpotlightHeader>\n <SpotlightBody>\n <Text>This is the spotlight body content describing the feature.</Text>\n </SpotlightBody>\n <SpotlightFooter>\n <SpotlightActions>\n <SpotlightPrimaryAction onClick={done}>Got it</SpotlightPrimaryAction>\n </SpotlightActions>\n </SpotlightFooter>\n </SpotlightCard>\n </PopoverContent>\n </PopoverProvider>\n );\n};",
19
- explanation: "Key changes when migrating a single step spotlight:\n1. Replace SpotlightManager with PopoverProvider - the new context provider\n2. Replace SpotlightTarget with PopoverTarget - wraps the element to highlight\n3. Replace SpotlightTransition and Spotlight with PopoverContent containing SpotlightCard - controls visibility and positioning\n4. The 'heading' prop becomes SpotlightHeadline inside SpotlightHeader\n5. The 'actions' array becomes SpotlightActions with SpotlightPrimaryAction (and optionally SpotlightSecondaryAction)\n6. The children content moves into SpotlightBody wrapped with Text component\n7. Add SpotlightDismissControl inside SpotlightControls for the close button\n8. The 'target' prop is no longer needed - PopoverTarget automatically handles this\n9. The 'dialogPlacement' prop becomes 'placement' on PopoverContent (e.g., 'bottom left' \u2192 'bottom-start')"
19
+ explanation: "Key changes when migrating a single step spotlight:\n- PopoverProvider maintains internal Spotlight state. SpotlightManager coordinated multiple @atlaskit/onboarding usages and is no longer needed.\n- Replace SpotlightTarget with PopoverTarget - wraps the element to highlight\n- Replace Spotlight with PopoverContent containing SpotlightCard - controls visibility and positioning\n- The 'heading' prop becomes SpotlightHeadline inside SpotlightHeader\n- The 'actions' array becomes SpotlightActions with SpotlightPrimaryAction (and optionally SpotlightSecondaryAction)\n- The children content moves into SpotlightBody wrapped with Text component\n- Add SpotlightDismissControl inside SpotlightControls for the close button\n- The 'target' and/or 'targetName' prop is replaced with PopoverTarget directly wrapping the target element\n- The 'dialogPlacement' prop becomes 'placement' on PopoverContent. Mapping: \"top-right\" \u2192 \"top-start\", \"top-center\" \u2192 \"top\", \"top-left\" \u2192 \"top-end\", \"right-bottom\" \u2192 \"right-start\", \"right-middle\" \u2192 \"right-start | right-end\", \"right-top\" \u2192 \"right-end\", \"bottom-left\" \u2192 \"bottom-end\", \"bottom-center\" \u2192 \"bottom\", \"bottom-right\" \u2192 \"bottom-start\", \"left-top\" \u2192 \"left-end\", \"left-middle\" \u2192 \"left-start | left-end\", \"left-bottom\" \u2192 \"left-start\"'"
20
20
  }],
21
- bestPractices: ['Use PopoverProvider as the root wrapper for spotlight functionality', 'PopoverTarget should wrap exactly one child element that will be highlighted', 'Always include SpotlightDismissControl for accessibility - allows users to dismiss via close button', 'Use SpotlightPrimaryAction for the main call-to-action button', 'Wrap body text content in the Text component from @atlaskit/primitives/compiled', 'Map old dialogPlacement values: "bottom left" → "bottom-start", "bottom center" → "bottom", "bottom right" → "bottom-end"'],
21
+ bestPractices: ['PopoverTarget should wrap exactly one child element that will be highlighted', 'Always include SpotlightDismissControl for accessibility - allows users to dismiss via close button', 'SpotlightPrimaryAction is required and wraps the main CTA button', 'Wrap body text content in the Text component from @atlaskit/primitives/compiled'],
22
+ additionalResources: additionalResources
23
+ };
24
+ var onboardingJiraSpotlight = exports.onboardingJiraSpotlight = {
25
+ id: 'onboarding-jira-spotlight',
26
+ title: 'JiraSpotlight Migration',
27
+ description: 'Migrate a <JiraSpotlight /> from @atlaskit/onboarding to @atlaskit/spotlight',
28
+ fromPackage: '@atlassian/jira-spotlight',
29
+ toPackage: '@atlaskit/spotlight',
30
+ examples: [{
31
+ title: 'Migrate <JiraSpotlight />',
32
+ description: 'Replace <JiraSpotlight> with <ChoreographedComponent> and migrate children to @atlaskit/spotlight.',
33
+ before: "\nimport { JiraSpotlight } from '@atlassian/jira-spotlight/src/ui/jira-spotlight.tsx';\nimport { SpotlightTarget, SpotlightTransition } from '@atlaskit/onboarding';\n\nexport const OnboardingSpotlightWrapper = () => {\n\tconst { dark, light } = spotlightImageUrls[spotlightId];\n\tconst imageUrl = colorMode === 'dark' ? dark : light;\n\n\tconst [isSpotlightVisible, actions] = useListViewOnboarding({\n\t\tprojectId: String(projectData.id),\n\t\tid: spotlightId,\n\t});\n\n\tif (isSpotlightVisible) {\n\t\treturn (\n\t\t\t<>\n\t\t\t\t<SpotlightTarget name={spotlightId}>{renderTrigger(isSpotlightVisible)}</SpotlightTarget>\n\t\t\t\t<SpotlightTransition>\n\t\t\t\t\t<JiraSpotlight\n\t\t\t\t\t\timage={imageUrl}\n\t\t\t\t\t\tactions={[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tonClick,\n\t\t\t\t\t\t\t\ttext: formatMessage(dismiss),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]}\n\t\t\t\t\t\theading={formatMessage(heading)}\n\t\t\t\t\t\ttarget={spotlightId}\n\t\t\t\t\t\tkey={spotlightId}\n\t\t\t\t\t\ttargetRadius={3}\n\t\t\t\t\t\ttargetBgColor={token('elevation.surface')}\n\t\t\t\t\t\tmessageId={spotlightId}\n\t\t\t\t\t\tmessageType=\"transactional\"\n\t\t\t\t\t\tdialogWidth={275}\n\t\t\t\t\t>\n\t\t\t\t\t\t{formatMessage(body)}\n\t\t\t\t\t</JiraSpotlight>\n\t\t\t\t</SpotlightTransition>\n\t\t\t</>\n\t\t);\n\t}\n}\n\t\t\t",
34
+ after: "\nimport { Text } from '@atlaskit/primitives/compiled';\nimport {\n PopoverContent,\n PopoverProvider,\n PopoverTarget,\n SpotlightActions,\n SpotlightBody,\n SpotlightCard,\n SpotlightControls,\n SpotlightDismissControl,\n SpotlightFooter,\n SpotlightHeader,\n SpotlightHeadline,\n SpotlightPrimaryAction,\n} from '@atlaskit/spotlight';\nimport { FadeIn } from '@atlaskit/motion';\nimport Image from '@atlaskit/image';\nimport { ChoreographedComponent } from '@atlassian/jira-spotlight/src/ui/ChoreographedComponent.tsx';\n\nexport const OnboardingSpotlightWrapper = () => {\n\tconst { dark, light } = spotlightImageUrls[spotlightId];\n\n\tconst [isSpotlightVisible, actions] = useListViewOnboarding({\n\t\tprojectId: String(projectData.id),\n\t\tid: spotlightId,\n\t});\n\n\treturn (\n\t\t<PopoverProvider>\n\t\t\t<PopoverTarget>{renderTrigger(isSpotlightVisible)}</PopoverTarget>\n\t\t\t<ChoreographedComponent messageId={spotlightId} messageType=\"transactional\">\n\t\t\t\t<PopoverContent isVisible={isSpotlightVisible} placement=\"bottom-start\" dismiss={onClick}>\n\t\t\t\t\t<FadeIn entranceDirection=\"left\">\n\t\t\t\t\t\t{(props) => (\n\t\t\t\t\t\t\t<div {...props}>\n\t\t\t\t\t\t\t\t<SpotlightCard>\n\t\t\t\t\t\t\t\t\t<SpotlightHeader>\n\t\t\t\t\t\t\t\t\t\t<SpotlightHeadline>{formatMessage(heading)}</SpotlightHeadline>\n\t\t\t\t\t\t\t\t\t\t<SpotlightControls>\n\t\t\t\t\t\t\t\t\t\t\t<SpotlightDismissControl />\n\t\t\t\t\t\t\t\t\t\t</SpotlightControls>\n\t\t\t\t\t\t\t\t\t</SpotlightHeader>\n\t\t\t\t\t\t\t\t\t<SpotlightMedia>\n\t\t\t\t\t\t\t\t\t\t<Image src={light} srcDark={dark} alt=\"\" />\n\t\t\t\t\t\t\t\t\t</SpotlightMedia>\n\t\t\t\t\t\t\t\t\t<SpotlightBody>\n\t\t\t\t\t\t\t\t\t\t<Text>{formatMessage(body)}</Text>\n\t\t\t\t\t\t\t\t\t</SpotlightBody>\n\t\t\t\t\t\t\t\t\t<SpotlightFooter>\n\t\t\t\t\t\t\t\t\t\t<SpotlightActions>\n\t\t\t\t\t\t\t\t\t\t\t<SpotlightPrimaryAction onClick={onClick}>\n\t\t\t\t\t\t\t\t\t\t\t\t{formatMessage(dismiss)}\n\t\t\t\t\t\t\t\t\t\t\t</SpotlightPrimaryAction>\n\t\t\t\t\t\t\t\t\t\t</SpotlightActions>\n\t\t\t\t\t\t\t\t\t</SpotlightFooter>\n\t\t\t\t\t\t\t\t</SpotlightCard>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</FadeIn>\n\t\t\t\t</PopoverContent>\n\t\t\t</ChoreographedComponent>\n\t\t</PopoverProvider>\n\t);\n};",
35
+ explanation: "Key changes when migrating a single step spotlight:\n- Replace JiraSpotlight with ChoreographedComponent from '@atlassian/jira-spotlight'.\n- PopoverProvider maintains internal Spotlight state. SpotlightManager coordinated multiple @atlaskit/onboarding usages and is no longer needed.\n- Replace SpotlightTarget with PopoverTarget - wraps the element to highlight\n- Replace Spotlight with PopoverContent containing SpotlightCard - controls visibility and positioning\n- The 'heading' prop becomes SpotlightHeadline inside SpotlightHeader\n- The 'actions' array becomes SpotlightActions with SpotlightPrimaryAction (and optionally SpotlightSecondaryAction)\n- The children content moves into SpotlightBody wrapped with Text component\n- Add SpotlightDismissControl inside SpotlightControls for the close button\n- The 'target' and/or 'targetName' prop is replaced with PopoverTarget directly wrapping the target element\n- The 'dialogPlacement' prop becomes 'placement' on PopoverContent. Mapping: \"top-right\" \u2192 \"top-start\", \"top-center\" \u2192 \"top\", \"top-left\" \u2192 \"top-end\", \"right-bottom\" \u2192 \"right-start\", \"right-middle\" \u2192 \"right-start | right-end\", \"right-top\" \u2192 \"right-end\", \"bottom-left\" \u2192 \"bottom-end\", \"bottom-center\" \u2192 \"bottom\", \"bottom-right\" \u2192 \"bottom-start\", \"left-top\" \u2192 \"left-end\", \"left-middle\" \u2192 \"left-start | left-end\", \"left-bottom\" \u2192 \"left-start\"'"
36
+ }],
37
+ bestPractices: ['PopoverTarget should wrap exactly one child element that will be highlighted', 'Always include SpotlightDismissControl for accessibility - allows users to dismiss via close button', 'SpotlightPrimaryAction is required and wraps the main CTA button', 'Wrap body text content in the Text component from @atlaskit/primitives/compiled'],
22
38
  additionalResources: additionalResources
23
39
  };
24
40
  var onboardingMultiStep = exports.onboardingMultiStep = {
@@ -32,9 +48,9 @@ var onboardingMultiStep = exports.onboardingMultiStep = {
32
48
  description: 'Replace the single SpotlightManager pattern with multiple PopoverProvider instances, one for each target in the tour',
33
49
  before: "import React, { useState } from 'react';\nimport Button, { IconButton } from '@atlaskit/button/new';\nimport CommentAddIcon from '@atlaskit/icon/core/comment-add';\nimport CopyIcon from '@atlaskit/icon/core/copy';\nimport {\n Spotlight,\n SpotlightManager,\n SpotlightTarget,\n SpotlightTransition,\n} from '@atlaskit/onboarding';\n\nconst OnboardingTour = () => {\n const [activeSpotlight, setActiveSpotlight] = useState<null | number>(null);\n const start = () => setActiveSpotlight(0);\n const next = () => setActiveSpotlight((activeSpotlight || 0) + 1);\n const back = () => setActiveSpotlight((activeSpotlight || 1) - 1);\n const end = () => setActiveSpotlight(null);\n\n const renderActiveSpotlight = () => {\n const spotlights = [\n <Spotlight\n actions={[\n { onClick: () => next(), text: 'Next' },\n { onClick: () => end(), text: 'Dismiss', appearance: 'subtle' },\n ]}\n heading=\"Add a comment\"\n target=\"comment\"\n key=\"comment\"\n >\n Quickly add a comment to the work item.\n </Spotlight>,\n <Spotlight\n actions={[\n { onClick: () => end(), text: 'Done' },\n { onClick: () => back(), text: 'Back', appearance: 'subtle' },\n ]}\n heading=\"Copy code\"\n target=\"copy\"\n key=\"copy\"\n >\n Click to copy the example code to your clipboard.\n </Spotlight>,\n ];\n\n if (activeSpotlight === null) {\n return null;\n }\n return spotlights[activeSpotlight];\n };\n\n return (\n <SpotlightManager>\n <SpotlightTarget name=\"comment\">\n <IconButton icon={CommentAddIcon} label=\"comment\" />\n </SpotlightTarget>\n <SpotlightTarget name=\"copy\">\n <IconButton icon={CopyIcon} label=\"Copy\" />\n </SpotlightTarget>\n <Button appearance=\"primary\" onClick={start}>\n Start tour\n </Button>\n <SpotlightTransition>{renderActiveSpotlight()}</SpotlightTransition>\n </SpotlightManager>\n );\n};",
34
50
  after: "import React, { useState } from 'react';\nimport Button, { IconButton } from '@atlaskit/button/new';\nimport CommentAddIcon from '@atlaskit/icon/core/comment-add';\nimport CopyIcon from '@atlaskit/icon/core/copy';\nimport { Text } from '@atlaskit/primitives/compiled';\nimport {\n PopoverContent,\n PopoverProvider,\n PopoverTarget,\n SpotlightActions,\n SpotlightBody,\n SpotlightCard,\n SpotlightControls,\n SpotlightDismissControl,\n SpotlightFooter,\n SpotlightHeader,\n SpotlightHeadline,\n SpotlightPrimaryAction,\n SpotlightSecondaryAction,\n SpotlightStepCount,\n} from '@atlaskit/spotlight';\n\nconst SpotlightTour = () => {\n const [currentStep, setCurrentStep] = useState<number>(0);\n\n const dismiss = () => setCurrentStep(0);\n const back = () => setCurrentStep(Math.max(currentStep - 1, 1));\n const next = () => setCurrentStep(Math.min(currentStep + 1, 2));\n const done = () => setCurrentStep(0);\n\n return (\n <>\n <PopoverProvider>\n <PopoverTarget>\n <IconButton icon={CommentAddIcon} label=\"comment\" />\n </PopoverTarget>\n <PopoverContent dismiss={dismiss} placement=\"bottom-start\" isVisible={currentStep === 1}>\n <SpotlightCard>\n <SpotlightHeader>\n <SpotlightHeadline>Add a comment</SpotlightHeadline>\n <SpotlightControls>\n <SpotlightDismissControl onClick={dismiss} />\n </SpotlightControls>\n </SpotlightHeader>\n <SpotlightBody>\n <Text>Quickly add a comment to the work item.</Text>\n </SpotlightBody>\n <SpotlightFooter>\n <SpotlightStepCount>1 of 2</SpotlightStepCount>\n <SpotlightActions>\n <SpotlightPrimaryAction onClick={next}>Next</SpotlightPrimaryAction>\n </SpotlightActions>\n </SpotlightFooter>\n </SpotlightCard>\n </PopoverContent>\n </PopoverProvider>\n\n <PopoverProvider>\n <PopoverTarget>\n <IconButton icon={CopyIcon} label=\"Copy\" />\n </PopoverTarget>\n <PopoverContent dismiss={dismiss} placement=\"bottom-start\" isVisible={currentStep === 2}>\n <SpotlightCard>\n <SpotlightHeader>\n <SpotlightHeadline>Copy code</SpotlightHeadline>\n <SpotlightControls>\n <SpotlightDismissControl onClick={dismiss} />\n </SpotlightControls>\n </SpotlightHeader>\n <SpotlightBody>\n <Text>Click to copy the example code to your clipboard.</Text>\n </SpotlightBody>\n <SpotlightFooter>\n <SpotlightStepCount>2 of 2</SpotlightStepCount>\n <SpotlightActions>\n <SpotlightSecondaryAction onClick={back}>Back</SpotlightSecondaryAction>\n <SpotlightPrimaryAction onClick={done}>Done</SpotlightPrimaryAction>\n </SpotlightActions>\n </SpotlightFooter>\n </SpotlightCard>\n </PopoverContent>\n </PopoverProvider>\n\n <Button appearance=\"primary\" onClick={() => setCurrentStep(1)}>\n Start tour\n </Button>\n </>\n );\n};",
35
- explanation: "Key changes when migrating a multi-step spotlight tour:\n1. Replace the single SpotlightManager with multiple PopoverProvider instances - one for each target element\n2. Each target gets its own PopoverProvider > PopoverTarget > PopoverContent structure\n3. The spotlight array pattern is replaced with individual SpotlightCard components per target\n4. Use a single currentStep state (starting at 0 for hidden, 1+ for active steps) instead of null/index\n5. Control visibility with isVisible={currentStep === n} on each PopoverContent\n6. Add SpotlightStepCount component in SpotlightFooter to show progress (e.g., \"1 of 3\")\n7. Use SpotlightSecondaryAction for \"Back\" buttons instead of appearance: 'subtle' in the actions array\n8. Use SpotlightPrimaryAction for \"Next\" and \"Done\" buttons\n9. The renderActiveSpotlight pattern is no longer needed - visibility is controlled declaratively\n10. Navigation functions use Math.max/Math.min to bound the step range safely"
51
+ explanation: "Key changes when migrating a multi-step spotlight tour:\n- SpotlightManager coordinated multiple spotlights in a tour. PopoverProvider manages internal state for a single spotlight.\n- Each target gets its own PopoverProvider > PopoverTarget > PopoverContent structure\n- The spotlight array pattern is replaced with individual SpotlightCard components per target\n- Control visibility with isVisible={currentStep === n} on each PopoverContent\n- Add SpotlightStepCount component in SpotlightFooter to show progress (e.g., \"1 of 3\")\n- Use SpotlightSecondaryAction for \"Back\" buttons instead of appearance: 'subtle' in the actions array\n- Use SpotlightPrimaryAction for \"Next\" and \"Done\" buttons\n- The renderActiveSpotlight pattern is no longer needed - visibility is controlled declaratively\n- Navigation functions use Math.max/Math.min to bound the step range safely\n- All other migration changes from single step spotlight apply(PopoverProvider, compositional components, etc.)"
36
52
  }],
37
- bestPractices: ['Each target element in a tour needs its own PopoverProvider wrapper', 'Use a numeric currentStep state where 0 = hidden, 1+ = active step number', 'Always include SpotlightStepCount in multi-step tours for user orientation', 'First step should only have "Next" action, middle steps have "Back" and "Next", last step has "Back" and "Done"', 'Use SpotlightSecondaryAction for back/dismiss actions and SpotlightPrimaryAction for next/done', 'Include SpotlightDismissControl with onClick={dismiss} so users can exit the tour at any point', 'Bound navigation functions with Math.max/Math.min to prevent invalid step values', 'Preference duplicating Spotlight code instead of trying to have a single `@atlaskit/spotlight` instance that conditionally renders content based on step.'],
53
+ bestPractices: ['Use a numeric currentStep state where 0 = hidden, 1+ = active step number', 'Always include SpotlightStepCount in multi-step tours for user orientation', 'First step should only have "Next" action, middle steps have "Back" and "Next", last step has "Back" and "Done"', 'Use SpotlightSecondaryAction for back/dismiss actions and SpotlightPrimaryAction for next/done', 'Include SpotlightDismissControl with onClick={dismiss} so users can exit the tour at any point', 'Bound navigation functions with Math.max/Math.min to prevent invalid step values', 'Preference duplicating Spotlight code instead of trying to have a single `@atlaskit/spotlight` instance that conditionally renders content based on step.'],
38
54
  additionalResources: additionalResources
39
55
  };
40
56
  var onboardingWithMotion = exports.onboardingWithMotion = {
@@ -48,8 +64,8 @@ var onboardingWithMotion = exports.onboardingWithMotion = {
48
64
  description: 'Replace SpotlightTransition with FadeIn from @atlaskit/motion wrapped around the SpotlightCard',
49
65
  before: "import React, { useState } from 'react';\nimport Button from '@atlaskit/button/new';\nimport {\n Spotlight,\n SpotlightManager,\n SpotlightTarget,\n SpotlightTransition,\n} from '@atlaskit/onboarding';\n\nconst OnboardingSpotlightWithTransition = () => {\n const [isSpotlightActive, setIsSpotlightActive] = useState(false);\n const start = () => setIsSpotlightActive(true);\n const end = () => setIsSpotlightActive(false);\n\n return (\n <SpotlightManager>\n <SpotlightTarget name=\"my-target\">\n <Button>Target Element</Button>\n </SpotlightTarget>\n <div>\n <Button appearance=\"primary\" onClick={start}>\n Show spotlight\n </Button>\n </div>\n <SpotlightTransition>\n {isSpotlightActive && (\n <Spotlight\n actions={[\n {\n onClick: end,\n text: 'Got it',\n },\n ]}\n heading=\"Feature Heading\"\n target=\"my-target\"\n key=\"my-target\"\n >\n This is the spotlight body content describing the feature.\n </Spotlight>\n )}\n </SpotlightTransition>\n </SpotlightManager>\n );\n};",
50
66
  after: "import React, { useState } from 'react';\nimport Button from '@atlaskit/button/new';\nimport { FadeIn } from '@atlaskit/motion';\nimport { Text } from '@atlaskit/primitives/compiled';\nimport {\n PopoverContent,\n PopoverProvider,\n PopoverTarget,\n SpotlightActions,\n SpotlightBody,\n SpotlightCard,\n SpotlightControls,\n SpotlightDismissControl,\n SpotlightFooter,\n SpotlightHeader,\n SpotlightHeadline,\n SpotlightPrimaryAction,\n} from '@atlaskit/spotlight';\n\nconst SpotlightWithMotion = () => {\n const [isVisible, setIsVisible] = useState(false);\n const dismiss = () => setIsVisible(false);\n const done = () => setIsVisible(false);\n\n return (\n <PopoverProvider>\n <PopoverTarget>\n <Button onClick={() => setIsVisible(true)}>Target Element</Button>\n </PopoverTarget>\n <PopoverContent done={done} dismiss={dismiss} placement=\"bottom-start\" isVisible={isVisible}>\n <FadeIn entranceDirection=\"left\">\n {(props) => (\n <div {...props}>\n <SpotlightCard>\n <SpotlightHeader>\n <SpotlightHeadline>Feature Heading</SpotlightHeadline>\n <SpotlightControls>\n <SpotlightDismissControl />\n </SpotlightControls>\n </SpotlightHeader>\n <SpotlightBody>\n <Text>This is the spotlight body content describing the feature.</Text>\n </SpotlightBody>\n <SpotlightFooter>\n <SpotlightActions>\n <SpotlightPrimaryAction onClick={done}>Got it</SpotlightPrimaryAction>\n </SpotlightActions>\n </SpotlightFooter>\n </SpotlightCard>\n </div>\n )}\n </FadeIn>\n </PopoverContent>\n </PopoverProvider>\n );\n};",
51
- explanation: "Key changes when migrating a spotlight with transition animation:\n1. Replace SpotlightTransition with FadeIn from @atlaskit/motion\n2. Import FadeIn from '@atlaskit/motion' instead of SpotlightTransition from '@atlaskit/onboarding'\n3. FadeIn uses a render props pattern - wrap content in {(props) => <div {...props}>...</div>}\n4. The entranceDirection prop controls animation direction: 'left', 'right', 'top', or 'bottom'\n5. SpotlightCard must be wrapped in a div that receives the animation props\n6. PopoverContent now accepts a 'done' prop in addition to 'dismiss' for completed actions\n7. All other migration changes from single step spotlight apply (PopoverProvider, compositional components, etc.)"
67
+ explanation: "Key changes when migrating a spotlight with transition animation:\n- Replace SpotlightTransition with FadeIn from @atlaskit/motion\n- Import FadeIn from '@atlaskit/motion' instead of SpotlightTransition from '@atlaskit/onboarding'\n- FadeIn uses a render props pattern - wrap content in {(props) => <div {...props}>...</div>}\n- The entranceDirection prop controls animation direction: 'left', 'right', 'top', or 'bottom'\n- All other migration changes from single step spotlight apply (PopoverProvider, compositional components, etc.)"
52
68
  }],
53
- bestPractices: ['Use FadeIn from @atlaskit/motion to add entrance animations to spotlights', 'Choose entranceDirection based on spotlight placement (e.g., "left" for right-placed spotlights)', 'Always wrap SpotlightCard in a div that receives the animation props from FadeIn', 'FadeIn uses render props pattern: {(props) => <div {...props}>content</div>}', 'Pass both done and dismiss props to PopoverContent when using motion', 'Motion is optional - only add if the original onboarding spotlight used SpotlightTransition for entrance effects'],
69
+ bestPractices: ['Choose entranceDirection based on spotlight placement (e.g., "left" for right-placed spotlights)', 'Motion is optional - only add if the original onboarding spotlight used SpotlightTransition for entrance effects'],
54
70
  additionalResources: additionalResources
55
71
  };
@@ -16,7 +16,7 @@ var _onboardingToSpotlight = require("./migrations/onboarding-to-spotlight");
16
16
  * 3. The tool will automatically include it in the available options
17
17
  */
18
18
 
19
- var migrationRegistry = exports.migrationRegistry = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _onboardingToSpotlight.onboardingSingleStep.id, _onboardingToSpotlight.onboardingSingleStep), _onboardingToSpotlight.onboardingMultiStep.id, _onboardingToSpotlight.onboardingMultiStep), _onboardingToSpotlight.onboardingWithMotion.id, _onboardingToSpotlight.onboardingWithMotion);
19
+ var migrationRegistry = exports.migrationRegistry = (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, _onboardingToSpotlight.onboardingJiraSpotlight.id, _onboardingToSpotlight.onboardingJiraSpotlight), _onboardingToSpotlight.onboardingSingleStep.id, _onboardingToSpotlight.onboardingSingleStep), _onboardingToSpotlight.onboardingMultiStep.id, _onboardingToSpotlight.onboardingMultiStep), _onboardingToSpotlight.onboardingWithMotion.id, _onboardingToSpotlight.onboardingWithMotion);
20
20
 
21
21
  /**
22
22
  * Get all available migration IDs for use in the tool schema
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Structured content for tokens generated from token-metadata.
5
5
  *
6
- * @codegen <<SignedSource::71826c7e9b24620099bd0316f7a0a305>>
6
+ * @codegen <<SignedSource::44592b6377caa433142db7aab9f15940>>
7
7
  * @codegenCommand yarn build structured-docs
8
8
  */
9
9
 
@@ -2298,44 +2298,44 @@ export const tokenStructuredContent = [{
2298
2298
  description: 'For our brand body text. Uses Charlie Text.',
2299
2299
  exampleValue: '"Charlie Text", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif'
2300
2300
  }, {
2301
- content: '# radius.xsmall\n\nUsed for small containers such as badges.\n\nExample Value: `0.125rem`\n',
2301
+ content: '# radius.xsmall\n\nUse for small detail elements: badges, checkboxes, avatar labels, keyboard shortcuts.\n\nExample Value: `0.125rem`\n',
2302
2302
  name: 'radius.xsmall',
2303
- description: 'Used for small containers such as badges.',
2303
+ description: 'Use for small detail elements: badges, checkboxes, avatar labels, keyboard shortcuts.',
2304
2304
  exampleValue: '0.125rem'
2305
2305
  }, {
2306
- content: '# radius.small\n\nUsed for labels.\n\nExample Value: `0.25rem`\n',
2306
+ content: '# radius.small\n\nUse for supporting elements: labels, lozenges, timestamps, tags, dates, tooltip containers, imagery inside a table, compact buttons.\n\nExample Value: `0.25rem`\n',
2307
2307
  name: 'radius.small',
2308
- description: 'Used for labels.',
2308
+ description: 'Use for supporting elements: labels, lozenges, timestamps, tags, dates, tooltip containers, imagery inside a table, compact buttons.',
2309
2309
  exampleValue: '0.25rem'
2310
2310
  }, {
2311
- content: '# radius.medium\n\nUsed for buttons and inputs.\n\nExample Value: `0.375rem`\n',
2311
+ content: '# radius.medium\n\nUse for interactive elements: buttons, inputs, text areas, selects, navigation items, smart links.\n\nExample Value: `0.375rem`\n',
2312
2312
  name: 'radius.medium',
2313
- description: 'Used for buttons and inputs.',
2313
+ description: 'Use for interactive elements: buttons, inputs, text areas, selects, navigation items, smart links.',
2314
2314
  exampleValue: '0.375rem'
2315
2315
  }, {
2316
- content: '# radius.large\n\nUsed for cards and small containers.\n\nExample Value: `0.5rem`\n',
2316
+ content: '# radius.large\n\nUse for containment elements: cards, in-page containers, floating UI, dropdown menus.\n\nExample Value: `0.5rem`\n',
2317
2317
  name: 'radius.large',
2318
- description: 'Used for cards and small containers.',
2318
+ description: 'Use for containment elements: cards, in-page containers, floating UI, dropdown menus.',
2319
2319
  exampleValue: '0.5rem'
2320
2320
  }, {
2321
- content: '# radius.xlarge\n\nUsed for modals and large containers.\n\nExample Value: `0.75rem`\n',
2321
+ content: '# radius.xlarge\n\nUse for large page elements: full-page containers, large containers, modals, Kanban columns, tables.\n\nExample Value: `0.75rem`\n',
2322
2322
  name: 'radius.xlarge',
2323
- description: 'Used for modals and large containers.',
2323
+ description: 'Use for large page elements: full-page containers, large containers, modals, Kanban columns, tables.',
2324
2324
  exampleValue: '0.75rem'
2325
2325
  }, {
2326
- content: '# radius.xxlarge\n\nUsed for the largest containers.\n\nExample Value: `1rem`\n',
2326
+ content: '# radius.xxlarge\n\nUse for video player containers.\n\nExample Value: `1rem`\n',
2327
2327
  name: 'radius.xxlarge',
2328
- description: 'Used for the largest containers.',
2328
+ description: 'Use for video player containers.',
2329
2329
  exampleValue: '1rem'
2330
2330
  }, {
2331
- content: '# radius.full\n\nUsed for circular containers, like a rounded button.\n\nExample Value: `624.9375rem`\n',
2331
+ content: '# radius.full\n\nUse for circular elements (user/people related): avatars, names, user-related UI, emoji reactions.\n\nExample Value: `624.9375rem`\n',
2332
2332
  name: 'radius.full',
2333
- description: 'Used for circular containers, like a rounded button.',
2333
+ description: 'Use for circular elements (user/people related): avatars, names, user-related UI, emoji reactions.',
2334
2334
  exampleValue: '624.9375rem'
2335
2335
  }, {
2336
- content: '# radius.tile\n\nUsed for tiles only.\n\nExample Value: `25%`\n',
2336
+ content: '# radius.tile\n\nUse this specific radius token exclusively for the tile component system.\n\nExample Value: `25%`\n',
2337
2337
  name: 'radius.tile',
2338
- description: 'Used for tiles only.',
2338
+ description: 'Use this specific radius token exclusively for the tile component system.',
2339
2339
  exampleValue: '25%'
2340
2340
  }, {
2341
2341
  content: '# border.width\n\nThe default width for all standard component borders and dividers.\n\nExample Value: `0.0625rem`\n',