Skip to content

Commit 8af3fac

Browse files
talissoncostaclaude
andcommitted
docs: component-folder convention and onboarding stories
Add the folder + barrel convention (frontend/CLAUDE.md rule 8) and Storybook stories for the onboarding components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9b64af7 commit 8af3fac

7 files changed

Lines changed: 271 additions & 0 deletions

File tree

frontend/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
5. **Linting**: ALWAYS run `npx eslint --fix <file>` on any files you modify
2020
6. **Type Enums**: Extract inline union types to named types (e.g., `type Status = 'A' | 'B'` instead of inline)
2121
7. **NO FETCH**: NEVER use `fetch()` directly - ALWAYS use RTK Query mutations/queries (inject endpoints into services in `common/services/`), see api-integration context
22+
8. **Component structure**: Each new component lives in its own folder with an `index.ts` barrel — `ComponentName/ComponentName.tsx`, co-located `ComponentName.scss`, any sub-components, and an `index.ts` that re-exports the default (and public types). Import via the folder (`components/.../ComponentName`), never the inner file. Keep files focused (~100 lines as a target); split by concern, not to hit a number. Data tables/constant maps are exempt.
2223

2324
## Key Files
2425
- Store: `common/store.ts`
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react'
2+
import type { Meta, StoryObj } from 'storybook'
3+
4+
import CodeCard from 'components/pages/onboarding/OnboardingConnectPanel/CodeCard'
5+
// CodeCard's structural styles (radius, header, lang label) live in the connect
6+
// panel's stylesheet; import it so the card renders fully styled in isolation.
7+
import 'components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss'
8+
9+
const meta: Meta<typeof CodeCard> = {
10+
args: {
11+
code: 'npm install flagsmith',
12+
headerLeft: (
13+
<span className='onboarding-connect__codecard-lang'>Shell</span>
14+
),
15+
hljsClass: 'bash',
16+
},
17+
component: CodeCard,
18+
parameters: {
19+
docs: {
20+
description: {
21+
component:
22+
'A copyable, syntax-highlighted code block with a header strip. Owns its own "Copied" feedback (announced via aria-live) and is theme-adaptive via semantic tokens — a light editor in light mode, dark in dark mode.',
23+
},
24+
},
25+
layout: 'padded',
26+
},
27+
title: 'Pages/Onboarding/CodeCard',
28+
}
29+
export default meta
30+
31+
type Story = StoryObj<typeof CodeCard>
32+
33+
export const Install: Story = {}
34+
35+
export const Wire: Story = {
36+
args: {
37+
code: "import flagsmith from 'flagsmith'\nflagsmith.init({ environmentID: 'ser.abc123EXAMPLEkey' })\nconst showDemo = flagsmith.hasFeature('show_demo_button')",
38+
headerLeft: (
39+
<span className='onboarding-connect__codecard-lang'>JavaScript</span>
40+
),
41+
hljsClass: 'javascript',
42+
},
43+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { useState } from 'react'
2+
import type { Meta, StoryObj } from 'storybook'
3+
4+
import EditableChip from 'components/pages/onboarding/EditableChip'
5+
6+
const meta: Meta<typeof EditableChip> = {
7+
component: EditableChip,
8+
parameters: {
9+
docs: {
10+
description: {
11+
component:
12+
'An onboarding-local rename chip: the shared Chip shell + a GhostInput + a pencil. Commits on blur / Enter; an empty value reverts to the last good name; an optional `transform` normalises on commit (e.g. flag-name rules). Used in the onboarding header sentence. Feature-local — not a shared inline-edit primitive.',
13+
},
14+
},
15+
layout: 'centered',
16+
},
17+
title: 'Pages/Onboarding/EditableChip',
18+
}
19+
export default meta
20+
21+
type Story = StoryObj<typeof EditableChip>
22+
23+
// EditableChip is controlled; wrap it so the stories commit and re-render.
24+
const Controlled = ({
25+
initial,
26+
label,
27+
transform,
28+
}: {
29+
initial: string
30+
label: string
31+
transform?: (raw: string) => string
32+
}) => {
33+
const [value, setValue] = useState(initial)
34+
return (
35+
<EditableChip
36+
label={label}
37+
value={value}
38+
onCommit={setValue}
39+
transform={transform}
40+
/>
41+
)
42+
}
43+
44+
export const Default: Story = {
45+
render: () => <Controlled label='Organisation' initial='Acme Inc' />,
46+
}
47+
48+
export const Empty: Story = {
49+
render: () => <Controlled label='Project' initial='' />,
50+
}
51+
52+
// Normalises on commit (spaces → underscores, lower-cased) like the flag chip.
53+
export const WithTransform: Story = {
54+
render: () => (
55+
<Controlled
56+
label='Flag'
57+
initial='show_demo_button'
58+
transform={(raw) => raw.replace(/ /g, '_').toLowerCase()}
59+
/>
60+
),
61+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Meta, StoryObj } from 'storybook'
2+
3+
import OnboardingConnectPanel from 'components/pages/onboarding/OnboardingConnectPanel'
4+
5+
const meta: Meta<typeof OnboardingConnectPanel> = {
6+
args: {
7+
environmentKey: 'ser.abc123EXAMPLEkey',
8+
featureName: 'show_demo_button',
9+
},
10+
component: OnboardingConnectPanel,
11+
parameters: {
12+
docs: {
13+
description: {
14+
component:
15+
'Two ways to connect an app to the pre-created flag: a "Connect with AI" tab (an agent-agnostic, zero-auth prompt carrying the real env key + flag) and a "Connect your code" tab (an SDK selector built on Chip, with install + wire snippets per language). Nothing is faked — the prompt and snippets carry the user\'s real env key + flag.',
16+
},
17+
},
18+
layout: 'padded',
19+
},
20+
title: 'Pages/Onboarding/OnboardingConnectPanel',
21+
}
22+
export default meta
23+
24+
type Story = StoryObj<typeof OnboardingConnectPanel>
25+
26+
export const Default: Story = {}
27+
28+
export const CustomFlagName: Story = {
29+
args: { featureName: 'enable_new_checkout' },
30+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { Meta, StoryObj } from 'storybook'
2+
3+
import OnboardingHeader from 'components/pages/onboarding/OnboardingHeader'
4+
5+
const meta: Meta<typeof OnboardingHeader> = {
6+
args: {
7+
caseSensitive: true,
8+
featureName: 'show_demo_button',
9+
organisationName: 'Acme Inc',
10+
projectName: 'Web App',
11+
},
12+
component: OnboardingHeader,
13+
parameters: {
14+
docs: {
15+
description: {
16+
component:
17+
'The top of the single-page onboarding flow: a welcome title and a sentence naming the resources we pre-created, with organisation / project / flag inline-editable in place via the shared GhostInput. Presentational — the rename handlers are owned by the page that assembles the flow.',
18+
},
19+
},
20+
layout: 'padded',
21+
},
22+
title: 'Pages/Onboarding/OnboardingHeader',
23+
}
24+
export default meta
25+
26+
type Story = StoryObj<typeof OnboardingHeader>
27+
28+
export const Default: Story = {}
29+
30+
export const LongNames: Story = {
31+
args: {
32+
featureName: 'enable_the_brand_new_checkout_experience_v2',
33+
organisationName: 'A Very Long Organisation Name That Wraps',
34+
projectName: 'Customer-Facing Marketing Website Project',
35+
},
36+
}
37+
38+
export const ShortNames: Story = {
39+
args: {
40+
featureName: 'beta',
41+
organisationName: 'Co',
42+
projectName: 'App',
43+
},
44+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useState } from 'react'
2+
import type { Meta, StoryObj } from 'storybook'
3+
4+
import OnboardingTabs, {
5+
OnboardingTab,
6+
} from 'components/pages/onboarding/OnboardingTabs'
7+
8+
const meta: Meta<typeof OnboardingTabs> = {
9+
component: OnboardingTabs,
10+
parameters: {
11+
docs: {
12+
description: {
13+
component:
14+
"One-off onboarding tablist following the WAI-ARIA tabs pattern: roving tabindex, Arrow/Home/End keys, aria-selected. A tab can be pushed to the trailing edge with `alignEnd` — the reason this isn't the shared navigation Tabs. Local to onboarding; pair with OnboardingTabPanel for the content side.",
15+
},
16+
},
17+
layout: 'padded',
18+
},
19+
title: 'Pages/Onboarding/OnboardingTabs',
20+
}
21+
export default meta
22+
23+
type Story = StoryObj<typeof OnboardingTabs>
24+
25+
const Controlled = ({ tabs }: { tabs: OnboardingTab[] }) => {
26+
const [active, setActive] = useState(tabs[0].id)
27+
return (
28+
<OnboardingTabs
29+
aria-label='Connect your app'
30+
tabs={tabs}
31+
activeId={active}
32+
onChange={setActive}
33+
/>
34+
)
35+
}
36+
37+
export const Default: Story = {
38+
render: () => (
39+
<Controlled
40+
tabs={[
41+
{ id: 'manual', label: 'Connect your code' },
42+
{ alignEnd: true, id: 'ai', label: 'Connect with AI' },
43+
]}
44+
/>
45+
),
46+
}
47+
48+
export const ThreeTabs: Story = {
49+
render: () => (
50+
<Controlled
51+
tabs={[
52+
{ id: 'one', label: 'First' },
53+
{ id: 'two', label: 'Second' },
54+
{ alignEnd: true, id: 'three', label: 'Third' },
55+
]}
56+
/>
57+
),
58+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { useState } from 'react'
2+
import type { Meta, StoryObj } from 'storybook'
3+
4+
import SdkPicker from 'components/pages/onboarding/OnboardingConnectPanel/SdkPicker'
5+
import {
6+
SDK_LANGS,
7+
SdkLang,
8+
} from 'components/pages/onboarding/OnboardingConnectPanel/sdkLangs'
9+
10+
const meta: Meta<typeof SdkPicker> = {
11+
component: SdkPicker,
12+
parameters: {
13+
docs: {
14+
description: {
15+
component:
16+
'The SDK / framework picker: popular SDKs as quick-pick Chips, the long tail behind "More". Controlled — the selected SDK lives in the parent.',
17+
},
18+
},
19+
layout: 'padded',
20+
},
21+
title: 'Pages/Onboarding/SdkPicker',
22+
}
23+
export default meta
24+
25+
type Story = StoryObj<typeof SdkPicker>
26+
27+
const Controlled = () => {
28+
const [selected, setSelected] = useState<SdkLang>(SDK_LANGS[0])
29+
return <SdkPicker selected={selected} onSelect={setSelected} />
30+
}
31+
32+
export const Default: Story = {
33+
render: () => <Controlled />,
34+
}

0 commit comments

Comments
 (0)