Agent Skillsauthgear/authgear-server › update-portal-ui

update-portal-ui

GitHub

提供 Portal React 前端页面更新与设计指南,涵盖组件规范、链接渲染规则及国际化模式。确保用户可见文本正确翻译与格式化,并指导正确使用内部/外部链接组件以避免样式丢失问题。

.claude/skills/update-portal-ui/SKILL.md authgear/authgear-server

Trigger Scenarios

在 portal/src 中添加或编辑 UI 组件 审查前端代码中的国际化与链接使用 配置图表库的本地化显示

Install

npx skills add authgear/authgear-server --skill update-portal-ui -g -y
More Options

Non-standard path

npx skills add https://github.com/authgear/authgear-server/tree/main/.claude/skills/update-portal-ui -g -y

Use without installing

npx skills use authgear/authgear-server@update-portal-ui

指定 Agent (Claude Code)

npx skills add authgear/authgear-server --skill update-portal-ui -a claude-code -g -y

安装 repo 全部 skill

npx skills add authgear/authgear-server --all -g -y

预览 repo 内 skill

npx skills add authgear/authgear-server --list

SKILL.md

Frontmatter
{
    "name": "update-portal-ui",
    "description": "Guidelines for updating or designing pages in the portal React frontend (portal\/src). Covers component conventions, link rendering rules, i18n patterns, and common pitfalls."
}

Follow this skill when adding, editing, or reviewing UI in portal/src.

All user-facing text must be translated

Every string a user can see or hear must go through renderToString/FormattedMessage/locale-data (portal/src/locale-data/en.json) — never a bare string literal. This applies beyond obvious JSX text nodes:

  • Chart/graph library config: dataset labels, legend text, tooltip callbacks, axis titles passed into chart.js (or any charting lib) config objects are still user-facing text, even though they live inside a plain JS config object, not JSX. Wrap them with renderToString the same as any other label.
  • Locale-aware formatting of derived values: any Intl.DisplayNames, Intl.NumberFormat, Intl.DateTimeFormat, or luxon DateTime#toFormat/toLocaleString call that produces user-visible output (country names, chart axis date labels, etc.) must be constructed with the active portal locale, not a hardcoded locale (e.g. new Intl.DisplayNames(["en"], ...)) and not left to the library's default. Grep for .toFormat(/.toLocaleString(/new Intl. in your diff and confirm each one is passed (or chained with) the active locale, not silently defaulting.
  • A string can be "translated" everywhere else in a file and still miss one of these — check every literal individually, don't assume a file is compliant because most of it uses FormattedMessage.

Link components

The portal has three link components. Use the right one — using the wrong one causes links to render as unstyled plain text inside certain wrappers.

Component Import path Use when
Link ../../Link (or relative path to portal/src/Link.tsx) Internal navigation (React Router)
ExternalLink ../../ExternalLink External URLs (href, opens in new tab)
LinkButton ../../LinkButton A button that visually looks like a link

Never use Link from react-router-dom directly — it renders a plain <a> tag with no FluentUI styling.

Why this matters: the WidgetDescription / Text trap

WidgetDescription wraps its children in a FluentUI Text component. FluentUI's Text overrides the colour of plain <a> tags to match surrounding text, making links invisible as links.

  • portal/src/Link.tsx and portal/src/ExternalLink.tsx both wrap FluentUI's FluentLink, which keeps its own link styling even inside Text. ✓
  • react-router-dom's Link renders a bare <a> — styling is stripped inside Text. ✗

Rule: Whenever a link appears inside WidgetDescription, Text (FluentUI), or any component that internally wraps FluentUI Text, use Link or ExternalLink from portal/src, not from react-router-dom.

Inline links inside FormattedMessage (i18n)

To embed a clickable link inside a translated string:

  1. In the translation string (portal/src/locale-data/en.json), use an XML-like tag:

    "my-key": "Read the <docLink>documentation</docLink> for details."
    
  2. In the component, pass a render function in FormattedMessage values whose key matches the tag name exactly:

    <FormattedMessage
      id="my-key"
      values={{
        // eslint-disable-next-line react/no-unstable-nested-components
        docLink: (chunks: React.ReactNode) => (
          <ExternalLink href="https://docs.authgear.com/...">
            {chunks}
          </ExternalLink>
        ),
      }}
    />
    
  3. Use Link for internal routes, ExternalLink for external URLs. Never use react-router-dom's Link here.

Passing rich content to callbacks that accept descriptions

Some components (e.g. FluentUI ChoiceGroup via onRenderLabel) accept a label-render callback. If the description contains a link, the callback must accept React.ReactNode, not string:

// Correct — accepts ReactNode so JSX can be passed
const onRenderLabel = useCallback((description: React.ReactNode) => {
  return (option?: IChoiceGroupOption) => (
    <div>
      <Text>{option?.text}</Text>
      <Text>{description}</Text>
    </div>
  );
}, []);

// Then pass FormattedMessage directly — no cast needed
onRenderLabel(
  <FormattedMessage id="..." values={{ reactRouterLink: ... }} />
)

Never cast JSX to string with as any as string — the link will not render correctly.

Verification checklist

Before submitting a portal UI change:

  • No hardcoded user-facing string literals anywhere in the diff, including non-JSX config objects (chart library label/legend/tooltip config, form option lists, etc.) — all go through renderToString/FormattedMessage.
  • Every Intl.DisplayNames/Intl.NumberFormat/Intl.DateTimeFormat/luxon toFormat/toLocaleString call that produces user-visible text uses the active portal locale, not a hardcoded or default locale.
  • Links inside WidgetDescription or FluentUI Text use Link or ExternalLink from portal/src, not from react-router-dom.
  • Inline links in FormattedMessage values use Link or ExternalLink from portal/src.
  • Callbacks that may receive rich content (links, JSX) are typed React.ReactNode, not string.
  • Run cd portal && npm run typecheck — must pass clean.

Version History

  • 2dd6d88 Current 2026-07-24 16:31

Same Skill Collection

.claude/skills/add-go-test/SKILL.md
.claude/skills/add-portal-admin-api-mutation/SKILL.md
.claude/skills/add-portal-admin-api-query/SKILL.md
.claude/skills/add-portal-screen/SKILL.md
.claude/skills/api-design/SKILL.md
.claude/skills/bootstrap-local-dev/SKILL.md
.claude/skills/dep-audit/SKILL.md
.claude/skills/generate-schemas-and-gentype/SKILL.md
.claude/skills/new-siteadmin-api/SKILL.md
.claude/skills/review-pr/SKILL.md
.claude/skills/update-deps/SKILL.md
.claude/skills/update-email-templates/SKILL.md
.claude/skills/update-feature-config/SKILL.md
.claude/skills/update-important-modules/SKILL.md
.claude/skills/update-vettedpositions/SKILL.md
.claude/skills/write-e2e-test/SKILL.md
.claude/skills/write-implementation-plan/SKILL.md
.claude/skills/update-go-version/SKILL.md
.claude/skills/portal-admin-api-graphql/SKILL.md

Metadata

Files
0
Version
e619570
Hash
13ddc8ba
Indexed
2026-07-24 16:31

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-17 04:56
浙ICP备14020137号-1 $mapa de visitantes$