diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInput.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInput.stories.tsx deleted file mode 100644 index 8f0d2af1..00000000 --- a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInput.stories.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React from 'react'; -import type { ComponentProps } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-native'; -import { EnrichedMarkdownTextInput } from 'react-native-enriched-markdown'; -import { - EnrichedMarkdownTextInputStory, - MentionsStory, - DEFAULT_MENTION_USERS, - DEFAULT_MENTION_CHANNELS, -} from './EnrichedMarkdownTextInputStory'; - -const meta: Meta = { - title: 'EnrichedMarkdownTextInput', - component: EnrichedMarkdownTextInput, - parameters: { - controls: { exclude: ['initialMarkdown'] }, - }, - argTypes: { - markdownStyle: { control: 'object' }, - onChangeMarkdown: { action: 'onChangeMarkdown' }, - onChangeText: { action: 'onChangeText' }, - onFocus: { action: 'onFocus' }, - onBlur: { action: 'onBlur' }, - onLinkDetected: { action: 'onLinkDetected' }, - }, -}; - -export default meta; -type Story = Omit, 'args' | 'render' | 'argTypes'> & { - args?: ComponentProps & Record; - render: ( - args: ComponentProps & Record - ) => React.ReactElement; - argTypes?: Record; -}; - -export const Formatting: Story = { - args: { - initialMarkdown: - '**Bold**, *italic*, _underline_, ~~strikethrough~~, ||spoiler|| [link](https://example.com)', - }, - render: (args) => ( - - ), -}; - -export const MarkdownStyle: Story = { - args: { - initialMarkdown: - '**Bold text**, *italic text*, [a link](https://example.com), ||spoiler||', - markdownStyle: { - strong: { color: '#1D4ED8' }, - em: { color: '#7C3AED' }, - link: { color: '#059669', underline: true }, - spoiler: { color: '#fff', backgroundColor: '#111' }, - }, - }, - render: (args) => ( - - ), -}; - -export const AutoLink: Story = { - args: { - initialMarkdown: '', - autoLink: true, - customRegex: '', - }, - argTypes: { - autoLink: { - control: 'boolean', - description: - 'Toggle auto-link detection on/off. When off, linkRegex={null} is passed and no URLs are detected.', - }, - customRegex: { - control: 'text', - description: - 'Custom regex pattern string (e.g. "https?:\\/\\/[^\\s]+"). Leave empty to use the default pattern. Only used when auto-link is enabled.', - }, - }, - render: ({ autoLink, customRegex, ...args }) => { - let linkRegex: RegExp | null | undefined = autoLink ? undefined : null; - if (autoLink && customRegex) { - try { - linkRegex = new RegExp(customRegex, 'i'); - } catch { - linkRegex = undefined; - } - } - return ( - - ); - }, -}; - -export const LinkVariants: Story = { - args: { - initialMarkdown: - '[jira://PROJ-123](jira://PROJ-123), [sftp://server.example.com/file.zip](sftp://server.example.com/file.zip), [notion://page-abc](notion://page-abc), [https://example.com](https://example.com)', - markdownStyle: { - link: { color: '#2563EB', underline: true }, - linkVariants: { - '^jira:': { - color: '#0052CC', - backgroundColor: '#DEEBFF', - underline: false, - }, - '^sftp:': { - color: '#065F46', - backgroundColor: '#D1FAE5', - underline: false, - }, - '^notion:': { - color: '#6B21A8', - backgroundColor: '#F3E8FF', - underline: false, - }, - }, - }, - }, - render: (args) => ( - - ), -}; - -export const Mentions: Story = { - args: { - userNames: DEFAULT_MENTION_USERS.map((u) => u.name), - channelNames: DEFAULT_MENTION_CHANNELS.map((c) => c.name), - }, - argTypes: { - userNames: { - control: 'multi-select', - options: DEFAULT_MENTION_USERS.map((u) => u.name), - description: 'Users available for @ mentions.', - }, - channelNames: { - control: 'multi-select', - options: DEFAULT_MENTION_CHANNELS.map((c) => c.name), - description: 'Channels available for # mentions.', - }, - }, - render: ({ userNames, channelNames }) => ( - userNames?.includes(u.name))} - channels={DEFAULT_MENTION_CHANNELS.filter((c) => - channelNames?.includes(c.name) - )} - /> - ), -}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInputStory.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInputStory.tsx index 946bce4d..f4a10992 100644 --- a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInputStory.tsx +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/EnrichedMarkdownTextInputStory.tsx @@ -18,12 +18,13 @@ type Props = { title: string; description: string; initialMarkdown?: string; -} & Omit; +} & EnrichedMarkdownTextInputProps; export function EnrichedMarkdownTextInputStory({ title, description, initialMarkdown, + style, ...props }: Props) { const inputRef = useRef(null); @@ -48,7 +49,7 @@ export function EnrichedMarkdownTextInputStory({ ref={inputRef} placeholder="Type markdown here..." placeholderTextColor="#9CA3AF" - style={styles.input} + style={{ ...styles.input, ...style }} onChangeState={setState} onChangeSelection={(sel) => setHasSelection(sel.start !== sel.end)} {...props} diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Bold.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Bold.stories.tsx new file mode 100644 index 00000000..57eb3442 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Bold.stories.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import { + inputStrongDefaults, + type InputStrongStyleControls, +} from '../shared/storybookInputStyles'; +import { + splitStyleControls, + toInputStrongStyle, +} from '../shared/storybookInputStyleBuilders'; +import type { InputStory } from '../shared/storyTypes'; + +const argTypes = { + color: { + control: 'color', + description: 'markdownStyle.strong.color', + }, +}; + +export default storyMeta('Inline', 'Bold'); + +export const Default: InputStory = { + args: { + initialMarkdown: '**Bold text** and normal text.', + ...inputStrongDefaults, + }, + argTypes, + render: (args) => { + const { controls, rest } = splitStyleControls(args, inputStrongDefaults); + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Italic.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Italic.stories.tsx new file mode 100644 index 00000000..290f6ad5 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Italic.stories.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import { + inputEmDefaults, + type InputEmStyleControls, +} from '../shared/storybookInputStyles'; +import { + splitStyleControls, + toInputEmStyle, +} from '../shared/storybookInputStyleBuilders'; +import type { InputStory } from '../shared/storyTypes'; + +const argTypes = { + color: { + control: 'color', + description: 'markdownStyle.em.color', + }, +}; + +export default storyMeta('Inline', 'Italic'); + +export const Default: InputStory = { + args: { + initialMarkdown: '*Italic text* and normal text.', + ...inputEmDefaults, + }, + argTypes, + render: (args) => { + const { controls, rest } = splitStyleControls(args, inputEmDefaults); + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Link.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Link.stories.tsx new file mode 100644 index 00000000..200c89fd --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Link.stories.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import { + inputLinkBaseArgTypes, + inputLinkDefaults, + inputLinkVariantsArgTypes, + inputLinkVariantsDemoDefaults, + type InputLinkStyleControls, + type InputLinkVariantsDemoControls, +} from '../shared/storybookInputStyles'; +import { + splitStyleControls, + toInputLinkStyle, + toInputLinkVariantsDemoStyle, +} from '../shared/storybookInputStyleBuilders'; +import type { InputStory } from '../shared/storyTypes'; + +const MARKDOWN = 'Visit [React Native](https://reactnative.dev) for docs.'; + +const VARIANTS_MARKDOWN = + '[jira://PROJ-123](jira://PROJ-123), [sftp://server.example.com/file.zip](sftp://server.example.com/file.zip), [notion://page-abc](notion://page-abc), [https://example.com](https://example.com)'; + +export default storyMeta('Inline', 'Link'); + +export const Default: InputStory = { + args: { + initialMarkdown: MARKDOWN, + ...inputLinkDefaults, + }, + argTypes: inputLinkBaseArgTypes, + render: (args) => { + const { controls, rest } = splitStyleControls(args, inputLinkDefaults); + return ( + + ); + }, +}; + +export const Variants: InputStory = { + args: { + initialMarkdown: VARIANTS_MARKDOWN, + ...inputLinkVariantsDemoDefaults, + }, + argTypes: inputLinkVariantsArgTypes, + render: (args) => { + const { controls, rest } = splitStyleControls( + args, + inputLinkVariantsDemoDefaults + ); + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Spoiler.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Spoiler.stories.tsx new file mode 100644 index 00000000..d21159c6 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Spoiler.stories.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import { + inputSpoilerDefaults, + type InputSpoilerStyleControls, +} from '../shared/storybookInputStyles'; +import { + splitStyleControls, + toInputSpoilerStyle, +} from '../shared/storybookInputStyleBuilders'; +import type { InputStory } from '../shared/storyTypes'; + +const argTypes = { + color: { + control: 'color', + description: 'markdownStyle.spoiler.color', + }, + backgroundColor: { + control: 'color', + description: 'markdownStyle.spoiler.backgroundColor', + }, +}; + +export default storyMeta('Inline', 'Spoiler'); + +export const Default: InputStory = { + args: { + initialMarkdown: 'The answer is ||spoiler text||.', + ...inputSpoilerDefaults, + }, + argTypes, + render: (args) => { + const { controls, rest } = splitStyleControls(args, inputSpoilerDefaults); + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Strikethrough.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Strikethrough.stories.tsx new file mode 100644 index 00000000..72376126 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Strikethrough.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; + +export default storyMeta('Inline', 'Strikethrough'); + +export const Default: InputStory = { + args: { + initialMarkdown: '~~Strikethrough text~~ and normal text.', + }, + render: (args) => ( + + ), +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Underline.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Underline.stories.tsx new file mode 100644 index 00000000..1a85d5fd --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/inline/Underline.stories.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; + +export default storyMeta('Inline', 'Underline'); + +export const Default: InputStory = { + args: { + initialMarkdown: '_Underlined text_ and normal text.', + }, + render: (args) => ( + + ), +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/AutoLink.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/AutoLink.stories.tsx new file mode 100644 index 00000000..6ed471e3 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/AutoLink.stories.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; + +type AutoLinkStoryExtra = { + autoLink: boolean; + customRegex: string; +}; + +export default storyMeta('Props', 'Auto-Link'); + +export const Default: InputStory = { + args: { + initialMarkdown: '', + autoLink: true, + customRegex: '', + }, + argTypes: { + autoLink: { + control: 'boolean', + description: + 'Toggle auto-link detection on/off. When off, linkRegex={null} is passed and no URLs are detected.', + }, + customRegex: { + control: 'text', + description: + 'Custom regex pattern string (e.g. "https?:\\/\\/[^\\s]+"). Leave empty to use the default pattern. Only used when auto-link is enabled.', + }, + }, + render: ({ autoLink, customRegex, ...args }) => { + let linkRegex: RegExp | null | undefined = autoLink ? undefined : null; + if (autoLink && customRegex) { + try { + linkRegex = new RegExp(customRegex, 'i'); + } catch { + linkRegex = undefined; + } + } + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/ContextMenu.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/ContextMenu.stories.tsx new file mode 100644 index 00000000..9d62889f --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/ContextMenu.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import type { EnrichedMarkdownTextInputProps } from 'react-native-enriched-markdown'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; + +const MARKDOWN = + 'Select this text to open the context menu with a custom action.'; + +type ContextMenuOnPress = NonNullable< + EnrichedMarkdownTextInputProps['contextMenuItems'] +>[number]['onPress']; + +type ContextMenuStoryExtra = { + showCustomItem: boolean; + onContextMenuItemPress?: ContextMenuOnPress; +}; + +const argTypes = { + showCustomItem: { + control: 'boolean', + description: + 'Adds a custom contextMenuItems entry alongside built-in actions.', + }, + onContextMenuItemPress: { action: 'contextMenuItem.onPress' }, +}; + +export default storyMeta('Props', 'Context Menu'); + +export const Default: InputStory = { + args: { + initialMarkdown: MARKDOWN, + showCustomItem: true, + }, + argTypes, + render: ({ showCustomItem, onContextMenuItemPress, ...args }) => { + const contextMenuItems: EnrichedMarkdownTextInputProps['contextMenuItems'] = + showCustomItem + ? [ + { + text: 'Log selection', + onPress: onContextMenuItemPress ?? (() => {}), + }, + ] + : undefined; + + return ( + + ); + }, +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/InputStyle.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/InputStyle.stories.tsx new file mode 100644 index 00000000..bffb3675 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/InputStyle.stories.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import type { TextStyle } from 'react-native'; +import { EnrichedMarkdownTextInputStory } from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; +import { numberControl } from '../../EnrichedMarkdownText/shared/storybookMarkdownStyles'; + +type InputStyleStoryExtra = { + fontSize: number; + color: string; + paddingHorizontal: number; + paddingVertical: number; + minHeight: number; + backgroundColor: string; +}; + +const MARKDOWN = + '**Bold**, *italic*, and [a link](https://example.com) inside the styled input.'; + +const argTypes = { + fontSize: numberControl('style.fontSize', { min: 12, max: 28, step: 1 }), + color: { + control: 'color', + description: 'style.color — base text color for unformatted content.', + }, + paddingHorizontal: numberControl('style.paddingHorizontal', { + min: 0, + max: 32, + step: 2, + }), + paddingVertical: numberControl('style.paddingVertical', { + min: 0, + max: 32, + step: 2, + }), + minHeight: numberControl('style.minHeight', { min: 80, max: 280, step: 10 }), + backgroundColor: { + control: 'color', + description: 'style.backgroundColor', + }, +}; + +export default storyMeta('Props', 'Input Style'); + +export const Default: InputStory = { + args: { + initialMarkdown: MARKDOWN, + fontSize: 15, + color: '#111827', + paddingHorizontal: 14, + paddingVertical: 12, + minHeight: 120, + backgroundColor: '#F9FAFB', + }, + argTypes, + render: ({ + fontSize, + color, + paddingHorizontal, + paddingVertical, + minHeight, + backgroundColor, + ...args + }) => ( + + ), +}; + +function buildInputStyle({ + fontSize, + color, + paddingHorizontal, + paddingVertical, + minHeight, + backgroundColor, +}: InputStyleStoryExtra): TextStyle { + return { + fontSize, + color, + paddingHorizontal, + paddingVertical, + minHeight, + backgroundColor, + }; +} diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/Mentions.stories.tsx b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/Mentions.stories.tsx new file mode 100644 index 00000000..baa8b320 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/props/Mentions.stories.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { + DEFAULT_MENTION_CHANNELS, + DEFAULT_MENTION_USERS, + MentionsStory, +} from '../EnrichedMarkdownTextInputStory'; +import { storyMeta } from '../shared/storyMeta'; +import type { InputStory } from '../shared/storyTypes'; + +type MentionsStoryExtra = { + userNames: string[]; + channelNames: string[]; +}; + +export default storyMeta('Props', 'Mentions'); + +export const Default: InputStory = { + args: { + userNames: DEFAULT_MENTION_USERS.map((u) => u.name), + channelNames: DEFAULT_MENTION_CHANNELS.map((c) => c.name), + }, + argTypes: { + userNames: { + control: 'multi-select', + options: DEFAULT_MENTION_USERS.map((u) => u.name), + description: 'Users available for @ mentions.', + }, + channelNames: { + control: 'multi-select', + options: DEFAULT_MENTION_CHANNELS.map((c) => c.name), + description: 'Channels available for # mentions.', + }, + }, + render: ({ userNames, channelNames }) => ( + userNames?.includes(u.name))} + channels={DEFAULT_MENTION_CHANNELS.filter((c) => + channelNames?.includes(c.name) + )} + /> + ), +}; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyMeta.ts b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyMeta.ts new file mode 100644 index 00000000..30c9181a --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyMeta.ts @@ -0,0 +1,26 @@ +import type { Meta } from '@storybook/react-native'; +import { EnrichedMarkdownTextInput } from 'react-native-enriched-markdown'; + +export type InputStoryCategory = 'Inline' | 'Props'; + +export const inputActionArgTypes = { + onChangeMarkdown: { action: 'onChangeMarkdown' }, + onChangeText: { action: 'onChangeText' }, + onFocus: { action: 'onFocus' }, + onBlur: { action: 'onBlur' }, + onLinkDetected: { action: 'onLinkDetected' }, +}; + +export function storyMeta( + category: InputStoryCategory, + name: string +): Meta { + return { + title: `EnrichedMarkdownTextInput/${category}/${name}`, + component: EnrichedMarkdownTextInput, + parameters: { + controls: { exclude: ['initialMarkdown'] }, + }, + argTypes: inputActionArgTypes, + }; +} diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyTypes.ts b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyTypes.ts new file mode 100644 index 00000000..186dddad --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storyTypes.ts @@ -0,0 +1,26 @@ +import type { ComponentProps, ReactElement } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-native'; +import { EnrichedMarkdownTextInput } from 'react-native-enriched-markdown'; + +export type InputStoryMeta = Meta; + +/** Component props merged with Storybook-only control args. */ +export type InputStoryArgs = {}> = Omit< + ComponentProps, + 'markdownStyle' +> & { + initialMarkdown?: string; +} & TExtra; + +type InputStoryBase = Omit< + StoryObj, + 'args' | 'render' | 'argTypes' +>; + +/** TExtra = Storybook-only control args (e.g. markdownStyle knobs) merged with component props. */ +export type InputStory = {}> = + InputStoryBase & { + args?: InputStoryArgs; + render: (args: InputStoryArgs) => ReactElement; + argTypes?: Record; + }; diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyleBuilders.ts b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyleBuilders.ts new file mode 100644 index 00000000..89e80b69 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyleBuilders.ts @@ -0,0 +1,94 @@ +import type { MarkdownTextInputStyle } from 'react-native-enriched-markdown'; +import type { InputStoryArgs } from './storyTypes'; +import type { + InputEmStyleControls, + InputLinkStyleControls, + InputLinkVariantsDemoControls, + InputSpoilerStyleControls, + InputStrongStyleControls, +} from './storybookInputStyles'; + +export function splitStyleControls>( + args: InputStoryArgs, + defaults: TControls +): { controls: TControls; rest: InputStoryArgs } { + const controls = { ...defaults }; + const rest = { ...args }; + for (const key of Object.keys(defaults) as (keyof TControls)[]) { + const value = args[key as keyof typeof args]; + if (value !== undefined) { + controls[key] = value as TControls[keyof TControls]; + } + delete rest[key as string]; + } + return { controls, rest: rest as InputStoryArgs }; +} + +export function toInputStrongStyle( + controls: InputStrongStyleControls +): NonNullable { + return { color: controls.color }; +} + +export function toInputEmStyle( + controls: InputEmStyleControls +): NonNullable { + return { color: controls.color }; +} + +export function toInputLinkStyle( + controls: InputLinkStyleControls +): NonNullable { + return { + color: controls.color, + underline: controls.underline, + backgroundColor: controls.backgroundColor, + }; +} + +export function toInputSpoilerStyle( + controls: InputSpoilerStyleControls +): NonNullable { + return { + color: controls.color, + backgroundColor: controls.backgroundColor, + }; +} + +export function toInputLinkVariantsDemoStyle( + controls: InputLinkVariantsDemoControls +): Pick { + const { + jiraVariantColor, + jiraVariantUnderline, + jiraVariantBackgroundColor, + sftpVariantColor, + sftpVariantUnderline, + sftpVariantBackgroundColor, + notionVariantColor, + notionVariantUnderline, + notionVariantBackgroundColor, + ...linkControls + } = controls; + + return { + link: toInputLinkStyle(linkControls), + linkVariants: { + '^jira:': { + color: jiraVariantColor, + underline: jiraVariantUnderline, + backgroundColor: jiraVariantBackgroundColor, + }, + '^sftp:': { + color: sftpVariantColor, + underline: sftpVariantUnderline, + backgroundColor: sftpVariantBackgroundColor, + }, + '^notion:': { + color: notionVariantColor, + underline: notionVariantUnderline, + backgroundColor: notionVariantBackgroundColor, + }, + }, + }; +} diff --git a/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyles.ts b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyles.ts new file mode 100644 index 00000000..6d59db19 --- /dev/null +++ b/apps/example/.rnstorybook/stories/components/EnrichedMarkdownTextInput/shared/storybookInputStyles.ts @@ -0,0 +1,121 @@ +export type InputStrongStyleControls = { + color: string; +}; + +export const inputStrongDefaults: InputStrongStyleControls = { + color: '#1D4ED8', +}; + +export type InputEmStyleControls = { + color: string; +}; + +export const inputEmDefaults: InputEmStyleControls = { + color: '#7C3AED', +}; + +export type InputLinkStyleControls = { + color: string; + underline: boolean; + backgroundColor: string; +}; + +export const inputLinkDefaults: InputLinkStyleControls = { + color: '#2563EB', + underline: true, + backgroundColor: 'transparent', +}; + +export type InputLinkVariantsDemoControls = InputLinkStyleControls & { + jiraVariantColor: string; + jiraVariantUnderline: boolean; + jiraVariantBackgroundColor: string; + sftpVariantColor: string; + sftpVariantUnderline: boolean; + sftpVariantBackgroundColor: string; + notionVariantColor: string; + notionVariantUnderline: boolean; + notionVariantBackgroundColor: string; +}; + +export const inputLinkVariantsDemoDefaults: InputLinkVariantsDemoControls = { + ...inputLinkDefaults, + jiraVariantColor: '#0052CC', + jiraVariantUnderline: false, + jiraVariantBackgroundColor: '#DEEBFF', + sftpVariantColor: '#065F46', + sftpVariantUnderline: false, + sftpVariantBackgroundColor: '#D1FAE5', + notionVariantColor: '#6B21A8', + notionVariantUnderline: false, + notionVariantBackgroundColor: '#F3E8FF', +}; + +export type InputSpoilerStyleControls = { + color: string; + backgroundColor: string; +}; + +export const inputSpoilerDefaults: InputSpoilerStyleControls = { + color: '#FFFFFF', + backgroundColor: '#111827', +}; + +export const inputLinkBaseArgTypes = { + color: { + control: 'color' as const, + description: 'markdownStyle.link.color', + }, + underline: { + control: 'boolean' as const, + description: 'markdownStyle.link.underline', + }, + backgroundColor: { + control: 'color' as const, + description: 'markdownStyle.link.backgroundColor', + }, +}; + +export const inputLinkVariantsArgTypes = { + ...inputLinkBaseArgTypes, + color: { + control: 'color' as const, + description: 'markdownStyle.link.color (fallback for unmatched URLs)', + }, + jiraVariantColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^jira:"].color', + }, + jiraVariantUnderline: { + control: 'boolean' as const, + description: 'markdownStyle.linkVariants["^jira:"].underline', + }, + jiraVariantBackgroundColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^jira:"].backgroundColor', + }, + sftpVariantColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^sftp:"].color', + }, + sftpVariantUnderline: { + control: 'boolean' as const, + description: 'markdownStyle.linkVariants["^sftp:"].underline', + }, + sftpVariantBackgroundColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^sftp:"].backgroundColor', + }, + notionVariantColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^notion:"].color', + }, + notionVariantUnderline: { + control: 'boolean' as const, + description: 'markdownStyle.linkVariants["^notion:"].underline', + }, + notionVariantBackgroundColor: { + control: 'color' as const, + description: 'markdownStyle.linkVariants["^notion:"].backgroundColor', + }, +};