Create Storybook guideline and add addon-designs to embed Figma (#27310)

* Add foundations and migrations docs

* Add storybook/addon-designs version 8.2.1

* Test Figma link

* Refactor Modal stories

* Create figmaDesignUrl

* Create foundations

* Create feature flags docs

* Create Storybook builds docs

* Add storybook/addon-designs version 8.2.1

* Test Figma link

* Add an example of Story with split-tests within the Storybook guidelines (#27260)

* Add FormatCurrency demo in feature-flags.mdx

* Add syntax highlight to code samples

* Update stories with figmaDesignUrl

* Figma access token

* Use OLButton

* Hide control for children and footer

* Add primitive colors

* Use useSplitTest instead

* Update cloud builds docs with `storybook-push-trigger`

* Make Foundations the default story

---------

Co-authored-by: Antoine Clausse <antoine.clausse@overleaf.com>
GitOrigin-RevId: 0729759803f190d89cf543d087eea86265b725f1
This commit is contained in:
Rebeka Dekany
2025-08-12 12:31:11 +02:00
committed by Copybot
parent 9f80a31d85
commit 8acb7af2b9
17 changed files with 1285 additions and 151 deletions

120
package-lock.json generated
View File

@@ -4912,6 +4912,30 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@figspec/components": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@figspec/components/-/components-1.0.3.tgz",
"integrity": "sha512-fBwHzJ4ouuOUJEi+yBZIrOy+0/fAjB3AeTcIHTT1PRxLz8P63xwC7R0EsIJXhScIcc+PljGmqbbVJCjLsnaGYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"lit": "^2.1.3"
}
},
"node_modules/@figspec/react": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@figspec/react/-/react-1.0.4.tgz",
"integrity": "sha512-jaPvkIef4d6NjsRiw91OZabrfdPH9FtoPGYcY5mpXjYEcdUqIq1aHtLq3SkMVyVysEapTEJ6yS8amy93MyXBEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@figspec/components": "^1.0.1",
"@lit-labs/react": "^1.0.2"
},
"peerDependencies": {
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@google-cloud/bigquery": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-5.10.0.tgz",
@@ -6543,6 +6567,30 @@
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@lit-labs/react": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@lit-labs/react/-/react-1.2.1.tgz",
"integrity": "sha512-DiZdJYFU0tBbdQkfwwRSwYyI/mcWkg3sWesKRsHUd4G+NekTmmeq9fzsurvcKTNVa0comNljwtg4Hvi1ds3V+A==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz",
"integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/@lukeed/csprng": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
@@ -10288,6 +10336,40 @@
"storybook": "^8.6.12"
}
},
"node_modules/@storybook/addon-designs": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/@storybook/addon-designs/-/addon-designs-8.2.1.tgz",
"integrity": "sha512-orwihs1D5alhh4Qu3BSJKbSgQOdSagvRX/25m5fYZQAaqVErBY0lRR4vCAU/G/STkcdv+MHwIQ5U+0kX5Tm2+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@figspec/react": "^1.0.0"
},
"peerDependencies": {
"@storybook/blocks": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"@storybook/components": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"@storybook/theming": "^8.0.0 || ^8.1.0-0 || ^8.2.0-0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta"
},
"peerDependenciesMeta": {
"@storybook/blocks": {
"optional": true
},
"@storybook/components": {
"optional": true
},
"@storybook/theming": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/@storybook/addon-docs": {
"version": "8.6.12",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.12.tgz",
@@ -12605,8 +12687,7 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"dev": true,
"optional": true
"dev": true
},
"node_modules/@types/uuid": {
"version": "9.0.8",
@@ -27787,6 +27868,40 @@
}
}
},
"node_modules/lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/lit-element": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/lit-html": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/load-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
@@ -44516,6 +44631,7 @@
"@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
"@sentry/browser": "7.46.0",
"@storybook/addon-a11y": "^8.6.12",
"@storybook/addon-designs": "^8.2.1",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-links": "^8.6.12",

View File

@@ -19,12 +19,15 @@ const config: StorybookConfig = {
stories: [
path.join(rootDir, 'frontend/stories/**/*.stories.{js,jsx,ts,tsx}'),
path.join(rootDir, 'modules/**/stories/**/*.stories.{js,jsx,ts,tsx}'),
path.join(rootDir, 'frontend/stories/**/*.mdx'),
path.join(rootDir, 'modules/**/stories/**/*.mdx'),
],
addons: [
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-essentials'),
getAbsolutePath('@storybook/addon-interactions'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-designs'),
getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
{
name: getAbsolutePath('@storybook/addon-styling-webpack'),

View File

@@ -131,7 +131,11 @@ const preview: Preview = {
options: {
storySort: {
method: 'alphabetical',
order: ['Shared'],
order: [
'Storybook Guideline',
['Foundations', 'Storybook builds', 'Feature Flags'],
'Shared',
],
},
},
},

View File

@@ -0,0 +1,20 @@
/** Creates design parameters for a Storybook story conditionally.
* Helper function to generate design parameters based on the presence of a Figma access token.
* The benefit of the token is that it allows component inspection directly in Storybook.
* If token is not available, it defaults to a basic Figma URL.
* Token can be generated in your Figma account settings.
* To copy URL: In your Figma file, click to select the specific component frame you want to display in Storybook.
* It's important to select the outer frame of the component, not just a single layer inside it.
*/
export const figmaDesignUrl = (url: string) => {
const accessToken = process.env.STORYBOOK_FIGMA_ACCESS_TOKEN
const designConfig = accessToken
? { type: 'figspec', url, accessToken }
: { type: 'figma', url }
return {
design: designConfig,
}
}

View File

@@ -1,8 +1,8 @@
import type { Meta } from '@storybook/react'
import _ from 'lodash'
import { SplitTestContext } from '../../frontend/js/shared/context/split-test-context'
import { SplitTestContext } from '@/shared/context/split-test-context'
export const splitTestsArgTypes = {
export const defaultSplitTestsArgTypes = {
// to be able to use this utility, you need to add the argTypes for each split test in this object
// Check the original implementation for an example: https://github.com/overleaf/internal/pull/17809
'editor-redesign': {
@@ -14,10 +14,13 @@ export const splitTestsArgTypes = {
},
}
export const withSplitTests = (
export const withSplitTests = <ArgTypes = typeof defaultSplitTestsArgTypes,>(
story: Meta,
splitTests: (keyof typeof splitTestsArgTypes)[] = []
splitTests: (keyof ArgTypes)[] = [],
/** @deprecated For demo purposes only. Add actual split tests in defaultSplitTestsArgTypes */
_splitTestsArgTypes?: ArgTypes
): Meta => {
const splitTestsArgTypes = _splitTestsArgTypes ?? defaultSplitTestsArgTypes
return {
...story,
argTypes: { ...story.argTypes, ..._.pick(splitTestsArgTypes, splitTests) },

View File

@@ -0,0 +1,24 @@
import { Meta } from '@storybook/blocks';
<Meta title="Storybook Guideline / Storybook builds" />
# Cloud Builds
Storybook builds can be used to share your development work. They can be found at [https://storybook.dev-overleaf.com/](https://storybook.dev-overleaf.com/).
The naming scheme is predictable, i.e.: `https://storybook.dev-overleaf.com/<BRANCH_NAME>/`.
# Triggers
Storybook builds are automatically triggered for the main branch. For ad hoc builds of feature branches, the commits must be pushed to branches prefixed with `storybook-`.
For example, you may temporarily push your branch to a `storybook-` branch: `git push origin my-example-feature:storybook-my-example-feature`
You can delete it shortly after: `git push origin :storybook-my-example-feature`
Alternatively, **if you already have a branch**, but you would like to create a Storybook, you can manually trigger the `storybook-push-trigger` on your branch via [Google Cloud Build triggers](https://console.cloud.google.com/cloud-build/triggers?project=overleaf-dev).
Storybook builds take about 10 minutes to finish.
**Source:** [Storybook in the developer manual](https://manual.dev-overleaf.com/development/code/storybook/)

View File

@@ -0,0 +1,40 @@
import { Canvas, Controls, Meta } from '@storybook/blocks';
import * as FormatCurrency from './format-currency.stories'
<Meta title="Storybook Guideline / Feature flags" />
# Feature flags
You can wrap your story with the `withSplitTests` utility to add split test variants to your Storybook stories, so you can toggle feature flags directly in the Storybook UI. See [withSplitTests on GitHub](https://github.com/overleaf/internal/blob/main/services/web/.storybook/utils/with-split-tests.tsx).
1. Define your split test argTypes:
Add your split test configurations to the `splitTestsArgTypes` object.
```js
export const splitTestsArgTypes = {
'local-ccy-format': {
description: 'Use local currency formatting',
control: { type: 'radio' },
options: ['default', 'enabled'],
},
}
```
2. Wrap your story with `withSplitTests`.
Import `withSplitTests` and `Meta` from '@storybook/react' in your stories.
```js
export default {
...config,
...withSplitTests(config, ['local-ccy-format']),
}
```
## Example
Resulting stories will have added controls to define the variants of the split tests.
<div>
<Canvas of={FormatCurrency.Story} />
<Controls of={FormatCurrency.Story} />
</div>

View File

@@ -0,0 +1,44 @@
import React from 'react'
import { useSplitTest } from '@/shared/context/split-test-context'
import { withSplitTests } from '../../../.storybook/utils/with-split-tests'
const FormatCurrency = () => {
const { variant } = useSplitTest('local-ccy-format')
const formatCurrency = (amount: number, narrowSymbol: boolean) => {
return variant === 'enabled'
? new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
currencyDisplay: narrowSymbol ? 'narrowSymbol' : 'name',
}).format(amount)
: `USD ${amount}`
}
return (
<div>
<strong>Variant:</strong> {JSON.stringify(variant)}
<br />
<strong>Long:</strong> {formatCurrency(1234.56, false)}
<br />
<strong>Short:</strong> {formatCurrency(1234.56, true)}
</div>
)
}
const config = {
title: 'Storybook Guideline / Feature Flags', // Must match MDX title exactly
component: FormatCurrency,
}
export default {
...config,
...withSplitTests(config, ['local-ccy-format'], {
'local-ccy-format': {
description: 'Use local currency formatting',
control: { type: 'radio' as const },
options: ['default', 'enabled'],
},
}),
tags: ['!dev'], // hides in the sidebar
}
export const Story = {}

View File

@@ -0,0 +1,234 @@
import { Meta, ColorItem, ColorPalette, Title, Typeset } from '@storybook/blocks';
import colors from '../../stylesheets/bootstrap-5/foundations/tokens/colors.json';
import typography from '../../stylesheets/bootstrap-5/foundations/tokens/typography.json';
import borderRadius from '../../stylesheets/bootstrap-5/foundations/tokens/borderRadius.json';
import spacing from '../../stylesheets/bootstrap-5/foundations/tokens/spacing.json';
<Meta title="Storybook Guideline / Foundations" />
# Foundations
Foundations in UX design are the basic rules and core elements, like colors, fonts, and spacing, that ensure consistency across the design.
<p>
These palettes are generated from our token files. The tokens were exported from Figma and split into separate files to categorise: <a href="https://github.com/overleaf/internal/tree/main/services/web/frontend/stylesheets/bootstrap-5/foundations/tokens" target="_blank" rel="noopener noreferrer">Token files on GitHub</a>
</p>
**Table of Contents**
- [Primitive colors](#primitive-colors)
- [Background colors](#background-colors)
- [Content colors](#content-colors)
- [Border colors](#border-colors)
- [Link colors](#link-colors)
- [Special colors](#special-colors)
- [Font weight](#font-weight)
- [Font size](#font-size)
- [Border radius](#border-radius)
- [Spacing](#spacing)
# Colors
<br/>
## Primitive colors
<ColorPalette>
{Object.entries(colors.PrimitiveColor).map(([name, color]) => (
<ColorItem
key={name}
title={name}
colors={[color.$value]}
/>
))}
</ColorPalette>
## Background colors
Colors used for page and component backgrounds.
<ColorPalette>
{Object.entries(colors)
.filter(([name]) => name.startsWith('bg-'))
.map(([name, colors]) => (
<ColorItem
key={name}
title={name}
subtitle={colors.$primitive}
colors={[colors.$value]}
/>
))}
</ColorPalette>
## Content colors
Colors used for text and icons.
<ColorPalette>
{Object.entries(colors)
.filter(([name]) => name.startsWith('content-'))
.map(([name, colors]) => (
<ColorItem
key={name}
title={name}
subtitle={colors.$primitive}
colors={[colors.$value]}
/>
))}
</ColorPalette>
## Border colors
Colors used for borders and dividers.
<ColorPalette>
{Object.entries(colors)
.filter(([name]) => name.startsWith('border-'))
.map(([name, colors]) => (
<ColorItem
key={name}
title={name}
subtitle={colors.$primitive}
colors={[colors.$value]}
/>
))}
</ColorPalette>
## Link colors
Colors used for hyperlink states.
<ColorPalette>
{Object.entries(colors)
.filter(([name]) => name.startsWith('link-'))
.map(([name, colors]) => (
<ColorItem
key={name}
title={name}
subtitle={colors.$primitive}
colors={[colors.$value]}
/>
))}
</ColorPalette>
## Special colors
Colors used for specific UI elements like color pickers.
<ColorPalette>
{Object.entries(colors)
.filter(([name]) => name.startsWith('special-'))
.map(([name, colors]) => (
<ColorItem
key={name}
title={name}
subtitle={colors.$primitive}
colors={[colors.$value]}
/>
))}
</ColorPalette>
# Typography
<br/>
## Font weight
<br/>
{Object.entries(typography.Fontweight).map(([name, typography]) => (
<div key={name}>
<strong>{name}</strong>
<Typeset
fontSizes={[14]}
fontWeight={typography.$value}
sampleText="The quick brown fox jumps over the lazy dog"
/>
</div>
))}
## Font size
<br/>
{Object.entries(typography['Fontsize']).map(([name, typography]) => {
const mixins = typography.$mixin ? [].concat(typography.$mixin) : [];
return (
<div key={name} style={{ marginBottom: '2rem' }}>
<strong>
{name}
{mixins.map(mixin => (
<span key={mixin}> / @mixin {mixin}</span>
))}
</strong>
<Typeset
fontSizes={[`${typography.$value}px`]}
fontWeight={500}
sampleText="The quick brown fox jumps over the lazy dog"
/>
</div>
);
})}
## Border radius
<br/>
<table style={{ width: '100%' }}>
<thead>
<tr>
<th>Variable name</th>
<th>Value</th>
<th style={{ textAlign: 'center' }}>Example</th>
</tr>
</thead>
<tbody>
{Object.entries(borderRadius['BorderRadius']).map(([name, borderRadius]) => (
<tr key={name}>
<td>
<strong>{name}</strong>
</td>
<td style={{ textAlign: 'center' }}>{borderRadius.$value}px</td>
<td style={{ textAlign: 'center', padding: '0.5rem' }}>
<div
style={{
width: '80px',
height: '80px',
borderRadius: `${borderRadius.$value}px`,
backgroundColor: '#098842',
display: 'inline-block',
}}
/>
</td>
</tr>
))}
</tbody>
</table>
## Spacing
<br/>
<table style={{ width: '100%' }}>
<thead>
<tr>
<th>Variable Name</th>
<th>Value</th>
<th style={{ textAlign: 'left', paddingLeft: '1rem' }}>Example</th>
</tr>
</thead>
<tbody>
{Object.entries(spacing.Spacing)
.filter(([, spacing]) => spacing.$value > 0)
.map(([name, spacing]) => (
<tr key={name}>
<td>
<strong>{name}</strong>
</td>
<td style={{ textAlign: 'center' }}>{spacing.$value}px</td>
<td style={{ padding: '0.5rem 1rem', verticalAlign: 'middle' }}>
<div
style={{
height: `${spacing.$value}px`,
width: '100%',
backgroundColor: '#098842',
}}
/>
</td>
</tr>
))}
</tbody>
</table>

View File

@@ -1,140 +1,80 @@
import Button from '@/shared/components/button/button'
import type { Meta, StoryObj } from '@storybook/react'
import { figmaDesignUrl } from './../../../.storybook/utils/figma-design-url'
import OLModal, {
OLModalHeader,
OLModalBody,
OLModalFooter,
OLModalTitle,
} from '@/shared/components/ol/ol-modal'
import OLButton from '@/shared/components/ol/ol-button'
type Story = StoryObj<typeof OLModal>
export const Default: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Heading</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
Lorem ipsum dolor sit lorem a amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam.
</p>
</OLModalBody>
<OLModalFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Primary</Button>
</OLModalFooter>
</OLModal>
)
args: {
title: 'Heading',
children: (
<p>
Always use the modal actions in the footer and use descriptive words for
them.
</p>
),
footer: (
<>
<OLButton variant="secondary">Cancel</OLButton>
<OLButton variant="primary">Primary</OLButton>
</>
),
},
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-82657&m=dev'
),
}
export const Informative: Story = {
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86576&m=dev'
),
args: {
title: 'Informative',
children: (
<p>
Presents information for the user to be aware of and doesnt require any
action.
</p>
),
},
}
export const ModalWithAcknowledgment: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Acknowledgment</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
System requires an acknowledgment from the user. Usually contains
only one primary button.
</p>
</OLModalBody>
<OLModalFooter>
<Button variant="primary">Accept</Button>
</OLModalFooter>
</OLModal>
)
export const Acknowledgment: Story = {
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86581&m=dev'
),
args: {
title: 'Acknowledgment',
children: (
<p>
System requires an acknowledgment from the user. Usually contains only
one primary button.
</p>
),
footer: <OLButton variant="primary">Accept</OLButton>,
},
}
export const ModalWithSecondary: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Heading</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
Lorem ipsum dolor sit lorem a amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam.
</p>
</OLModalBody>
<OLModalFooter>
<Button variant="secondary">Cancel</Button>
</OLModalFooter>
</OLModal>
)
},
}
export const ModalWithTertiary: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Heading</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>Used for destructive or irreversible actions.</p>
</OLModalBody>
<OLModalFooter>
<Button variant="secondary">Third</Button>
<Button variant="secondary" className="ms-auto">
Cancel
</Button>
<Button variant="primary">Primary</Button>
</OLModalFooter>
</OLModal>
)
},
}
export const ModalInformative: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Informative</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
Presents information for the user to be aware of and doesnt require
any action.
</p>
</OLModalBody>
</OLModal>
)
},
}
export const ModalDanger: Story = {
render: args => {
return (
<OLModal show {...args}>
<OLModalHeader closeButton>
<OLModalTitle>Danger</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>
Lorem ipsum dolor sit lorem a amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam.
</p>
</OLModalBody>
<OLModalFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="danger">Delete</Button>
</OLModalFooter>
</OLModal>
)
export const Danger: Story = {
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?m=auto&node-id=3488-86586&m=dev'
),
args: {
title: 'Danger',
children: <p>Used for destructive or irreversible actions.</p>,
footer: (
<>
<OLButton variant="secondary">Cancel</OLButton>
<OLButton variant="danger">Delete</OLButton>
</>
),
},
}
@@ -146,7 +86,24 @@ const meta: Meta<typeof OLModal> = {
control: 'radio',
options: ['lg', 'md', 'sm'],
},
title: { control: 'text' },
children: { control: false },
footer: { control: false },
},
args: {
show: true,
size: 'sm',
onHide: () => {},
},
render: ({ title, children, footer, ...args }) => (
<OLModal {...args}>
<OLModalHeader closeButton>
<OLModalTitle>{title}</OLModalTitle>
</OLModalHeader>
<OLModalBody>{children}</OLModalBody>
{footer && <OLModalFooter>{footer}</OLModalFooter>}
</OLModal>
),
}
export default meta

View File

@@ -1,7 +1,8 @@
import Badge from '@/shared/components/badge/badge'
import MaterialIcon from '@/shared/components/material-icon'
import type { Meta, StoryObj } from '@storybook/react'
import classnames from 'classnames'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import Badge from '@/shared/components/badge/badge'
import MaterialIcon from '@/shared/components/material-icon'
const meta: Meta<typeof Badge> = {
title: 'Shared / Components / Badge',
@@ -31,20 +32,27 @@ export default meta
type Story = StoryObj<typeof Badge>
export const BadgeDefault: Story = {
render: args => {
return (
<Badge
className={classnames({ 'text-dark': args.bg === 'light' })}
{...args}
/>
)
args: {
bg: meta.argTypes!.bg!.options![0],
},
}
BadgeDefault.args = {
bg: meta.argTypes!.bg!.options![0],
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-9502&m=dev'
),
render: args => (
<Badge
className={classnames({ 'text-dark': args.bg === 'light' })}
{...args}
/>
),
}
export const BadgePrepend: Story = {
args: {
bg: meta.argTypes!.bg!.options![0],
},
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-11319&m=dev'
),
render: args => {
return (
<Badge
@@ -55,6 +63,3 @@ export const BadgePrepend: Story = {
)
},
}
BadgePrepend.args = {
bg: meta.argTypes!.bg!.options![0],
}

View File

@@ -1,27 +1,28 @@
import Button from '@/shared/components/button/button'
import { Meta } from '@storybook/react'
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
import OLButton from '@/shared/components/ol/ol-button'
type Args = React.ComponentProps<typeof Button>
type Args = React.ComponentProps<typeof OLButton>
export const NewButton = (args: Args) => {
return <Button {...args} />
return <OLButton {...args} />
}
export const ButtonWithLeadingIcon = (args: Args) => {
return <Button leadingIcon="add" {...args} />
return <OLButton leadingIcon="add" {...args} />
}
export const ButtonWithTrailingIcon = (args: Args) => {
return <Button trailingIcon="add" {...args} />
return <OLButton trailingIcon="add" {...args} />
}
export const ButtonWithIcons = (args: Args) => {
return <Button trailingIcon="add" leadingIcon="add" {...args} />
return <OLButton trailingIcon="add" leadingIcon="add" {...args} />
}
const meta: Meta<typeof Button> = {
const meta: Meta<typeof OLButton> = {
title: 'Shared / Components / Button',
component: Button,
component: OLButton,
args: {
children: 'A Button',
disabled: false,
@@ -30,7 +31,7 @@ const meta: Meta<typeof Button> = {
argTypes: {
size: {
control: 'radio',
options: ['small', 'default', 'large'],
options: ['lg', 'md', 'sm'],
},
variant: {
control: 'radio',
@@ -42,9 +43,13 @@ const meta: Meta<typeof Button> = {
'danger-ghost',
'premium',
'premium-secondary',
'link',
],
},
},
parameters: figmaDesignUrl(
'https://www.figma.com/design/V7Ogph1Ocs4ux2A4WMNAh7/Overleaf---Components?node-id=3458-22412&m=dev'
),
}
export default meta

View File

@@ -0,0 +1,24 @@
{
"BorderRadius": {
"border-radius-none": {
"$type": "number",
"$value": 0
},
"border-radius-base": {
"$type": "number",
"$value": 4
},
"border-radius-medium": {
"$type": "number",
"$value": 8
},
"border-radius-large": {
"$type": "number",
"$value": 16
},
"border-radius-full": {
"$type": "number",
"$value": 9999
}
}
}

View File

@@ -0,0 +1,449 @@
{
"PrimitiveColor": {
"white": {
"$type": "color",
"$value": "#fff",
"$primitive": null
},
"neutral-10": {
"$type": "color",
"$value": "#f4f5f6",
"$primitive": null
},
"neutral-20": {
"$type": "color",
"$value": "#e7e9ee",
"$primitive": null
},
"neutral-30": {
"$type": "color",
"$value": "#d0d5dd",
"$primitive": null
},
"neutral-40": {
"$type": "color",
"$value": "#afb5c0",
"$primitive": null
},
"neutral-50": {
"$type": "color",
"$value": "#8d96a5",
"$primitive": null
},
"neutral-60": {
"$type": "color",
"$value": "#677283",
"$primitive": null
},
"neutral-70": {
"$type": "color",
"$value": "#495365",
"$primitive": null
},
"neutral-80": {
"$type": "color",
"$value": "#2f3a4c",
"$primitive": null
},
"neutral-90": {
"$type": "color",
"$value": "#1b222c",
"$primitive": null
},
"green-10": {
"$type": "color",
"$value": "#eaf6ef",
"$primitive": null
},
"green-20": {
"$type": "color",
"$value": "#b8dbc8",
"$primitive": null
},
"green-30": {
"$type": "color",
"$value": "#86caa5",
"$primitive": null
},
"green-40": {
"$type": "color",
"$value": "#53b57f",
"$primitive": null
},
"green-50": {
"$type": "color",
"$value": "#098842",
"$primitive": null
},
"green-60": {
"$type": "color",
"$value": "#1e6b41",
"$primitive": null
},
"green-70": {
"$type": "color",
"$value": "#195936",
"$primitive": null
},
"blue-10": {
"$type": "color",
"$value": "#f1f4f9",
"$primitive": null
},
"blue-20": {
"$type": "color",
"$value": "#c3d0e3",
"$primitive": null
},
"blue-30": {
"$type": "color",
"$value": "#97b6e5",
"$primitive": null
},
"blue-40": {
"$type": "color",
"$value": "#6597e0",
"$primitive": null
},
"blue-50": {
"$type": "color",
"$value": "#366cbf",
"$primitive": null
},
"blue-60": {
"$type": "color",
"$value": "#28518f",
"$primitive": null
},
"blue-70": {
"$type": "color",
"$value": "#214475",
"$primitive": null
},
"red-10": {
"$type": "color",
"$value": "#f9f1f1",
"$primitive": null
},
"red-20": {
"$type": "color",
"$value": "#f5beba",
"$primitive": null
},
"red-30": {
"$type": "color",
"$value": "#e59d9a",
"$primitive": null
},
"red-40": {
"$type": "color",
"$value": "#e36d66",
"$primitive": null
},
"red-50": {
"$type": "color",
"$value": "#b83a33",
"$primitive": null
},
"red-60": {
"$type": "color",
"$value": "#942f2a",
"$primitive": null
},
"red-70": {
"$type": "color",
"$value": "#782722",
"$primitive": null
},
"yellow-10": {
"$type": "color",
"$value": "#fcf1e3",
"$primitive": null
},
"yellow-20": {
"$type": "color",
"$value": "#fcc483",
"$primitive": null
},
"yellow-30": {
"$type": "color",
"$value": "#f7a445",
"$primitive": null
},
"yellow-40": {
"$type": "color",
"$value": "#de8014",
"$primitive": null
},
"yellow-50": {
"$type": "color",
"$value": "#8f5514",
"$primitive": null
},
"yellow-60": {
"$type": "color",
"$value": "#7a4304",
"$primitive": null
},
"yellow-70": {
"$type": "color",
"$value": "#633a0b",
"$primitive": null
}
},
"special-neutral": {
"$type": "color",
"$value": "#a7b1c2",
"$primitive": "Neutral"
},
"special-red": {
"$type": "color",
"$value": "#f04343",
"$primitive": "Red"
},
"special-orange": {
"$type": "color",
"$value": "#dd8a3e",
"$primitive": "Orange"
},
"special-yellow": {
"$type": "color",
"$value": "#e4ca3e",
"$primitive": "Yellow"
},
"special-green": {
"$type": "color",
"$value": "#33cf67",
"$primitive": "Green"
},
"special-lightblue": {
"$type": "color",
"$value": "#43a7f0",
"$primitive": "LightBlue"
},
"special-blue": {
"$type": "color",
"$value": "#434af0",
"$primitive": "Blue"
},
"special-violet": {
"$type": "color",
"$value": "#b943f0",
"$primitive": "Violet"
},
"special-pink": {
"$type": "color",
"$value": "#ff4bcd",
"$primitive": "Pink"
},
"bg-light-primary": {
"$type": "color",
"$value": "#ffffff",
"$primitive": "white"
},
"bg-light-secondary": {
"$type": "color",
"$value": "#f4f5f6",
"$primitive": "neutral-10"
},
"bg-light-tertiary": {
"$type": "color",
"$value": "#e7e9ee",
"$primitive": "neutral-20"
},
"bg-light-disabled": {
"$type": "color",
"$value": "#e7e9ee",
"$primitive": "neutral-20"
},
"bg-dark-primary": {
"$type": "color",
"$value": "#1b222c",
"$primitive": "neutral-90"
},
"bg-dark-secondary": {
"$type": "color",
"$value": "#2f3a4c",
"$primitive": "neutral-80"
},
"bg-dark-tertiary": {
"$type": "color",
"$value": "#495365",
"$primitive": "neutral-70"
},
"bg-dark-disabled": {
"$type": "color",
"$value": "#495365",
"$primitive": "neutral-70"
},
"bg-accent-01": {
"$type": "color",
"$value": "#098842",
"$primitive": "green-50"
},
"bg-accent-02": {
"$type": "color",
"$value": "#1e6b41",
"$primitive": "green-60"
},
"bg-accent-03": {
"$type": "color",
"$value": "#eaf6ef",
"$primitive": "green-10"
},
"bg-danger-01": {
"$type": "color",
"$value": "#b83a33",
"$primitive": "red-50"
},
"bg-danger-02": {
"$type": "color",
"$value": "#942f2a",
"$primitive": "red-60"
},
"bg-danger-03": {
"$type": "color",
"$value": "#f9f1f1",
"$primitive": "red-10"
},
"bg-warning-01": {
"$type": "color",
"$value": "#8f5514",
"$primitive": "yellow-50"
},
"bg-warning-02": {
"$type": "color",
"$value": "#7a4304",
"$primitive": "yellow-60"
},
"bg-warning-03": {
"$type": "color",
"$value": "#fcf1e3",
"$primitive": "yellow-10"
},
"bg-info-01": {
"$type": "color",
"$value": "#366cbf",
"$primitive": "blue-50"
},
"bg-info-02": {
"$type": "color",
"$value": "#28518f",
"$primitive": "blue-60"
},
"bg-info-03": {
"$type": "color",
"$value": "#f1f4f9",
"$primitive": "blue-10"
},
"content-primary": {
"$type": "color",
"$value": "#1b222c",
"$primitive": "neutral-90"
},
"content-secondary": {
"$type": "color",
"$value": "#495365",
"$primitive": "neutral-70"
},
"content-disabled": {
"$type": "color",
"$value": "#afb5c0",
"$primitive": "neutral-40"
},
"content-placeholder": {
"$type": "color",
"$value": "#677283",
"$primitive": "neutral-60"
},
"content-danger": {
"$type": "color",
"$value": "#b83a33",
"$primitive": "red-50"
},
"content-warning": {
"$type": "color",
"$value": "#8f5514",
"$primitive": "yellow-50"
},
"content-positive": {
"$type": "color",
"$value": "#098842",
"$primitive": "green-50"
},
"content-info": {
"$type": "color",
"$value": "#366cbf",
"$primitive": "blue-50"
},
"border-primary": {
"$type": "color",
"$value": "#677283",
"$primitive": "neutral-60"
},
"border-hover": {
"$type": "color",
"$value": "#495365",
"$primitive": "neutral-70"
},
"border-disabled": {
"$type": "color",
"$value": "#e7e9ee",
"$primitive": "neutral-20"
},
"border-danger": {
"$type": "color",
"$value": "#b83a33",
"$primitive": "red-50"
},
"border-divider": {
"$type": "color",
"$value": "#e7e9ee",
"$primitive": "neutral-20"
},
"border-active": {
"$type": "color",
"$value": "#366cbf",
"$primitive": "blue-50"
},
"link-web": {
"$type": "color",
"$value": "#1e6b41",
"$primitive": "green-60"
},
"link-web-hover": {
"$type": "color",
"$value": "#195936",
"$primitive": "green-70"
},
"link-web-visited": {
"$type": "color",
"$value": "#1e6b41",
"$primitive": "green-60"
},
"link-ui": {
"$type": "color",
"$value": "#366cbf",
"$primitive": "blue-50"
},
"link-ui-hover": {
"$type": "color",
"$value": "#28518f",
"$primitive": "blue-60"
},
"link-ui-visited": {
"$type": "color",
"$value": "#28518f",
"$primitive": "blue-60"
},
"hover-interaction": {
"$type": "color",
"$value": "rgba(27, 34, 44, 0.0800)",
"$primitive": null
},
"overlay": {
"$type": "color",
"$value": "rgba(27, 34, 44, 0.8000)",
"$primitive": null
}
}

View File

@@ -0,0 +1,72 @@
{
"Spacing": {
"spacing-00": {
"$type": "number",
"$value": 0
},
"spacing-01": {
"$type": "number",
"$value": 2
},
"spacing-02": {
"$type": "number",
"$value": 4
},
"spacing-03": {
"$type": "number",
"$value": 6
},
"spacing-04": {
"$type": "number",
"$value": 8
},
"spacing-05": {
"$type": "number",
"$value": 12
},
"spacing-06": {
"$type": "number",
"$value": 16
},
"spacing-07": {
"$type": "number",
"$value": 20
},
"spacing-08": {
"$type": "number",
"$value": 24
},
"spacing-09": {
"$type": "number",
"$value": 32
},
"spacing-10": {
"$type": "number",
"$value": 40
},
"spacing-11": {
"$type": "number",
"$value": 48
},
"spacing-12": {
"$type": "number",
"$value": 56
},
"spacing-13": {
"$type": "number",
"$value": 64
},
"spacing-14": {
"$type": "number",
"$value": 72
},
"spacing-15": {
"$type": "number",
"$value": 80
},
"spacing-16": {
"$type": "number",
"$value": 96
}
}
}

View File

@@ -0,0 +1,133 @@
{
"Fontweight": {
"font-weight-regular": {
"$type": "number",
"$value": 400
},
"font-weight-medium": {
"$type": "number",
"$value": 500
},
"font-weight-semibold": {
"$type": "number",
"$value": 600
}
},
"Fontsize": {
"font-size-01": {
"$type": "number",
"$value": 12,
"$mixin": "body-xs"
},
"font-size-02": {
"$type": "number",
"$value": 14,
"$mixin": "body-sm"
},
"font-size-03": {
"$type": "number",
"$value": 16,
"$mixin": "body-base"
},
"font-size-04": {
"$type": "number",
"$value": 18,
"$mixin": ["body-lg", "heading-xs"]
},
"font-size-05": {
"$type": "number",
"$value": 20,
"$mixin": "heading-sm"
},
"font-size-06": {
"$type": "number",
"$value": 24,
"$mixin": "heading-md"
},
"font-size-07": {
"$type": "number",
"$value": 30,
"$mixin": "heading-lg"
},
"font-size-08": {
"$type": "number",
"$value": 36,
"$mixin": "heading-xl"
},
"font-size-09": {
"$type": "number",
"$value": 48,
"$mixin": "heading-2xl"
},
"font-size-10": {
"$type": "number",
"$value": 52,
"$mixin": "display-xs"
},
"font-size-11": {
"$type": "number",
"$value": 60,
"$mixin": "display-sm"
},
"font-size-12": {
"$type": "number",
"$value": 72,
"$mixin": "display-md"
},
"font-size-13": {
"$type": "number",
"$value": 96,
"$mixin": "display-lg"
}
},
"Lineheight": {
"line-height-01": {
"$type": "number",
"$value": 16
},
"line-height-02": {
"$type": "number",
"$value": 20
},
"line-height-03": {
"$type": "number",
"$value": 24
},
"line-height-04": {
"$type": "number",
"$value": 28
},
"line-height-05": {
"$type": "number",
"$value": 32
},
"line-height-06": {
"$type": "number",
"$value": 40
},
"line-height-07": {
"$type": "number",
"$value": 48
},
"line-height-08": {
"$type": "number",
"$value": 64
},
"line-height-09": {
"$type": "number",
"$value": 68
},
"line-height-10": {
"$type": "number",
"$value": 80
},
"line-height-11": {
"$type": "number",
"$value": 96
},
"line-height-12": {
"$type": "number",
"$value": 128
}
}
}

View File

@@ -218,6 +218,7 @@
"@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
"@sentry/browser": "7.46.0",
"@storybook/addon-a11y": "^8.6.12",
"@storybook/addon-designs": "^8.2.1",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/addon-links": "^8.6.12",