diff --git a/.changeset/cruel-apes-relax.md b/.changeset/cruel-apes-relax.md new file mode 100644 index 000000000..f0b1f31a6 --- /dev/null +++ b/.changeset/cruel-apes-relax.md @@ -0,0 +1,5 @@ +--- +"@launchpad-ui/components": patch +--- + +Figma code connect files, design links and component overview added diff --git a/.storybook/launchpad_logo.svg b/.storybook/launchpad_logo.svg new file mode 100644 index 000000000..6c1d7694c --- /dev/null +++ b/.storybook/launchpad_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.storybook/main.ts b/.storybook/main.ts index aee5883cd..0fa7921c3 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -19,6 +19,29 @@ const config: StorybookConfig = { disableTelemetry: true, }, staticDirs: ['.', { from: '../packages/tokens/dist', to: '/static' }], + managerHead: (head) => ` + ${head} + + `, + previewHead: (head) => ` + ${head} + + + `, async viteFinal(config, { configType }) { const { mergeConfig } = await import('vite'); diff --git a/.storybook/manager.css b/.storybook/manager.css new file mode 100644 index 000000000..07d8982ff --- /dev/null +++ b/.storybook/manager.css @@ -0,0 +1,497 @@ +/* Additional custom styles for Storybook manager UI */ + +/* Brand logo styling */ +.sidebar-header img, +[data-item-id='brand'] img, +.sidebar-brand img { + width: 90% !important; + height: auto !important; + max-width: none !important; +} + +/* Light theme additional styles */ +.light-theme { + /* Custom CSS variables using LaunchPad design tokens */ + --storybook-accent-color: #405bff; /* brand.blue.base */ + --storybook-accent-hover: #7084ff; /* brand.blue.light */ +} + +/* Dark theme additional styles */ +.dark-theme { + /* Custom CSS variables using LaunchPad design tokens */ + --storybook-accent-color: #7084ff; /* brand.blue.light */ + --storybook-accent-hover: #405bff; /* brand.blue.base */ +} + +/* Docs container theming using LaunchPad design tokens */ +.docs-dark { + background-color: #181a1f !important; /* gray.950 */ + color: #eceff2 !important; /* gray.50 */ +} + +.docs-light { + background-color: #ffffff !important; /* white.950 */ + color: #23252a !important; /* gray.900 */ +} + +/* Code blocks in light mode - only target docs content, not Code components */ +:not(.dark-theme) .docblock-argstable code:not(.docs-story code), +:not(.dark-theme) .docblock-argstable-description code:not(.docs-story code), +:not(.dark-theme) .docblock-argstable-body code:not(.docs-story code), +.docs-light .docblock-argstable code:not(.docs-story code), +.docs-light .sbdocs code:not([class*='code_']):not(.docs-story code) { + background-color: #eceff2 !important; /* gray.50 - matching --lp-color-bg-ui-tertiary light */ + color: #238ca3 !important; /* brand.cyan.dark - matching --lp-color-text-code-string light */ + border-color: #d8dde3 !important; /* gray.100 */ + border-radius: 4px !important; /* matching --lp-border-radius-regular */ + padding: 4px 8px !important; /* matching --lp-spacing-100 --lp-spacing-200 */ + font-weight: 400 !important; /* matching --lp-font-weight-regular */ +} + +/* Dark theme docs content styling using LaunchPad design tokens */ +.docs-dark .docblock-argstable, +.docs-dark .docblock-argstable table, +.docs-dark .docblock-argstable tbody, +.docs-dark .docblock-argstable tr, +.docs-dark .docblock-argstable td, +.docs-dark .docblock-argstable th { + background-color: #23252a !important; /* gray.900 */ + color: #eceff2 !important; /* gray.50 */ + border-color: #3f454c !important; /* gray.700 */ +} + +.docs-dark .docblock-argstable th { + background-color: #3f454c !important; /* gray.700 */ + color: #eceff2 !important; /* gray.50 */ +} + +.docs-dark .docblock-argstable-body { + background-color: #23252a !important; /* gray.900 */ +} + +.docs-dark .docblock-code-toggle, +.docs-dark .docblock-code-toggle button { + background-color: #3f454c !important; /* gray.700 */ + color: #eceff2 !important; /* gray.50 */ + border-color: #545a62 !important; /* gray.600 */ +} + +.docs-dark .docblock-code-toggle:hover, +.docs-dark .docblock-code-toggle button:hover { + background-color: #545a62 !important; /* gray.600 */ +} + +/* Code blocks in docs - only target docs content, not Code components */ +.docs-dark .docblock-argstable code, +.docs-dark .sbdocs code:not([class*='code_']), +/* Target specific args table elements only in dark theme */ + .dark-theme .docblock-argstable + code, +.dark-theme .docblock-argstable-description code, +.dark-theme .docblock-argstable-body code { + background-color: #2d3239 !important; /* gray.800 - matching --lp-color-bg-ui-tertiary dark */ + color: #6de0f7 !important; /* brand.cyan.light - matching --lp-color-text-code-string dark */ + border-color: #3f454c !important; /* gray.700 */ + border-radius: 4px !important; /* matching --lp-border-radius-regular */ + padding: 4px 8px !important; /* matching --lp-spacing-100 --lp-spacing-200 */ + font-weight: 400 !important; /* matching --lp-font-weight-regular */ +} + +/* Only target Storybook-specific prism code blocks */ +.docs-dark .prism-code:not([class*='code_']), +.dark-theme .docblock-argstable .prism-code { + background-color: #2d3239 !important; /* gray.800 - matching --lp-color-bg-ui-tertiary dark */ +} + +/* Description and other text content using LaunchPad design tokens */ +.docs-dark .sbdocs-content, +.docs-dark .sbdocs-wrapper, +.docs-dark .sbdocs-preview, +.docs-dark .sbdocs p, +.docs-dark .sbdocs h1, +.docs-dark .sbdocs h2, +.docs-dark .sbdocs h3, +.docs-dark .sbdocs h4, +.docs-dark .sbdocs h5, +.docs-dark .sbdocs h6 { + color: #eceff2 !important; /* gray.50 */ +} + +.docs-dark .sbdocs-wrapper { + background-color: #181a1f !important; /* gray.950 */ +} + +/* Global dark theme styling for docs using LaunchPad design tokens */ +.dark-theme .sbdocs, +.dark-theme .sbdocs-content, +.dark-theme .sbdocs-wrapper, +.dark-theme .sbdocs-preview { + background-color: #181a1f !important; /* gray.950 */ + color: #eceff2 !important; /* gray.50 */ +} + +/* Args table styling for dark theme using LaunchPad design tokens */ +.dark-theme .docblock-argstable, +.dark-theme .docblock-argstable table, +.dark-theme .docblock-argstable tbody, +.dark-theme .docblock-argstable tr, +.dark-theme .docblock-argstable td, +.dark-theme .docblock-argstable th { + background-color: #23252a !important; /* gray.900 */ + color: #eceff2 !important; /* gray.50 */ + border-color: #3f454c !important; /* gray.700 */ +} + +.dark-theme .docblock-argstable th { + background-color: #3f454c !important; /* gray.700 */ + color: #eceff2 !important; /* gray.50 */ +} + +.dark-theme .docblock-argstable-body { + background-color: #23252a !important; /* gray.900 */ +} + +/* Text content in dark theme using LaunchPad design tokens */ +.dark-theme :not(.docs-story) .sbdocs p, +.dark-theme :not(.docs-story) .sbdocs h1, +.dark-theme :not(.docs-story) .sbdocs h2, +.dark-theme :not(.docs-story) .sbdocs h3, +.dark-theme :not(.docs-story) .sbdocs h4, +.dark-theme :not(.docs-story) .sbdocs h5, +.dark-theme :not(.docs-story) .sbdocs h6, +.dark-theme :not(.docs-story) .sbdocs li, +.dark-theme :not(.docs-story) .sbdocs span, +.dark-theme :not(.docs-story) .sbdocs div { + color: #eceff2 !important; /* gray.50 */ + text-decoration: none !important; +} + +/* Removed overly broad selectors to preserve Code.tsx component styling */ + +/* Storybook-specific code styling - only target docs and args table, not actual Code components */ +.sb-main-padded .docblock-argstable code:not(.docs-story code), +.sb-main-padded .sbdocs code:not(.docs-story code), +.docblock-argstable-description code:not(.docs-story code), +#storybook-docs code:not([class*='code_']):not(.docs-story code), +/* Additional specific selectors for argstable code */ + .docblock-argstable td + code:not(.docs-story code), +.docblock-argstable-body code:not(.docs-story code), +.docblock-argstable-row code:not(.docs-story code) { + background-color: #eceff2 !important; /* gray.50 - matching --lp-color-bg-ui-tertiary light */ + color: #238ca3 !important; /* brand.cyan.dark - matching --lp-color-text-code-string light */ + border-radius: 4px !important; + padding: 4px 8px !important; + font-weight: 400 !important; +} + +.dark-theme .sb-main-padded .docblock-argstable code:not(.docs-story code), +.dark-theme .sb-main-padded .sbdocs code:not(.docs-story code), +.dark-theme .docblock-argstable-description code:not(.docs-story code), +.dark-theme #storybook-docs code:not([class*='code_']):not(.docs-story code), +/* Additional specific selectors for argstable code in dark mode */ + .dark-theme .docblock-argstable + td + code:not(.docs-story code), +.dark-theme .docblock-argstable-body code:not(.docs-story code), +.dark-theme .docblock-argstable-row code:not(.docs-story code), +/* More comprehensive selectors for all possible argstable code elements */ + .dark-theme .docblock-argstable + code:not(.docs-story code), +html.dark-theme .docblock-argstable code:not(.docs-story code), +[data-theme='dark'] .docblock-argstable code:not(.docs-story code), +/* Force override with highest specificity */ + .dark-theme div[class*='docblock'] + code:not(.docs-story code), +.dark-theme div[class*='argstable'] code:not(.docs-story code) { + background-color: #2d3239 !important; /* gray.800 - matching --lp-color-bg-ui-tertiary dark */ + color: #6de0f7 !important; /* brand.cyan.light - matching --lp-color-text-code-string dark */ + border-radius: 4px !important; + padding: 4px 8px !important; + font-weight: 400 !important; +} + +/* Override any remaining light backgrounds using LaunchPad design tokens */ +.dark-theme .docs-story, +.dark-theme [data-docs-story] { + background-color: #181a1f !important; /* gray.950 */ +} + +/* Only apply text color to docs story wrapper, not story content */ +.dark-theme .docs-story > div:first-child, +.dark-theme [data-docs-story] > div:first-child { + color: #eceff2 !important; /* gray.50 */ +} + +/* Smooth transitions for theme changes */ +html, +body, +#storybook-root { + transition: + background-color 0.3s ease, + color 0.3s ease; +} + +/* Custom styling for sidebar and toolbar */ +.light-theme .sidebar { + background: #ffffff; + border-color: rgba(0, 0, 0, 0.1); +} + +.dark-theme .sidebar { + background: #1e293b; + border-color: rgba(255, 255, 255, 0.1); +} + +/* All non-docs icon styling - folders and other items */ +.light-theme [data-ref-id*='--'] svg, +.dark-theme [data-ref-id*='--'] svg, +.light-theme .tree-node[data-nodetype='group'] svg, +.dark-theme .tree-node[data-nodetype='group'] svg, +.light-theme [data-item-id*='--'] svg, +.dark-theme [data-item-id*='--'] svg, +/* Target folder icons specifically */ + .light-theme .sidebar-item + svg[data-icon='folder'], +.dark-theme .sidebar-item svg[data-icon='folder'], +.light-theme .tree-folder-icon svg, +.dark-theme .tree-folder-icon svg, +/* Additional selectors for various folder icon implementations */ + .light-theme [data-nodetype='group'] + svg, +.dark-theme [data-nodetype='group'] svg, +.light-theme [data-parent-id] svg, +.dark-theme [data-parent-id] svg, +.light-theme .sidebar-subheading svg, +.dark-theme .sidebar-subheading svg { + color: #405bff !important; + fill: #405bff !important; +} + +/* Docs icon styling */ +.light-theme [data-nodetype='component'] svg, +.dark-theme [data-nodetype='component'] svg, +.light-theme [data-ref-id*='docs'] svg, +.dark-theme [data-ref-id*='docs'] svg, +.light-theme [data-item-id*='docs'] svg, +.dark-theme [data-item-id*='docs'] svg, +/* Target docs icons specifically */ + .light-theme .sidebar-item + svg[data-icon='document'], +.dark-theme .sidebar-item svg[data-icon='document'], +.light-theme .sidebar-item svg[data-icon='doc'], +.dark-theme .sidebar-item svg[data-icon='doc'], +.light-theme .tree-leaf-icon svg, +.dark-theme .tree-leaf-icon svg, +/* Additional selectors for docs icon implementations */ + .light-theme [data-nodetype='story'] + svg, +.dark-theme [data-nodetype='story'] svg { + color: #3dd6f5 !important; + fill: #3dd6f5 !important; +} + +.dark-theme .search-result-item--label { + color: #eceff2 !important; +} + +.search-result-item--label mark { + color: #7084ff !important; +} + +/* Additional selectors for sidepanel items */ +.light-theme .sidebar-item[data-selected='true'], +.dark-theme .sidebar-item[data-selected='true'], +.light-theme .tree-node[data-selected='true'], +.dark-theme .tree-node[data-selected='true'] { + background-color: #425eff !important; + color: white !important; +} + +/* Selected item icons should always be white */ +.light-theme .sidebar-item[data-selected='true'] svg, +.dark-theme .sidebar-item[data-selected='true'] svg, +.light-theme .tree-node[data-selected='true'] svg, +.dark-theme .tree-node[data-selected='true'] svg, +.light-theme .sidebar-item[data-selected='true'] svg, +.dark-theme .sidebar-item[data-selected='true'] svg, +.light-theme .tree-node[data-selected='true'] svg, +.dark-theme .tree-node[data-selected='true'] svg { + color: white !important; + fill: white !important; +} + +/* Documentation links styling - excluding story content */ +.light-theme .sbdocs a:not(.docs-story a), +.light-theme #storybook-docs a:not(.docs-story a), +.light-theme .docblock-argstable a:not(.docs-story a), +.dark-theme .sbdocs a:not(.docs-story a), +.dark-theme #storybook-docs a:not(.docs-story a), +.dark-theme .docblock-argstable a:not(.docs-story a) { + color: #7084ff !important; + text-decoration: underline; +} + +.light-theme .sbdocs a:hover:not(.docs-story a:hover), +.light-theme #storybook-docs a:hover:not(.docs-story a:hover), +.light-theme .docblock-argstable a:hover:not(.docs-story a:hover), +.dark-theme .sbdocs a:hover:not(.docs-story a:hover), +.dark-theme #storybook-docs a:hover:not(.docs-story a:hover), +.dark-theme .docblock-argstable a:hover:not(.docs-story a:hover) { + color: #2536a6 !important; + text-decoration: underline; +} + +/* Story content links should use their natural styling */ +.docs-story a { + all: revert !important; +} + +/* Override specific CSS module classes for docblock-argstable in dark mode */ +/* Order by specificity: lowest to highest */ +.dark-theme .docblock-argstable tbody > tr > *, +.dark-theme .docblock-argstable [class*='css-'] tbody > tr > *, +.dark-theme .docblock-argstable .css-19yaz2m tbody > tr > *, +.dark-theme .docblock-argstable .css-19yaz2m.css-19yaz2m tbody > tr > * { + background-color: #000000 !important; +} + +/* Ensure table cells in dark mode argstable have proper black background */ +.dark-theme .docblock-argstable tbody tr td, +.dark-theme .docblock-argstable tbody tr th { + background-color: #000000 !important; +} + +.dark-theme .docblock-argstable tbody tr * { + background-color: transparent !important; + color: #eceff2 !important; +} + +/* Custom scrollbar styling */ +.light-theme ::-webkit-scrollbar-track { + background: #f8fafc; +} + +.light-theme ::-webkit-scrollbar-thumb { + background: #cbd5e1; +} + +.light-theme ::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +.dark-theme ::-webkit-scrollbar-track { + background: #1e293b; +} + +.dark-theme ::-webkit-scrollbar-thumb { + background: #475569; +} + +.dark-theme ::-webkit-scrollbar-thumb:hover { + background: #64748b; +} + +.dark-theme .sbdocs code:not(.docs-story code):not([class*='code_']), +.dark-theme #storybook-docs code:not(.docs-story code):not([class*='code_']), +.dark-theme .docblock-argstable code:not(.docs-story code):not([class*='code_']), +.dark-theme .sb-unstyled code:not(.docs-story code):not([class*='code_']) { + background-color: #2d3239 !important; + color: #6de0f7 !important; + border-radius: 4px !important; + padding: 4px 8px !important; + font-weight: 400 !important; +} + +/* Fix docblock-source sb-unstyled container background in dark mode */ +.dark-theme .docblock-source.sb-unstyled, +.dark-theme .docblock-source .sb-unstyled, +.dark-theme .sb-unstyled.docblock-source, +.dark-theme .docblock-source, +.dark-theme [class*='docblock-source'] { + background-color: #2d3239 !important; + color: #eceff2 !important; +} + +/* Additional specific targeting for pre and code elements within docblock-source */ +.dark-theme .docblock-source pre, +.dark-theme .docblock-source.sb-unstyled pre, +.dark-theme .sb-unstyled .docblock-source pre { + background-color: #2d3239 !important; + color: #eceff2 !important; +} + +/* Exception: Don't override actual Code component styling or story content */ +.dark-theme [class*='code_'] code, +.dark-theme .docs-story code, +.dark-theme [data-docs-story] code { + background-color: unset !important; + color: unset !important; + border-radius: unset !important; + padding: unset !important; + font-weight: unset !important; +} + +/* Ensure sidebar background is correct in dark mode */ +.dark-theme .sidebar-container, +.dark-theme [data-side='left'], +.dark-theme .os-host-flexbox { + background-color: #23252a !important; +} + +/* Sidebar item hover color */ +.sidebar-item:hover, +[data-nodetype]:hover, +.tree-node:hover, +[role='treeitem']:hover { + background-color: rgba(84, 90, 98, 0.1) !important; +} + +/* Sidebar text color in dark mode */ +.dark-theme .sidebar-item, +.dark-theme [data-nodetype], +.dark-theme .tree-node, +.dark-theme [role='treeitem'], +.dark-theme .sidebar-item span, +.dark-theme [data-nodetype] span, +.dark-theme .tree-node span, +.dark-theme [role='treeitem'] span { + color: white !important; +} + +/* Remove border-right from side panel */ +div[style*='grid-area: sidebar'], +div[style*='grid-area:sidebar'] { + border-right: none !important; +} + +/* Dark mode styling for preview toolbar */ +.dark-theme [data-test-id='sb-preview-toolbar'], +html.dark-theme [data-test-id='sb-preview-toolbar'], +body.dark-theme [data-test-id='sb-preview-toolbar'], +.dark-theme .sb-preview-toolbar, +html.dark-theme .sb-preview-toolbar, +body.dark-theme .sb-preview-toolbar { + background-color: #23252a !important; + color: #eceff2 !important; + border-color: #3f454c !important; + border-top: 1px solid #3f454c !important; +} + +/* Dark mode styling for toolbar buttons */ +.dark-theme [data-test-id='sb-preview-toolbar'] button, +.dark-theme [data-test-id='sb-preview-toolbar'] [role='button'], +.dark-theme .sb-preview-toolbar button, +.dark-theme .sb-preview-toolbar [role='button'] { + background-color: transparent !important; + color: #eceff2 !important; + border-color: #3f454c !important; +} + +.dark-theme [data-test-id='sb-preview-toolbar'] button:hover, +.dark-theme [data-test-id='sb-preview-toolbar'] [role='button']:hover, +.dark-theme .sb-preview-toolbar button:hover, +.dark-theme .sb-preview-toolbar [role='button']:hover { + background-color: #3f454c !important; + color: #ffffff !important; +} diff --git a/.storybook/manager.ts b/.storybook/manager.ts index a5abc9493..fd03fc83e 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,7 +1,39 @@ -import { addons } from 'storybook/manager-api'; +import { addons } from 'storybook/internal/manager-api'; +import { darkTheme, lightTheme } from './themes'; + +// Set the default theme addons.setConfig({ - sidebar: { - collapsedRoots: ['legacy'], - }, + theme: lightTheme, }); + +// Listen for theme changes +if (typeof window !== 'undefined') { + // Set initial theme based on system preference + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + if (prefersDark) { + document.documentElement.classList.add('dark-theme'); + } else { + document.documentElement.classList.add('light-theme'); + } + + // Listen for theme changes via custom events or global state + // This will be triggered by the theme decorator + window.addEventListener('storage', (e) => { + if (e.key === 'storybook-theme') { + const currentTheme = e.newValue; + const theme = currentTheme === 'dark' ? darkTheme : lightTheme; + + addons.setConfig({ theme }); + + const managerRoot = document.documentElement; + if (currentTheme === 'dark') { + managerRoot.classList.add('dark-theme'); + managerRoot.classList.remove('light-theme'); + } else { + managerRoot.classList.add('light-theme'); + managerRoot.classList.remove('dark-theme'); + } + } + }); +} diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 0d093a2a1..b9914dc84 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -6,12 +6,12 @@ import { Box } from '@launchpad-ui/box'; import sprite from '@launchpad-ui/icons/img/sprite.svg'; import { withThemeByDataAttribute } from '@storybook/addon-themes'; import { BrowserRouter, useNavigate } from 'react-router'; -import { themes } from 'storybook/theming'; import { RouterProvider as AriaRouterProvider } from '../packages/components/src/RouterProvider'; import { useHref } from '../packages/components/src/utils'; import custom from './custom.svg'; import { allModes } from './modes'; +import { darkTheme, lightTheme } from './themes'; import '../packages/components/src/styles/base.css'; import '../packages/components/src/styles/themes.css'; @@ -64,6 +64,7 @@ const parameters: Parameters = { method: 'alphabetical', order: [ 'Getting started', + 'Component Overview', 'Contributing', 'Guidelines', 'Tokens', @@ -91,12 +92,62 @@ const parameters: Parameters = { prefersReducedMotion: 'reduce', }, docs: { - theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? themes.dark : themes.light, + theme: lightTheme, // Default theme, will be updated dynamically codePanel: true, }, }; const decorators: DecoratorFunction[] = [ + // Dynamic theme updater for Storybook UI + (StoryFn, context) => { + const currentTheme = context.globals.theme || 'default'; + const isDark = currentTheme === 'dark'; + + // Update the docs theme dynamically + if (context.parameters.docs) { + context.parameters.docs.theme = isDark ? darkTheme : lightTheme; + } + + // Apply theme classes to the document + if (typeof window !== 'undefined') { + // Update current document (preview iframe) + const currentRoot = document.documentElement; + if (isDark) { + currentRoot.classList.add('dark-theme'); + currentRoot.classList.remove('light-theme'); + } else { + currentRoot.classList.add('light-theme'); + currentRoot.classList.remove('dark-theme'); + } + + // Communicate theme change to manager via localStorage + localStorage.setItem('storybook-theme', currentTheme); + window.dispatchEvent( + new StorageEvent('storage', { + key: 'storybook-theme', + newValue: currentTheme, + }), + ); + + // Also try to update the parent document (manager) + if (window.parent && window.parent !== window) { + try { + const managerRoot = window.parent.document.documentElement; + if (isDark) { + managerRoot.classList.add('dark-theme'); + managerRoot.classList.remove('light-theme'); + } else { + managerRoot.classList.add('light-theme'); + managerRoot.classList.remove('dark-theme'); + } + } catch (_e) { + // Cross-origin restrictions, ignore + } + } + } + + return ; + }, (StoryFn, context) => { const mirror = context.viewMode === 'story' ? context.globals.mirror : undefined; const sideBySide = mirror === 'side-by-side'; @@ -138,6 +189,7 @@ const decorators: DecoratorFunction[] = [ dark: 'dark', }, defaultTheme: 'default', + attributeName: 'data-theme', }), ]; @@ -157,6 +209,170 @@ const globalTypes: GlobalTypes = { }, }; +// Auto-enhance argTypes based on prop types +const enhanceArgTypes = (context: any) => { + const { argTypes } = context; + const enhanced: any = {}; + + Object.keys(argTypes || {}).forEach((key) => { + const argType = argTypes[key]; + + // Skip if already has a control defined, is disabled, or is ref + if (argType.control !== undefined || argType.table?.disable) { + return; + } + + // Auto-detect control type based on prop type + if (argType.type) { + const typeName = argType.type.name || ''; + + // Function props → disable completely + if ( + typeName === 'func' || + typeName === 'function' || + key.startsWith('on') || + key === 'ref' || + (key === 'children' && argType.type !== 'string') + ) { + enhanced[key] = { + ...argType, + control: { disable: true }, + }; + } + // Boolean props → toggle + else if (typeName === 'boolean' || (typeName === 'enum' && argType.type.value.length === 2)) { + enhanced[key] = { + ...argType, + control: { type: 'boolean' }, + }; + } + // Check for boolean unions (like boolean | undefined) → checkbox + else if (typeName === 'union' && argType.type.value) { + const hasBoolean = argType.type.value.some((v: any) => v.name === 'boolean'); + + if (hasBoolean) { + enhanced[key] = { + ...argType, + control: { type: 'boolean' }, + }; + } else { + // Component-specific props that should be radio buttons (whitelist approach) + const componentProps = [ + 'size', + 'variant', + 'spacing', + 'orientation', + 'color', + 'appearance', + 'position', + 'placement', + ]; + + if (componentProps.includes(key)) { + const options = argType.type.value + .filter((v: any) => { + if (v.name === 'literal') { + const value = v.value; + return ( + value !== null && + value !== undefined && + value !== 'null' && + value !== 'undefined' && + value !== '' && + typeof value === 'string' + ); + } + return v.name !== 'null' && v.name !== 'undefined' && v.name !== 'void'; + }) + .map((v: any) => v.value || v.name) + .filter(Boolean); + + if (options.length > 0) { + // + if (options.length <= 5) { + enhanced[key] = { + ...argType, + control: { type: 'radio' }, + options, + }; + } else { + enhanced[key] = { + ...argType, + control: { type: 'select' }, + options, + }; + } + } + } else { + // All other string/union props → text input + enhanced[key] = { + ...argType, + control: { type: 'text' }, + }; + } + } + } + // Enum types → radio or select (for whitelisted component props only) + else if (typeName === 'enum' && argType.type.value) { + const componentProps = [ + 'size', + 'variant', + 'spacing', + 'orientation', + 'color', + 'appearance', + 'position', + 'placement', + ]; + + if (componentProps.includes(key)) { + const options = argType.type.value + .filter((v: any) => v !== null && v !== undefined && v !== 'null' && v !== 'undefined') + .map((v: any) => v); + + if (options.length > 0) { + if (options.length <= 5) { + enhanced[key] = { + ...argType, + control: { type: 'radio' }, + options, + }; + } else { + enhanced[key] = { + ...argType, + control: { type: 'select' }, + options, + }; + } + } + } else { + // Non-whitelisted enums → text input + enhanced[key] = { + ...argType, + control: { type: 'text' }, + }; + } + } + // All string props → text input + else if (typeName === 'string') { + enhanced[key] = { + ...argType, + control: { type: 'text' }, + }; + } + // Number props → number input + else if (typeName === 'number') { + enhanced[key] = { + ...argType, + control: { type: 'number' }, + }; + } + } + }); + + return enhanced; +}; + const preview: Preview = { tags: ['autodocs'], parameters, @@ -165,6 +381,38 @@ const preview: Preview = { initialGlobals: { backgrounds: { value: 'default' }, }, + // Auto-enhance argTypes for better controls + argTypesEnhancers: [(context) => enhanceArgTypes(context)], + // Global argTypes that apply to all stories + argTypes: { + // Hide function props globally by default to keep the prop table skimmable + onPressStart: { table: { disable: true } }, + onPressEnd: { table: { disable: true } }, + onPressChange: { table: { disable: true } }, + onPressUp: { table: { disable: true } }, + onFocusChange: { table: { disable: true } }, + onHoverStart: { table: { disable: true } }, + onHoverEnd: { table: { disable: true } }, + preventFocusOnPress: { table: { disable: true } }, + excludeFromTabOrder: { table: { disable: true } }, + + // Hide technical props + style: { table: { disable: true } }, + UNSAFE_className: { table: { disable: true } }, + UNSAFE_style: { table: { disable: true } }, + + // Hide verbose aria props (keep common ones like aria-label visible) + 'aria-controls': { table: { disable: true } }, + 'aria-expanded': { table: { disable: true } }, + 'aria-haspopup': { table: { disable: true } }, + 'aria-pressed': { table: { disable: true } }, + 'aria-required': { table: { disable: true } }, + 'aria-roledescription': { table: { disable: true } }, + 'aria-selected': { table: { disable: true } }, + 'aria-describedby': { table: { disable: true } }, + 'aria-details': { table: { disable: true } }, + 'aria-labelledby': { table: { disable: true } }, + }, }; export default preview; diff --git a/.storybook/themes.ts b/.storybook/themes.ts new file mode 100644 index 000000000..46197445f --- /dev/null +++ b/.storybook/themes.ts @@ -0,0 +1,89 @@ +import { create } from 'storybook/theming'; + +// Base configuration +const baseTheme = { + brandTitle: 'LaunchPad UI', + brandUrl: 'https://github.com/launchdarkly/launchpad-ui', + brandImage: './launchpad_logo.svg', + brandTarget: '_self', + + // Typography + fontBase: '"Inter", sans-serif', + fontCode: '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Courier New", monospace', + + // Toolbar default and active colors + barSelectedColor: '#405BFF', // brand.blue.base + barBg: 'transparent', + + // Form colors + inputBorder: '#D8DDE3', // gray.100 + inputTextColor: '#23252A', // gray.900 + inputBorderRadius: 6, +}; + +export const lightTheme = create({ + base: 'light', + ...baseTheme, + + // Storybook-specific color palette using LaunchPad design tokens + colorPrimary: '#405BFF', // brand.blue.base + colorSecondary: '#23252A', // gray.900 + + // UI colors + appBg: '#FFFFFF', // white.950 + appContentBg: '#F7F9FB', // gray.0 + appPreviewBg: '#FFFFFF', // white.950 + appBorderColor: '#D8DDE3', // gray.100 + appBorderRadius: 8, + + // Text colors using gray scale + textColor: '#23252A', // gray.900 + textInverseColor: '#FFFFFF', // white.950 + textMutedColor: '#6C727A', // gray.500 + + // Toolbar colors + barTextColor: '#6C727A', // gray.500 + barHoverColor: '#7084FF', // brand.blue.light + + // Button colors + buttonBg: '#F7F9FB', // gray.0 + buttonBorder: '#D8DDE3', // gray.100 + + // Form colors + inputBg: '#FFFFFF', // white.950 +}); + +export const darkTheme = create({ + base: 'dark', + ...baseTheme, + + // Storybook-specific color palette using LaunchPad design tokens + colorPrimary: '#7084FF', // brand.blue.light (better for dark backgrounds) + colorSecondary: '#ECEFF2', // gray.50 + + // UI colors using darker grays + appBg: '#181A1F', // gray.950 + appContentBg: '#23252A', // gray.900 + appPreviewBg: '#181A1F', // gray.950 + appBorderColor: '#3F454C', // gray.700 + appBorderRadius: 8, + + // Text colors using gray scale + textColor: '#ECEFF2', // gray.50 + textInverseColor: '#23252A', // gray.900 + textMutedColor: '#A9AFB4', // gray.300 + + // Toolbar colors + barTextColor: '#A9AFB4', // gray.300 + barHoverColor: '#7084FF', // brand.blue.light + barBg: '#23252A', // gray.900 + + // Button colors + buttonBg: '#3F454C', // gray.700 + buttonBorder: '#545A62', // gray.600 + + // Form colors + inputBg: '#3F454C', // gray.700 + inputTextColor: '#ECEFF2', // gray.50 + inputBorder: '#545A62', // gray.600 +}); diff --git a/AI_PROMPT_HEADER.md b/AI_PROMPT_HEADER.md new file mode 100644 index 000000000..d8959ad4e --- /dev/null +++ b/AI_PROMPT_HEADER.md @@ -0,0 +1,96 @@ +# AI Code Generation Prompt Header + +This document contains a standardized prompt header to use when working with AI assistants (Claude, ChatGPT, etc.) to generate code for the LaunchDarkly LaunchPad UI project. This ensures consistent, high-quality code that follows our established patterns and standards. + +## The Prompt Header + +Copy and paste this entire section at the beginning of any AI conversation when generating code: + +--- + +**Always follow these LaunchDarkly LaunchPad UI standards:** + +- **Use only approved LaunchPad components** from `@launchpad-ui/components`, `@launchpad-ui/icons`, and other LaunchPad packages. Never create custom components when LaunchPad equivalents exist. + +- **Import components using named imports** following the pattern: `import { Button, Alert, Icon } from '@launchpad-ui/components'` and `import { Icon, StatusIcon } from '@launchpad-ui/icons'`. Never use default exports (enforced by Biome linting). + +- **Use design tokens exclusively** with CSS variables prefixed `--lp-` for colors, spacing, typography, borders, and shadows. Examples: `var(--lp-color-bg-ui-primary)`, `var(--lp-spacing-400)`, `var(--lp-border-radius-medium)`. Never hardcode values. + +- **Follow React Aria Components foundation** - all components extend React Aria Components with additional LaunchPad styling and tokens. Use proper ARIA attributes and semantic HTML for accessibility compliance. + +- **Use string union types for props** instead of enums (e.g., `size="medium"`, `variant="primary"`, `status="error"`). This reduces bundle size and improves DX without requiring additional imports. + +- **Apply CSS Modules for styling** with scoped class names using the pattern `styles.className`. Use `class-variance-authority` (cva) for component variants with the structure: base styles + variants + defaultVariants. + +- **Include comprehensive JSDoc comments** for all components and exported functions, especially describing props, usage examples, and referencing React Aria documentation URLs. + +- **Follow Biome code formatting rules** - single quotes for strings, double quotes for JSX attributes, 100 character line width, organized imports with type imports first, and no default exports except for stories/config files. + +- **Ensure accessibility compliance** - components must pass automated a11y tests using Playwright and axe-core. Include proper ARIA labels, focus management, and keyboard navigation support. + +- **Use Context patterns for component composition** - create `ComponentContext` for shared state and `useLPContextProps` utility for prop merging. Export both component and context for advanced use cases. + +- **Test components using React Testing Library** with `@testing-library/react` utilities, focusing on user interactions and accessibility. Use `render()` from the project's test utils, not directly from RTL. + +--- + +## How to Use + +### 1. Copy the Header +Copy the entire prompt header section above (everything between the horizontal rules). + +### 2. Start Your AI Conversation +Paste the header at the beginning of your first message to the AI, then add your specific code generation request. + +### 3. Example Usage + +``` +[Paste the prompt header here] + +Now please create a React component for a user settings form that includes: +- Text inputs for name and email +- Toggle switches for notifications +- A save button with loading state +- Proper validation and error handling + +Make it accessible and follow the LaunchPad design system. +``` + +## What This Ensures + +Using this prompt header guarantees that AI-generated code will: +- Use existing LaunchPad components instead of creating new ones +- Follow design token system for consistent styling +- Meet accessibility standards (WCAG 2.1 AA) +- Use correct TypeScript patterns and import conventions +- Pass Biome linting rules +- Follow React Aria Components foundation +- Include proper JSDoc documentation +- Use established testing patterns + +## Available Components + +Key components available in `@launchpad-ui/components`: +- **Buttons**: `Button`, `IconButton`, `LinkButton`, `ToggleButton` +- **Forms**: `TextField`, `TextArea`, `Checkbox`, `Radio`, `Select`, `Switch` +- **Feedback**: `Alert`, `Toast`, `ProgressBar`, `Meter` +- **Layout**: `Modal`, `Popover`, `Drawer`, `Tabs`, `Disclosure` +- **Data**: `Table`, `ListBox`, `GridList`, `Menu`, `ComboBox` +- **Typography**: `Text`, `Heading`, `Label`, `Code` +- **Navigation**: `Link`, `Breadcrumbs`, `Navigation` + +And many more! See the [full component list](packages/components/src/index.ts). + +## Team Guidelines + +- **Always use this header** when working with AI assistants for code generation +- **Share this document** with new team members during onboarding +- **Update the header** as our component library and standards evolve +- **Report issues** if AI-generated code doesn't follow these standards despite using the header + +## Related Documentation + +- [Component Architecture Decision Records](docs/) +- [Accessibility Testing Guidelines](docs/adr-007-a11y-tests-on-stories.md) +- [Styling Conventions](docs/adr-005-styles.md) +- [Component Foundation](docs/adr-008-component-foundation.md) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ad871d44..e87c4d7bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to LaunchPad UI -:+1::tada: First off, thanks for taking the time to read our contribution docs! :tada::+1: +First off, thanks for taking the time to read our contribution docs! The following is a set of guidelines for contributing to LaunchPad and its packages, which are hosted in the [LaunchDarkly Organization](https://github.com/launchdarkly) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. @@ -162,6 +162,24 @@ Run this command to start a local instance in your browser: $ pnpm storybook ``` +### Figma Code Connect + +Figma Code Connect is a tool to connect LaunchPad code with LaunchPad Figma Design System real-time. When developers will click component or its insance in design, they will be directed which component and code they should use for implementing the design. + +Using Figma connect requires generating an access Token in LaunchDarkly Figma. You can learn more about code connect [here](https://www.figma.com/code-connect-docs/). + +Test Code Connect Build locally: + +``` +npx figma connect publish --dry-run +``` + +Publish all changes to Figma Dev Mode: + +``` +npx figma connect publish +``` + ### Running Tests #### Unit Tests diff --git a/README.md b/README.md index f801abeaf..3e1514fd0 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ $ yarn add @launchpad-ui/components @launchpad-ui/icons @launchpad-ui/tokens [![version](https://img.shields.io/visual-studio-marketplace/v/LaunchDarklyOfficial.launchpad-design-system?label=LaunchPad%20VS%20Code%20extension)](https://marketplace.visualstudio.com/items?itemName=LaunchDarklyOfficial.launchpad-design-system) +## AI Code Generation + +For consistent, high-quality AI-generated code that follows LaunchPad standards, see our [AI Prompt Header guide](AI_PROMPT_HEADER.md). This ensures AI assistants use our design system correctly and follow established patterns. + ## Contributing We welcome contributions! See the [contributing docs](https://github.com/launchdarkly/launchpad-ui/blob/main/CONTRIBUTING.md) to learn how to get started. @@ -34,3 +38,14 @@ We welcome contributions! See the [contributing docs](https://github.com/launchd - LaunchDarkly employees can reach out with questions or comments in [our Slack channel, #ask-launchpad-design-system](https://launchdarkly.slack.com/channels/CDXEFNMLP) - You can also [start a discussion](https://github.com/launchdarkly/launchpad-ui/discussions) in the LaunchPad repository to ask a question! + + +## Figma MCP Server Setup + +If you'd like to generate new components or recipes with AI using [Figma MCP Server](https://www.figma.com/blog/introducing-figmas-dev-mode-mcp-server/), follow the instructions [here](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server). + +Note that using Figma MCP server requires Dev or Full seat in Figma. For the best results it's recommended to use LaunchPad components and auto layout in your designs to ensure the generated code is mapped correctly to components and tokens instead of generating raw HTML code and styles. + +Once the MCP is enabled in your Figma and IDE, open the AI chat as usual and use prompt like: + +Create a new code example in recipes that displays the selected Figma File layout. Use the design system components and use figma.tsx files to help with the mapping. \ No newline at end of file diff --git a/apps/vscode/images/launchpad_ui.webp b/apps/vscode/images/launchpad_ui.webp new file mode 100644 index 000000000..23ea9b721 Binary files /dev/null and b/apps/vscode/images/launchpad_ui.webp differ diff --git a/docs/stories/getting-started.mdx b/docs/stories/getting-started.mdx index 77a56808f..f2ddaf64e 100644 --- a/docs/stories/getting-started.mdx +++ b/docs/stories/getting-started.mdx @@ -1,7 +1,10 @@ import { Meta, Markdown } from '@storybook/addon-docs/blocks'; import README from '../../README.md?raw'; +import launchpadImage from '../../apps/vscode/images/launchpad_ui.webp'; +LaunchPad UI + {README} \ No newline at end of file diff --git a/packages/box/stories/Box.stories.tsx b/packages/box/stories/Box.stories.tsx index 35f2e1b44..98cce3476 100644 --- a/packages/box/stories/Box.stories.tsx +++ b/packages/box/stories/Box.stories.tsx @@ -6,6 +6,7 @@ import { Box } from '../src'; export default { component: Box, title: 'Legacy/Experimental/Box', + tags: ['deprecated'], description: '', }; diff --git a/packages/button/stories/Button.stories.tsx b/packages/button/stories/Button.stories.tsx index a0cea7f2c..8e4e79858 100644 --- a/packages/button/stories/Button.stories.tsx +++ b/packages/button/stories/Button.stories.tsx @@ -46,6 +46,7 @@ const buttonTemplateWithStates: Decorator = (storyComponent, context) => { export default { component: Button, title: 'Legacy/Button', + tags: ['deprecated'], description: 'Buttons trigger actions based on user interaction.', decorators: [buttonTemplateWithStates], parameters: { diff --git a/packages/button/stories/ButtonGroup.stories.tsx b/packages/button/stories/ButtonGroup.stories.tsx index 6018d7e21..33f5d0121 100644 --- a/packages/button/stories/ButtonGroup.stories.tsx +++ b/packages/button/stories/ButtonGroup.stories.tsx @@ -50,6 +50,7 @@ const buttonTemplateWithStates: Decorator = (storyComponent, context) => { export default { component: ButtonGroup, title: 'Legacy/Button/ButtonGroup', + tags: ['deprecated'], description: 'ButtonGroups group related actions and trigger them based on user interaction.', decorators: [buttonTemplateWithStates], parameters: { diff --git a/packages/button/stories/IconButton.stories.tsx b/packages/button/stories/IconButton.stories.tsx index 711bfed1d..caf10f8eb 100644 --- a/packages/button/stories/IconButton.stories.tsx +++ b/packages/button/stories/IconButton.stories.tsx @@ -48,6 +48,7 @@ const buttonTemplateWithStates: Decorator = (storyComponent, context) => { export default { component: IconButton, title: 'Legacy/Button/IconButton', + tags: ['deprecated'], description: 'Buttons trigger actions based on user interaction.', decorators: [buttonTemplateWithStates], parameters: { diff --git a/packages/button/stories/UploadButton.stories.tsx b/packages/button/stories/UploadButton.stories.tsx index bb04c1189..1579d33e2 100644 --- a/packages/button/stories/UploadButton.stories.tsx +++ b/packages/button/stories/UploadButton.stories.tsx @@ -5,6 +5,7 @@ import { UploadButton } from '../src'; export default { component: UploadButton, title: 'Legacy/Button/UploadButton', + tags: ['deprecated'], description: 'UploadButtons trigger a native file upload experience.', parameters: { chromatic: { disableSnapshot: true }, diff --git a/packages/components/figma/Alert.figma.tsx b/packages/components/figma/Alert.figma.tsx new file mode 100644 index 000000000..38bedc73c --- /dev/null +++ b/packages/components/figma/Alert.figma.tsx @@ -0,0 +1,53 @@ +import figma from '@figma/code-connect'; + +import { Alert } from '../src/Alert'; +import { Heading } from '../src/Heading'; +import { Text } from '../src/Text'; + +// Alert with header (when status and variant are undefined/default) +figma.connect( + Alert, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=8003%3A631', + { + props: { + isDismissable: figma.boolean('Dismissible'), + headerText: figma.string('Header'), + messageText: figma.string('Message'), + }, + example: (props) => ( + {}}> + {props.headerText} + {props.messageText} + + ), + }, +); + +// Alert without header (when variant is inline) +figma.connect( + Alert, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=8003%3A631', + { + variant: { Variant: 'Inline' }, + props: { + isDismissable: figma.boolean('Dismissible'), + status: figma.enum('State', { + Information: 'info', + }), + variant: figma.enum('Variant', { + Inline: 'inline', + }), + messageText: figma.string('Message'), + }, + example: (props) => ( + {}} + > + {props.messageText} + + ), + }, +); diff --git a/packages/components/figma/Avatar.figma.tsx b/packages/components/figma/Avatar.figma.tsx new file mode 100644 index 000000000..77787dab5 --- /dev/null +++ b/packages/components/figma/Avatar.figma.tsx @@ -0,0 +1,56 @@ +import figma from '@figma/code-connect'; + +import { Avatar } from '../src/Avatar'; + +// Avatar with image +figma.connect( + Avatar, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A43538', + { + variant: { Kind: 'Image' }, + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + }, + example: (props) => ( + + ), + }, +); + +// Avatar with initials +figma.connect( + Avatar, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A43538', + { + variant: { Kind: 'Initials' }, + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + }, + example: (props) => JD, + }, +); + +// Avatar with icon (default) +figma.connect( + Avatar, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A43538', + { + variant: { Kind: 'Icon' }, + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + }, + example: (props) => , + }, +); diff --git a/packages/components/figma/BadgeIcon.figma.tsx b/packages/components/figma/BadgeIcon.figma.tsx new file mode 100644 index 000000000..e7441390a --- /dev/null +++ b/packages/components/figma/BadgeIcon.figma.tsx @@ -0,0 +1,34 @@ +import figma from '@figma/code-connect'; + +import { BadgeIcon } from '../../icons/src/BadgeIcon'; + +figma.connect( + BadgeIcon, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A3636', + { + props: { + size: figma.enum('Size', { + '16 (tiny)': 'tiny', + '24 (small)': 'small', + '36 (medium)': 'medium', + '64 (large)': 'large', + }), + color: figma.enum('Color', { + Gray: 'gray', + Blue: 'blue', + Cyan: 'cyan', + Purple: 'purple', + Pink: 'pink', + Orange: 'orange', + Yellow: 'yellow', + Green: 'green', + Gradient1: 'gradient-1', + Gradient2: 'gradient-2', + Gradient3: 'gradient-3', + Gradient4: 'gradient-4', + Gradient5: 'gradient-5', + }), + }, + example: (props) => , + }, +); diff --git a/packages/components/figma/ButtonGroup.figma.tsx b/packages/components/figma/ButtonGroup.figma.tsx new file mode 100644 index 000000000..c9e4124e6 --- /dev/null +++ b/packages/components/figma/ButtonGroup.figma.tsx @@ -0,0 +1,19 @@ +import figma from '@figma/code-connect'; + +import { ButtonGroup } from '../src'; + +figma.connect( + ButtonGroup, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-28639', + { + props: { + children: figma.children(['Button']), + spacing: figma.enum('Type', { + Basic: 'basic', + Compact: 'compact', + Large: 'large', + }), + }, + example: ({ spacing, children }) => {children}, + }, +); diff --git a/packages/components/figma/Calendar.figma.tsx b/packages/components/figma/Calendar.figma.tsx new file mode 100644 index 000000000..68f5df0dc --- /dev/null +++ b/packages/components/figma/Calendar.figma.tsx @@ -0,0 +1,14 @@ +import figma from '@figma/code-connect'; + +import { Calendar } from '../src/Calendar'; + +figma.connect( + Calendar, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10675%3A815', + { + props: { + buttons: figma.children(['Button']), + }, + example: () => , + }, +); diff --git a/packages/components/figma/Checkbox.figma.tsx b/packages/components/figma/Checkbox.figma.tsx new file mode 100644 index 000000000..725ac9d5d --- /dev/null +++ b/packages/components/figma/Checkbox.figma.tsx @@ -0,0 +1,26 @@ +import figma from '@figma/code-connect'; + +import { Checkbox } from '../src/Checkbox'; + +figma.connect( + Checkbox, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A32735', + { + props: { + isIndeterminate: figma.enum('Check', { + Indeterminate: true, + }), + label: figma.boolean('Has label', { + true: figma.children(['Label']), + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ label, isIndeterminate, isInvalid, isDisabled }) => ( + + {label} + + ), + }, +); diff --git a/packages/components/figma/CheckboxGroup.figma.tsx b/packages/components/figma/CheckboxGroup.figma.tsx new file mode 100644 index 000000000..0f3ca9713 --- /dev/null +++ b/packages/components/figma/CheckboxGroup.figma.tsx @@ -0,0 +1,32 @@ +import figma from '@figma/code-connect'; + +import { CheckboxGroup } from '../src/CheckboxGroup'; + +figma.connect( + CheckboxGroup, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A33127', + { + props: { + validationMessage: figma.string('Validation message'), + description: figma.boolean('Description?', { + true: figma.string('Description'), + false: undefined, + }), + label: figma.boolean('Has label?', { + true: figma.children(['Label']), + false: undefined, + }), + direction: figma.enum('Direction', { + Vertical: 'vertical', + Horizontal: 'horizontal', + }), + children: figma.children(['Checkbox']), + }, + example: (props) => ( + <> + {props.label} + {props.children} + + ), + }, +); diff --git a/packages/components/figma/Code.figma.tsx b/packages/components/figma/Code.figma.tsx new file mode 100644 index 000000000..ec5af4ecd --- /dev/null +++ b/packages/components/figma/Code.figma.tsx @@ -0,0 +1,14 @@ +import figma from '@figma/code-connect'; + +import { Code } from '../src'; + +figma.connect( + Code, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=2308-667', + { + props: { + children: figma.string('Inline text'), + }, + example: ({ children }) => {children}, + }, +); diff --git a/packages/components/figma/DateField.figma.tsx b/packages/components/figma/DateField.figma.tsx new file mode 100644 index 000000000..68441675d --- /dev/null +++ b/packages/components/figma/DateField.figma.tsx @@ -0,0 +1,28 @@ +import figma from '@figma/code-connect'; + +import { DateField } from '../src/DateField'; + +figma.connect( + DateField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10726%3A60155', + { + props: { + label: figma.boolean('Label?', { + true: figma.children(['Label']), + false: undefined, + }), + helpText: figma.boolean('Help text?', { + true: figma.string('Help text'), + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ label, isInvalid, isDisabled }) => ( + <> + {label} + + + ), + }, +); diff --git a/packages/components/figma/DatePicker.figma.tsx b/packages/components/figma/DatePicker.figma.tsx new file mode 100644 index 000000000..3ad3cea25 --- /dev/null +++ b/packages/components/figma/DatePicker.figma.tsx @@ -0,0 +1,48 @@ +import figma from '@figma/code-connect'; + +import { DatePicker } from '../src/DatePicker'; + +figma.connect( + DatePicker, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10833%3A38082', + { + variant: { 'Label?': 'false' }, + props: { + helpText: figma.boolean('Help text?', { + true: figma.string('Help text'), + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ isInvalid, isDisabled }) => ( + + ), + }, +); + +figma.connect( + DatePicker, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10833%3A38082', + { + variant: { 'Label?': 'true' }, + props: { + label: figma.boolean('Label?', { + true: figma.children(['Label']), + false: undefined, + }), + helpText: figma.boolean('Help text?', { + true: figma.string('Help text'), + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ label, isInvalid, isDisabled }) => ( + <> + {label} + + + ), + }, +); diff --git a/packages/components/figma/FieldGroup.figma.tsx b/packages/components/figma/FieldGroup.figma.tsx new file mode 100644 index 000000000..0408c9be3 --- /dev/null +++ b/packages/components/figma/FieldGroup.figma.tsx @@ -0,0 +1,22 @@ +import figma from '@figma/code-connect'; + +import { FieldGroup } from '../src/FieldGroup'; + +figma.connect( + FieldGroup, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A33319', + { + props: { + variant: figma.enum('Variant', { + Default: 'default', + Custom: 'custom', + }), + children: figma.children(['TextField', 'Label']), + legend: figma.boolean('Legend?', { + true: figma.string('Legend'), + false: undefined, + }), + }, + example: ({ children, legend }) => {children}, + }, +); diff --git a/packages/components/figma/FileTrigger.figma.tsx b/packages/components/figma/FileTrigger.figma.tsx new file mode 100644 index 000000000..c2a586058 --- /dev/null +++ b/packages/components/figma/FileTrigger.figma.tsx @@ -0,0 +1,14 @@ +import figma from '@figma/code-connect'; + +import { FileTrigger } from '../src/FileTrigger'; + +figma.connect( + FileTrigger, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-28805', + { + props: { + children: figma.children(['Button']), + }, + example: ({ children }) => {children}, + }, +); diff --git a/packages/components/figma/Label.figma.tsx b/packages/components/figma/Label.figma.tsx new file mode 100644 index 000000000..cda2e8646 --- /dev/null +++ b/packages/components/figma/Label.figma.tsx @@ -0,0 +1,39 @@ +import figma from '@figma/code-connect'; + +import { Label } from '../src/Label'; +import { Text } from '../src/Text'; + +figma.connect( + Label, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A34227', + { + variant: { 'Description?': 'false' }, + props: { + label: figma.textContent('Label'), + required: figma.boolean('Required?'), + }, + example: ({ label }) => , + }, +); + +figma.connect( + Label, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A34227', + { + variant: { 'Description?': 'true' }, + props: { + label: figma.textContent('Label'), + required: figma.boolean('Required?'), + description: figma.boolean('Description?', { + true: figma.textContent('Description'), + false: undefined, + }), + }, + example: ({ label, description }) => ( + <> + + {description} + + ), + }, +); diff --git a/packages/components/figma/Link.figma.tsx b/packages/components/figma/Link.figma.tsx new file mode 100644 index 000000000..71d9669ba --- /dev/null +++ b/packages/components/figma/Link.figma.tsx @@ -0,0 +1,27 @@ +import figma from '@figma/code-connect'; + +import { Link } from '../src/Link'; + +figma.connect( + Link, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A35013', + { + props: { + size: figma.enum('Size', { + Body1: 'body-1', + Body2: 'body-2', + Small1: 'small-1', + }), + fontWeight: figma.enum('Font weight', { + Regular: 'regular', + Semibold: 'semibold', + }), + link: figma.string('Link'), + variant: figma.enum('Style', { + Default: 'default', + Subtle: 'subtle', + }), + }, + example: ({ variant }) => Link, + }, +); diff --git a/packages/components/figma/LinkButton.figma.tsx b/packages/components/figma/LinkButton.figma.tsx new file mode 100644 index 000000000..5b1555c41 --- /dev/null +++ b/packages/components/figma/LinkButton.figma.tsx @@ -0,0 +1,22 @@ +import figma from '@figma/code-connect'; + +import { LinkButton } from '../src/LinkButton'; + +/** + * -- This file was auto-generated by Code Connect -- + * None of your props could be automatically mapped to Figma properties. + * You should update the `props` object to include a mapping from your + * code props to Figma properties, and update the `example` function to + * return the code example you'd like to see in Figma + */ + +figma.connect( + LinkButton, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A35126', + { + props: { + children: figma.children(['Button']), + }, + example: ({ children }) => {children}, + }, +); diff --git a/packages/components/figma/LinkIconButton.figma.tsx b/packages/components/figma/LinkIconButton.figma.tsx new file mode 100644 index 000000000..cac3b8326 --- /dev/null +++ b/packages/components/figma/LinkIconButton.figma.tsx @@ -0,0 +1,11 @@ +import figma from '@figma/code-connect'; + +import { LinkIconButton } from '../src/LinkIconButton'; + +figma.connect( + LinkIconButton, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A35190', + { + example: () => , + }, +); diff --git a/packages/components/figma/Menu.figma.tsx b/packages/components/figma/Menu.figma.tsx new file mode 100644 index 000000000..3e716bb93 --- /dev/null +++ b/packages/components/figma/Menu.figma.tsx @@ -0,0 +1,14 @@ +import figma from '@figma/code-connect'; + +import { Menu } from '../src/Menu'; + +figma.connect( + Menu, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-30645', + { + props: { + children: figma.children(['MenuItem']), + }, + example: ({ children }) => {children}, + }, +); diff --git a/packages/components/figma/MenuItem.Figma.tsx b/packages/components/figma/MenuItem.Figma.tsx new file mode 100644 index 000000000..9d7f18a44 --- /dev/null +++ b/packages/components/figma/MenuItem.Figma.tsx @@ -0,0 +1,24 @@ +import figma from '@figma/code-connect'; + +import { MenuItem } from '../src/Menu'; + +figma.connect( + MenuItem, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-30645', + { + props: { + disabled: figma.enum('State', { Disabled: true }), + label: figma.instance('Label'), + icon: figma.boolean('Has icon', { + true: figma.instance('Icon'), + false: undefined, + }), + shortcut: figma.string('Shortcut'), + }, + example: ({ disabled, label, shortcut }) => ( + + {label} {shortcut} + + ), + }, +); diff --git a/packages/components/figma/Modal.figma.tsx b/packages/components/figma/Modal.figma.tsx new file mode 100644 index 000000000..9d73d935b --- /dev/null +++ b/packages/components/figma/Modal.figma.tsx @@ -0,0 +1,77 @@ +import figma from '@figma/code-connect'; + +import { Button } from '../src/Button'; +import { Dialog, DialogTrigger } from '../src/Dialog'; +import { Heading } from '../src/Heading'; +import { Modal, ModalOverlay } from '../src/Modal'; + +figma.connect( + Modal, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A35555', + { + variant: { 'Footer?': 'false' }, + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + variant: figma.enum('Type', { + Default: 'default', + Drawer: 'drawer', + }), + heading: figma.string('Heading'), + }, + example: ({ size, variant, heading }) => ( + + + + + + {heading} + Content goes here + + + + + ), + }, +); + +figma.connect( + Modal, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A35555', + { + variant: { 'Footer?': 'true' }, + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + heading: figma.string('Heading'), + variant: figma.enum('Type', { + Default: 'default', + Drawer: 'drawer', + }), + footer: figma.boolean('Footer?', { + true: figma.children(['ButtonGroup', 'Button']), + false: undefined, + }), + }, + example: ({ size, variant, footer, heading }) => ( + + + + + + {heading} + Content goes here + {footer} + + + + + ), + }, +); diff --git a/packages/components/figma/NumberField.figma.tsx b/packages/components/figma/NumberField.figma.tsx new file mode 100644 index 000000000..84cb616ed --- /dev/null +++ b/packages/components/figma/NumberField.figma.tsx @@ -0,0 +1,47 @@ +import figma from '@figma/code-connect'; + +import { NumberField } from '../src/NumberField'; + +figma.connect( + NumberField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10876%3A32982', + { + props: { + defaultValue: figma.boolean('Value?', { + true: 10, + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ isInvalid, isDisabled, defaultValue }) => ( + + ), + }, +); + +figma.connect( + NumberField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10876%3A32982', + { + variant: { 'Label?': 'true' }, + props: { + label: figma.boolean('Label?', { + true: figma.children(['Label']), + false: undefined, + }), + defaultValue: figma.boolean('Value?', { + true: 10, + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ isInvalid, isDisabled, defaultValue, label }) => ( + <> + {label} + + + ), + }, +); diff --git a/packages/components/figma/Popover.figma.tsx b/packages/components/figma/Popover.figma.tsx new file mode 100644 index 000000000..93fd8c1ad --- /dev/null +++ b/packages/components/figma/Popover.figma.tsx @@ -0,0 +1,12 @@ +import figma from '@figma/code-connect'; + +import { Popover } from '../src/Popover'; + +figma.connect( + Popover, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=4431%3A2829', + { + props: {}, + example: (props) => , + }, +); diff --git a/packages/components/figma/ProgressBar.figma.tsx b/packages/components/figma/ProgressBar.figma.tsx new file mode 100644 index 000000000..12a5efcc1 --- /dev/null +++ b/packages/components/figma/ProgressBar.figma.tsx @@ -0,0 +1,18 @@ +import figma from '@figma/code-connect'; + +import { ProgressBar } from '../src/ProgressBar'; + +figma.connect( + ProgressBar, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A37274', + { + props: { + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + Large: 'large', + }), + }, + example: ({ size }) => , + }, +); diff --git a/packages/components/figma/Radio.figma.tsx b/packages/components/figma/Radio.figma.tsx new file mode 100644 index 000000000..d8297f5ff --- /dev/null +++ b/packages/components/figma/Radio.figma.tsx @@ -0,0 +1,41 @@ +import figma from '@figma/code-connect'; + +import { Radio } from '../src/Radio'; + +figma.connect( + Radio, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-33572', + { + props: { + // selected state controlled by the parent RadioGroup + isSelected: figma.boolean('Selected'), + isDisabled: figma.enum('State', { Disabled: true }), + isInvalid: figma.enum('State', { Invalid: true }), + }, + example: ({ isDisabled }) => , + }, +); + +figma.connect( + Radio, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-33572', + { + variant: { 'Has label': 'true' }, + props: { + // selected state controlled by the parent RadioGroup + isSelected: figma.boolean('Selected'), + isDisabled: figma.enum('State', { Disabled: true }), + isInvalid: figma.enum('State', { Invalid: true }), + label: figma.boolean('Has label', { + true: figma.children(['Label']), + false: undefined, + }), + }, + example: ({ isDisabled, label }) => ( + <> + {label} + + + ), + }, +); diff --git a/packages/components/figma/RadioGroup.figma.tsx b/packages/components/figma/RadioGroup.figma.tsx new file mode 100644 index 000000000..06e66bdbd --- /dev/null +++ b/packages/components/figma/RadioGroup.figma.tsx @@ -0,0 +1,25 @@ +import figma from '@figma/code-connect'; + +import { ButtonGroup, RadioGroup } from '../src'; + +figma.connect( + RadioGroup, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-33595', + { + props: { + label: figma.boolean('Has label', { + true: figma.children(['Label']), + false: undefined, + }), + radios: figma.children(['.Radio']), + }, + example: ({ label, radios }) => ( + + {label} + + {radios} + + + ), + }, +); diff --git a/packages/components/figma/SearchField.figma.tsx b/packages/components/figma/SearchField.figma.tsx new file mode 100644 index 000000000..080326d3e --- /dev/null +++ b/packages/components/figma/SearchField.figma.tsx @@ -0,0 +1,33 @@ +import figma from '@figma/code-connect'; + +import { Group, IconButton, Input, SearchField } from '../src'; + +figma.connect( + SearchField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10993-56547', + { + props: { + label: figma.boolean('Label?', { + true: figma.children(['Label']), + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ label, isInvalid, isDisabled }) => ( + + {label} + + {/* */} + + + + + ), + }, +); diff --git a/packages/components/figma/Select.figma.tsx b/packages/components/figma/Select.figma.tsx new file mode 100644 index 000000000..77cb8cb70 --- /dev/null +++ b/packages/components/figma/Select.figma.tsx @@ -0,0 +1,33 @@ +import figma from '@figma/code-connect'; + +import { Button } from '../src/Button'; +import { ListBox, ListBoxItem } from '../src/ListBox'; +import { Popover } from '../src/Popover'; +import { Select, SelectValue } from '../src/Select'; + +figma.connect( + Select, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=10974-97679', + { + props: { + label: figma.boolean('Label?', { + true: 'Select an option', + false: undefined, + }), + }, + example: ({ label }) => ( + + ), + }, +); diff --git a/packages/components/figma/Switch.figma.tsx b/packages/components/figma/Switch.figma.tsx new file mode 100644 index 000000000..1c943dff7 --- /dev/null +++ b/packages/components/figma/Switch.figma.tsx @@ -0,0 +1,23 @@ +import figma from '@figma/code-connect'; + +import { Switch } from '../src/Switch'; + +figma.connect( + Switch, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A34050', + { + props: { + isDisabled: figma.enum('State', { Disabled: true }), + isSelected: figma.boolean('Toggle'), + }, + example: ({ isSelected, isDisabled }) => ( + { + console.log(selected); + }} + /> + ), + }, +); diff --git a/packages/components/figma/Tab.figma.tsx b/packages/components/figma/Tab.figma.tsx new file mode 100644 index 000000000..f2fdfdc52 --- /dev/null +++ b/packages/components/figma/Tab.figma.tsx @@ -0,0 +1,20 @@ +import figma from '@figma/code-connect'; + +import { Tab } from '../src/Tabs'; + +figma.connect( + Tab, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-45112', + { + props: { + label: figma.string('Label'), + selected: figma.boolean('Selected'), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ label, isDisabled }) => ( + + {label} + + ), + }, +); diff --git a/packages/components/figma/Table.figma.tsx b/packages/components/figma/Table.figma.tsx new file mode 100644 index 000000000..6de6e8e96 --- /dev/null +++ b/packages/components/figma/Table.figma.tsx @@ -0,0 +1,27 @@ +import figma from '@figma/code-connect'; + +import { Cell, Column, Row, Table, TableBody, TableHeader } from '../src/Table'; + +figma.connect( + Table, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=4670%3A84987', + { + props: {}, + example: () => ( + + + Column 1 + Column 2 + Column 3 + + + + Cell 1 + Cell 2 + Cell 3 + + +
+ ), + }, +); diff --git a/packages/components/figma/Tabs.figma.tsx b/packages/components/figma/Tabs.figma.tsx new file mode 100644 index 000000000..b61e98bdf --- /dev/null +++ b/packages/components/figma/Tabs.figma.tsx @@ -0,0 +1,27 @@ +import figma from '@figma/code-connect'; + +import { Tab, TabList, TabPanel, Tabs } from '../src/Tabs'; + +figma.connect( + Tab, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1%3A45112', + { + props: { + // In Figma Tab is marked as hidden (.), needs to be fixed there before can be used here. + // For now sample is used instead of actual tabs. + // tabs: figma.children('.Tab'), + }, + example: () => ( + console.log(key)}> + + Tab 1 + Tab 2 + Tab 3 + + Tab content 1 + Tab content 2 + Tab content 3 + + ), + }, +); diff --git a/packages/components/figma/Tag.figma.tsx b/packages/components/figma/Tag.figma.tsx new file mode 100644 index 000000000..e0dfa5b91 --- /dev/null +++ b/packages/components/figma/Tag.figma.tsx @@ -0,0 +1,40 @@ +import figma from '@figma/code-connect'; + +import { Tag } from '../src'; + +figma.connect( + Tag, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-31713', + { + props: { + icon: figma.boolean('Icon?', { + true: figma.instance('Icon'), + false: undefined, + }), + label: figma.textContent('Label'), + removable: figma.boolean('Removable?'), + size: figma.enum('Size', { + Small: 'small', + Medium: 'medium', + }), + isDisabled: figma.enum('State', { Disabled: true }), + variant: figma.enum('Variant', { + Default: 'default', + Destructive: 'error', + Success: 'success', + Error: 'error', + Beta: 'beta', + New: 'new', + Info: 'info', + Warning: 'warning', + Federal: 'federal', + }), + }, + example: ({ icon, label, size, isDisabled, variant }) => ( + + {icon} + {label} + + ), + }, +); diff --git a/packages/components/figma/TagGroup.figma.tsx b/packages/components/figma/TagGroup.figma.tsx new file mode 100644 index 000000000..693fd392d --- /dev/null +++ b/packages/components/figma/TagGroup.figma.tsx @@ -0,0 +1,23 @@ +import figma from '@figma/code-connect'; + +import { TagGroup, TagList } from '../src'; + +figma.connect( + TagGroup, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-32126', + { + props: { + label: figma.boolean('Has label', { + true: figma.children(['Label']), + false: undefined, + }), + tags: figma.children('Tag'), + }, + example: ({ tags, label }) => ( + + {label} + {tags} + + ), + }, +); diff --git a/packages/components/figma/TextField.figma.tsx b/packages/components/figma/TextField.figma.tsx new file mode 100644 index 000000000..86f990f11 --- /dev/null +++ b/packages/components/figma/TextField.figma.tsx @@ -0,0 +1,152 @@ +import figma from '@figma/code-connect'; + +import { FieldError } from '../src/FieldError'; +import { Input } from '../src/Input'; +import { TextArea } from '../src/TextArea'; +import { TextField } from '../src/TextField'; + +figma.connect( + TextField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-34232', + { + variant: { 'Multi Line': 'false' }, + props: { + children: figma.boolean('Label?', { + true: figma.children(['Label']), + false: undefined, + }), + value: figma.boolean('Value?', { + true: figma.textContent('Value'), + false: undefined, + }), + multiline: figma.boolean('Multi Line'), + variant: figma.boolean('Minimal', { + true: 'minimal', + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + placeholder: figma.boolean('Placeholder?', { + true: figma.textContent('Placeholder'), + false: undefined, + }), + isDisabled: figma.enum('State', { Disabled: true }), + validationMessage: figma.textContent('Validation message'), + }, + example: ({ children, variant, placeholder, value, isDisabled }) => { + return ( + + {children} + + + ); + }, + }, +); + +figma.connect( + TextField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-34232', + { + variant: { 'Multi Line': 'false', State: 'Invalid' }, + props: { + children: figma.boolean('Label?', { + true: figma.children(['Label', 'Input']), + false: undefined, + }), + variant: figma.boolean('Minimal', { + true: 'minimal', + false: undefined, + }), + isInvalid: figma.enum('State', { Invalid: true }), + placeholder: figma.boolean('Placeholder?', { + true: figma.textContent('Placeholder'), + false: undefined, + }), + value: figma.boolean('Value?', { + true: figma.textContent('Value'), + false: undefined, + }), + isDisabled: figma.enum('State', { Disabled: true }), + validationMessage: figma.textContent('Validation message'), + }, + example: ({ + children, + variant, + placeholder, + value, + isDisabled, + isInvalid, + validationMessage, + }) => { + return ( + + {children} + + {validationMessage} + + ); + }, + }, +); + +figma.connect( + TextField, + 'https://www.figma.com/design/98HKKXL2dTle29ikJ3tzk7/%F0%9F%9A%80-LaunchPad?node-id=1-34232', + { + variant: { 'Multi Line': 'true' }, + props: { + children: figma.boolean('Label?', { + true: figma.children(['Label', 'Input']), + false: undefined, + }), + placeholder: figma.boolean('Placeholder?', { + true: figma.textContent('Placeholder'), + false: undefined, + }), + value: figma.boolean('Value?', { + true: figma.textContent('Value'), + false: undefined, + }), + isDisabled: figma.enum('State', { Disabled: true }), + }, + example: ({ children, placeholder, value, isDisabled }) => { + return ( + + {children} +