-
Notifications
You must be signed in to change notification settings - Fork 546
feat(onboarding): "next quest" cards #7941
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c1a079e
4581b15
a553b1f
12e69c9
3292d24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { Meta, StoryObj } from 'storybook' | ||
|
|
||
| import OnboardingNextSteps from 'components/pages/onboarding/OnboardingNextSteps' | ||
|
|
||
| const meta: Meta<typeof OnboardingNextSteps> = { | ||
| args: { | ||
| locked: false, | ||
| onSelect: () => {}, | ||
| }, | ||
| component: OnboardingNextSteps, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: | ||
| 'The "Choose your next quest" section at the bottom of the onboarding flow: the three ways the demo flag can level up (gradual rollout, experiment, remote config), each linking to its real config. Locked and dimmed until the app connects.', | ||
| }, | ||
| }, | ||
| layout: 'padded', | ||
| }, | ||
| title: 'Pages/Onboarding/OnboardingNextSteps', | ||
| } | ||
| export default meta | ||
|
|
||
| type Story = StoryObj<typeof OnboardingNextSteps> | ||
|
|
||
| export const Connected: Story = {} | ||
|
|
||
| export const Locked: Story = { | ||
| args: { locked: true }, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,8 @@ | |
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| flex: 1; | ||
| min-width: 0; | ||
| } | ||
|
|
||
| &__icon { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { Tag as TTag } from 'common/types/responses' | |
| import FeatureName from 'components/feature-summary/FeatureName' | ||
| import Tag from 'components/tags/Tag' | ||
| import Switch from 'components/Switch' | ||
| import Icon from 'components/icons/Icon' | ||
| import './OnboardingFlagsTable.scss' | ||
|
|
||
| export type OnboardingFlagsTableStatus = 'waiting' | 'connected' | ||
|
|
@@ -41,12 +42,20 @@ const OnboardingFlagsTable: FC<OnboardingFlagsTableProps> = ({ | |
| className='onboarding-flags d-flex flex-column align-items-center' | ||
| aria-labelledby='onboarding-flags-title' | ||
| > | ||
| <h3 | ||
| className='onboarding-flags__title m-0 fw-bold' | ||
| id='onboarding-flags-title' | ||
| > | ||
| Your flags | ||
| </h3> | ||
| <div className='onboarding-flags__heading d-flex align-items-center gap-2'> | ||
| <h3 | ||
| className='onboarding-flags__title m-0 fw-bold' | ||
| id='onboarding-flags-title' | ||
| > | ||
| Your flags | ||
| </h3> | ||
| {waiting && ( | ||
| <span className='onboarding-flags__hint d-flex align-items-center gap-1'> | ||
| <Icon name='lock' width={13} /> | ||
| Flip it once your app connects | ||
| </span> | ||
| )} | ||
| </div> | ||
|
Comment on lines
+45
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Keep the hint aligned with every disabled state. The switch is disabled when |
||
| <div | ||
| className={classNames( | ||
| 'onboarding-flags__table bg-surface-default rounded-xl', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,9 @@ import ThemeToggle from 'components/pages/onboarding/ThemeToggle' | |
| import OnboardingConnectPanel from 'components/pages/onboarding/OnboardingConnectPanel' | ||
| import OnboardingTerminal from 'components/pages/onboarding/OnboardingTerminal' | ||
| import OnboardingFlagsTable from 'components/pages/onboarding/OnboardingFlagsTable' | ||
| import OnboardingNextSteps, { | ||
| OnboardingNextStep, | ||
| } from 'components/pages/onboarding/OnboardingNextSteps' | ||
| import { useEnsureOnboardingResources } from 'components/pages/onboarding/hooks/useEnsureOnboardingResources' | ||
| import { useOnboardingFlagRename } from 'components/pages/onboarding/hooks/useOnboardingFlagRename' | ||
| import { useOnboardingFlag } from 'components/pages/onboarding/hooks/useOnboardingFlag' | ||
|
|
@@ -64,6 +67,7 @@ const OnboardingFlow: FC = () => { | |
| const [snippetCopied, setSnippetCopied] = useState(false) | ||
| const { | ||
| enabled: flagEnabled, | ||
| flagId, | ||
| isToggling, | ||
| ready: flagStateReady, | ||
| tags: flagTags, | ||
|
|
@@ -117,6 +121,24 @@ const OnboardingFlow: FC = () => { | |
| } | ||
| } | ||
|
|
||
| // Each next-step card deep-links to the flag's real config; nothing faked. | ||
| const goToNextStep = (step: OnboardingNextStep) => { | ||
| if (projectId === null) { | ||
| return | ||
| } | ||
| const base = `/project/${projectId}/environment/${environmentKey}` | ||
| if (step === 'experiment') { | ||
| history.push(`${base}/experiments`) | ||
| return | ||
| } | ||
| if (flagId === null) { | ||
| return | ||
| } | ||
| // Tab param is the slugified tab label (see TabMenu/Tabs urlParam). | ||
| const tab = step === 'rollout' ? 'segment-overrides' : 'value' | ||
| history.push(`${base}/features?feature=${flagId}&tab=${tab}`) | ||
| } | ||
|
Comment on lines
+124
to
+140
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Do not unlock dependent cards before The handler returns without navigating when Also applies to: 203-205 |
||
|
|
||
| if (status === 'creating') { | ||
| return ( | ||
| <div className='onboarding-flow mx-auto text-center'> | ||
|
|
@@ -178,6 +200,10 @@ const OnboardingFlow: FC = () => { | |
| togglingFlag={isToggling ? featureName : null} | ||
| togglesReady={flagStateReady} | ||
| /> | ||
| <OnboardingNextSteps | ||
| locked={connection !== 'connected'} | ||
| onSelect={goToNextStep} | ||
| /> | ||
| <div className='d-flex justify-content-end'> | ||
| <Button theme='text' onClick={skipToApp}> | ||
| <span className='d-inline-flex align-items-center gap-1'> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // "Choose your next quest" section. Layout comes from utilities in the markup; | ||
| // this file holds the type sizes, secondary text colour, the dimmed locked | ||
| // state, and the small per-card preview shapes (rollout track, A/B segmented, | ||
| // value pill). Secondary text isn't a utility: Bootstrap's .text-secondary | ||
| // (gold, !important) would win over our token utility, so it's set here. | ||
| .onboarding-next-steps { | ||
| &__title { | ||
| font-size: 16px; | ||
| } | ||
|
|
||
| &__subtitle { | ||
| font-size: 13px; | ||
| line-height: 1.5; | ||
| color: var(--color-text-secondary); | ||
| } | ||
|
|
||
| // Locked affordance: intentionally faint (disabled look). The lock icon | ||
| // inherits this via currentColor. | ||
| &__lock { | ||
| font-size: 12px; | ||
| color: var(--color-text-tertiary); | ||
| } | ||
|
|
||
| &__caption { | ||
| font-size: 12px; | ||
| color: var(--color-text-secondary); | ||
| } | ||
|
|
||
| // Dimmed until the app connects (the design's 0.4 locked state). The `inert` | ||
| // attribute on the same element blocks pointer + keyboard interaction. | ||
| &__body--locked { | ||
| opacity: 0.4; | ||
| } | ||
|
|
||
| // Equal-width cards regardless of their copy length. | ||
| &__row > * { | ||
| flex: 1 1 0; | ||
| min-width: 0; | ||
| } | ||
|
|
||
| // Illustrative preview sits a little below the card's description. | ||
| &__preview { | ||
| margin-top: 6px; | ||
| } | ||
|
|
||
| // Rollout: a part-filled pill track. | ||
| &__track { | ||
| display: block; | ||
| width: 100%; | ||
| height: 8px; | ||
| border-radius: var(--radius-full); | ||
| background: var(--color-border-default); | ||
| } | ||
|
|
||
| &__track-fill { | ||
| display: block; | ||
| width: 25%; | ||
| height: 100%; | ||
| border-radius: var(--radius-full); | ||
| background: var(--color-surface-action); | ||
| } | ||
|
|
||
| // Experiment: an A/B segmented control. | ||
| &__segmented { | ||
| align-self: flex-start; | ||
| overflow: hidden; | ||
| border: 1px solid var(--color-border-default); | ||
| border-radius: var(--radius-lg); | ||
| } | ||
|
|
||
| &__segment { | ||
| padding: 5px 16px; | ||
| font-size: 12px; | ||
| color: var(--color-text-secondary); | ||
| background: var(--color-surface-default); | ||
|
|
||
| &--active { | ||
| color: var(--color-text-action); | ||
| background: var(--color-surface-action-subtle); | ||
| } | ||
| } | ||
|
|
||
| // Remote config: a value chip. | ||
| &__value-pill { | ||
| align-self: flex-start; | ||
| padding: 5px 10px; | ||
| border-radius: var(--radius-md); | ||
| font-size: 12px; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does Storybook 10 export Meta and StoryObj types from the bare 'storybook' package, or only from framework packages like@storybook/react?💡 Result:
In Storybook 10, the Meta and StoryObj types are exported from your specific framework package (e.g.,
@storybook/react,@storybook/vue3-vite,@storybook/tanstack-react), not from the bare storybook package [1][2][3]. Official documentation and migration guidance consistently instruct developers to import these utility types from the framework-specific package they are using [4][5][6]. This is necessary because these types are generics that require the framework-aware parameter types (such as React, Vue, or TanStack-specific annotations) to provide correct type safety, autocompletion, and inference for your component's props and arguments [4][5][3]. Using imports like import { Meta, StoryObj } from '@storybook/react' (or your respective framework package) ensures that Storybook can correctly map your component's metadata and args to the framework's internal type definitions [3][6].Citations:
🏁 Script executed:
Repository: Flagsmith/flagsmith
Length of output: 2002
🏁 Script executed:
Repository: Flagsmith/flagsmith
Length of output: 2002
🌐 Web query:
site:storybook.js.org/docs StoryObj import from 'storybook' Meta StoryObj framework package💡 Result:
In Storybook, Meta and StoryObj are TypeScript utility types used to type your story files and ensure type safety [1][2]. You should import them from your specific framework's Storybook package, not from a generic 'storybook' package [3][4][5]. Replace 'your-framework' in the import statement with the specific Storybook framework package you are using (e.g.,
@storybook/react-vite,@storybook/nextjs,@storybook/vue3-vite) [6][1][2]. Example usage in a TypeScript story file: import type { Meta, StoryObj } from '@storybook/your-framework'; import { Button } from './Button'; const meta = { component: Button, } satisfies Meta; export default meta; type Story = StoryObj; export const Primary: Story = { args: { primary: true, }, }; Using these types allows Storybook to infer component metadata and argument types, providing autocompletion and error checking for your stories [1][2]. The satisfies operator is typically used with Meta to associate the component definition with the meta object, which then allows StoryObj to correctly infer the argument types from that meta definition [1][2].Citations:
Import
Meta/StoryObjfrom the configured Storybook framework packagestorybookdoes not export these types; use the framework package for this project (for example@storybook/reactor@storybook/react-vite) so this story type-checks.