-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30256 from storybookjs/gert/cli-with-ink
wip
- Loading branch information
Showing
4 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import { Init } from './Init'; | ||
import { xtermDecorator } from './xtermDecorator'; | ||
|
||
const meta: Meta<typeof Init> = { | ||
component: Init, | ||
args: { | ||
width: 120, | ||
height: 50, | ||
}, | ||
parameters: { | ||
layout: 'fullscreen', | ||
}, | ||
decorators: [xtermDecorator], | ||
}; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export default meta; | ||
|
||
export const Normal: Story = {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import React from 'react'; | ||
|
||
import { Box, Text, useInput } from 'ink'; | ||
|
||
export function Init(state: { | ||
name: string | string[]; | ||
width: number; | ||
height: number; | ||
}): React.ReactNode { | ||
const { width } = state; | ||
const [activePrompt, setActivePrompt] = React.useState(0); | ||
const [highlightedOption, setHighlightedOption] = React.useState(0); | ||
const [selectedOptions, setSelectedOptions] = React.useState([0]); | ||
|
||
const prompts = [ | ||
{ | ||
title: 'What would you like to use Storybook for?', | ||
description: | ||
'This indicates your general interest and will help us install the right components.', | ||
options: [ | ||
{ label: 'Development', hint: '(always enabled)', disabled: true, selected: true }, | ||
{ label: 'Documentation' }, | ||
{ label: 'Testing' }, | ||
], | ||
}, | ||
{ title: 'Checking your project for compatibility...' }, | ||
{ | ||
title: "OK. We'll install the following packages:", | ||
body: ( | ||
<> | ||
<Box flexDirection="column"> | ||
<Text>- @storybook/core</Text> | ||
<Text>- @storybook/react</Text> | ||
<Text> | ||
- storybook <Text dimColor>(CLI)</Text> | ||
</Text> | ||
</Box> | ||
<Text bold>Continue? Y/n</Text> | ||
</> | ||
), | ||
}, | ||
]; | ||
|
||
useInput((input, key) => { | ||
const question = prompts[activePrompt]; | ||
if (!question) { | ||
return; | ||
} | ||
|
||
if (key.return) { | ||
setActivePrompt((prev) => prev + 1); | ||
setHighlightedOption(0); | ||
} | ||
|
||
const { options } = question; | ||
if (options && options[highlightedOption]) { | ||
if (key.downArrow) { | ||
setHighlightedOption((prev) => (prev + 1) % options.length); | ||
} else if (key.upArrow) { | ||
setHighlightedOption((prev) => (prev + 2) % options.length); | ||
} | ||
if (input === ' ' && !options[highlightedOption].disabled) { | ||
setSelectedOptions((prev) => | ||
prev.includes(highlightedOption) | ||
? prev.filter((option) => option !== highlightedOption) | ||
: [...prev, highlightedOption] | ||
); | ||
} | ||
} | ||
}); | ||
|
||
return ( | ||
<Box width={width} flexDirection="column"> | ||
<Box paddingLeft={1}> | ||
<Text>Welcome to Storybook!</Text> | ||
</Box> | ||
|
||
{prompts.map(({ title, description, body, options }, promptIndex) => { | ||
if (promptIndex > activePrompt) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Box | ||
key={promptIndex} | ||
width={'100%'} | ||
borderDimColor={promptIndex !== activePrompt} | ||
borderStyle="round" | ||
flexDirection="column" | ||
padding={1} | ||
paddingLeft={2} | ||
gap={1} | ||
> | ||
<Box flexDirection="column"> | ||
<Text bold>{title}</Text> | ||
<Text dimColor>{description}</Text> | ||
</Box> | ||
{options && ( | ||
<> | ||
{promptIndex === activePrompt ? ( | ||
<> | ||
<Box flexDirection="column"> | ||
{options.map((option, index) => ( | ||
<Box gap={1} key={option.label}> | ||
<Text>{highlightedOption === index ? '❯' : ' '}</Text> | ||
<Text dimColor={option.disabled}> | ||
{selectedOptions.includes(index) ? '◼' : '◻'} | ||
</Text> | ||
<Text bold={highlightedOption === index}> | ||
[{index + 1}] {option.label} | ||
</Text> | ||
{(option.disabled || option.hint) && highlightedOption === index && ( | ||
<Text dimColor>{option.hint || '(disabled)'}</Text> | ||
)} | ||
</Box> | ||
))} | ||
</Box> | ||
<Box flexDirection="column"> | ||
<Text dimColor>Use arrow keys to highlight, space to select an item.</Text> | ||
<Text dimColor>Press Enter to submit.</Text> | ||
</Box> | ||
</> | ||
) : ( | ||
<Text>{selectedOptions.map((i) => options[i].label).join(', ')}</Text> | ||
)} | ||
</> | ||
)} | ||
{body} | ||
</Box> | ||
); | ||
})} | ||
</Box> | ||
); | ||
} |
26 changes: 26 additions & 0 deletions
26
code/lib/create-storybook/src/ink/components/Prompt.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import { xtermDecorator } from '../xtermDecorator'; | ||
import { Prompt } from './Prompt'; | ||
|
||
const meta: Meta<typeof Prompt> = { | ||
component: Prompt, | ||
args: { | ||
text: 'Hello, World!', | ||
}, | ||
parameters: { | ||
layout: 'fullscreen', | ||
}, | ||
decorators: [xtermDecorator], | ||
} satisfies Meta<typeof Prompt>; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export default meta; | ||
|
||
export const Normal: Story = {}; | ||
export const Active: Story = { | ||
args: { | ||
active: true, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
|
||
import { Box, Text } from 'ink'; | ||
|
||
export const Prompt = ({ active }: { active?: boolean }) => { | ||
const icon = active ? '◆' : '◇'; | ||
const color = active ? 'cyanBright' : 'white'; | ||
|
||
return ( | ||
<Box flexDirection="column"> | ||
<Box gap={2}> | ||
<Text color={color}>{icon}</Text> | ||
<Text>Cool</Text> | ||
</Box> | ||
<Text color={color} dimColor={!active}> | ||
│ | ||
</Text> | ||
{active && <Text color="cyanBright">└</Text>} | ||
</Box> | ||
); | ||
}; |