Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ global.Select = (props) =>
window.Tooltip = Tooltip
window.Row = Row
window.FormGroup = FormGroup
// isMobile is set at app boot; stub it so components that read it render in Storybook.
window.isMobile = false

/** @type { import('storybook').Preview } */
const preview = {
Expand Down
11 changes: 11 additions & 0 deletions frontend/common/theme/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@
"warning": { "cssVar": "--color-text-warning", "light": "#ff9f43", "dark": "#ff9f43" },
"info": { "cssVar": "--color-text-info", "light": "#0aaddf", "dark": "#0aaddf" }
},
"code": {
"text": { "cssVar": "--color-code-text", "light": "#2d3443", "dark": "#9da4ae" },
"comment": { "cssVar": "--color-code-comment", "light": "#9da4ae", "dark": "#656d7b" },
"keyword": { "cssVar": "--color-code-keyword", "light": "#6837fc", "dark": "#906af6" },
"name": { "cssVar": "--color-code-name", "light": "#ef4d56", "dark": "#f57c78" },
"literal": { "cssVar": "--color-code-literal", "light": "#0b8bb2", "dark": "#45bce0" },
"string": { "cssVar": "--color-code-string", "light": "#27ab95", "dark": "#56ccad" },
"variable": { "cssVar": "--color-code-variable", "light": "#d06907", "dark": "#efb47c" },
"title": { "cssVar": "--color-code-title", "light": "#0aaddf", "dark": "#7ecde2" },
"builtin": { "cssVar": "--color-code-builtin", "light": "#d4b050", "dark": "#f9dc80" }
},
"border": {
"default": { "cssVar": "--color-border-default", "light": "rgba(101, 109, 123, 0.16)", "dark": "rgba(255, 255, 255, 0.16)" },
"strong": { "cssVar": "--color-border-strong", "light": "rgba(101, 109, 123, 0.24)", "dark": "rgba(255, 255, 255, 0.24)" },
Expand Down
11 changes: 11 additions & 0 deletions frontend/common/theme/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ export const colorBorderStrong =
export const colorBorderSuccess = 'var(--color-border-success, #27ab95)'
export const colorBorderWarning = 'var(--color-border-warning, #ff9f43)'

// Code
export const colorCodeBuiltin = 'var(--color-code-builtin, #d4b050)'
export const colorCodeComment = 'var(--color-code-comment, #9da4ae)'
export const colorCodeKeyword = 'var(--color-code-keyword, #6837fc)'
export const colorCodeLiteral = 'var(--color-code-literal, #0b8bb2)'
export const colorCodeName = 'var(--color-code-name, #ef4d56)'
export const colorCodeString = 'var(--color-code-string, #27ab95)'
export const colorCodeText = 'var(--color-code-text, #2d3443)'
export const colorCodeTitle = 'var(--color-code-title, #0aaddf)'
export const colorCodeVariable = 'var(--color-code-variable, #d06907)'

// Icon
export const colorIconAction = 'var(--color-icon-action, #6837fc)'
export const colorIconDanger = 'var(--color-icon-danger, #ef4d56)'
Expand Down
2 changes: 2 additions & 0 deletions frontend/documentation/SemanticTokens.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const TOKEN_PREFIX = '--color-'
const GROUP_LABELS: Record<string, string> = {
border: 'Border',
brand: 'Brand',
code: 'Code',
danger: 'Danger',
icon: 'Icon',
info: 'Info',
Expand Down Expand Up @@ -83,6 +84,7 @@ function readTokens(): TokenGroupData[] {
'brand',
'surface',
'text',
'code',
'border',
'icon',
'danger',
Expand Down
83 changes: 83 additions & 0 deletions frontend/documentation/TokenReference.generated.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,89 @@ export const AllTokens: StoryObj = {
</tr>
</tbody>
</table>
<h3>Colour: code</h3>
Comment thread
talissoncosta marked this conversation as resolved.
<table className='docs-table'>
<thead>
<tr>
<th>Token</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>--color-code-text</code>
</td>
<td>
<code>var(--slate-700)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-comment</code>
</td>
<td>
<code>var(--slate-300)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-keyword</code>
</td>
<td>
<code>var(--purple-600)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-name</code>
</td>
<td>
<code>var(--red-500)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-literal</code>
</td>
<td>
<code>var(--blue-600)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-string</code>
</td>
<td>
<code>var(--green-500)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-variable</code>
</td>
<td>
<code>var(--orange-700)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-title</code>
</td>
<td>
<code>var(--blue-500)</code>
</td>
</tr>
<tr>
<td>
<code>--color-code-builtin</code>
</td>
<td>
<code>var(--gold-700)</code>
</td>
</tr>
</tbody>
</table>
<h3>Colour: border</h3>
<table className='docs-table'>
<thead>
Expand Down
93 changes: 93 additions & 0 deletions frontend/documentation/pages/Highlight.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Meta, StoryObj } from 'storybook'

import Highlight from 'components/Highlight'

const meta: Meta<typeof Highlight> = {
component: Highlight,
parameters: {
docs: {
description: {
component:
'The code wrapper: renders a `<pre><code class="hljs">` block and runs ' +
'highlight.js over it. This is what the shared code theme styles (surface, ' +
'border, syntax palette). Toggle the theme in the toolbar to QA light vs dark; ' +
'`className` is the highlight.js language.',
},
},
layout: 'padded',
},
title: 'Components/Highlight',
}

export default meta

type Story = StoryObj<typeof Highlight>

const js = `// Initialise the client
import flagsmith from 'flagsmith'

flagsmith.init({
environmentID: 'YOUR_ENVIRONMENT_KEY',
onChange: () => {
const bannerSize = flagsmith.getValue('banner_size')
},
})`

const ts = `import { useFlags } from 'flagsmith/react'

type FlagName = 'banner_size' | 'my_feature'

const flags = useFlags<FlagName>(['banner_size'])
const size: number = flags.banner_size.value ?? 0`

const json = `{
"flags": [
{ "feature": "banner_size", "enabled": true, "value": 42 },
{ "feature": "my_feature", "enabled": false, "value": null }
]
}`

const yaml = `flags:
- feature: banner_size
enabled: true
value: 42`

// Mirrors the React SDK snippet in code-help: markup tags inside JavaScript.
const jsx = `export function HomePage() {
const flags = useFlags(['banner_size'])
return (
<Row className="banner">
<Flag name="banner_size" size={flags.banner_size.value} />
</Row>
)
}`

export const JavaScript: Story = {
args: { children: js, className: 'javascript', forceExpanded: true },
}

export const TypeScript: Story = {
args: { children: ts, className: 'typescript', forceExpanded: true },
}

export const Json: Story = {
args: { children: json, className: 'json', forceExpanded: true },
}

export const Yaml: Story = {
args: { children: yaml, className: 'yaml', forceExpanded: true },
}

export const Jsx: Story = {
args: { children: jsx, className: 'javascript', forceExpanded: true },
}

// Embedded variant: transparent, for code whose container owns the surface.
export const Embedded: Story = {
args: {
children: js,
className: 'javascript',
embedded: true,
forceExpanded: true,
},
}
31 changes: 12 additions & 19 deletions frontend/web/components/Highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,20 @@ class Highlight extends React.Component {
this.props.onBlur?.()
}

// The value to render before escaping: the live edit while focused, the
// current value when there's content, otherwise a disabled/empty placeholder.
getRawHtml = () => {
Comment thread
talissoncosta marked this conversation as resolved.
if (this.state.focus) return this.state.value
if (this.props.children) return { ...this.state.value }
return this.props.disabled ? defaultDisabledValue : defaultValue
}

render() {
const {
children,
className,
disabled,
element: Element,
embedded,
innerHTML,
} = this.props
const props = { className, ref: this.setEl }
Expand All @@ -125,23 +133,8 @@ class Highlight extends React.Component {
return <Element {...props}>{children}</Element>
}

const html = this.props.preventEscape
? this.state.focus
? this.state.value
: this.props.children
? { ...this.state.value }
: disabled
? defaultDisabledValue
: defaultValue
: escapeHtml(
this.state.focus
? this.state.value
: this.props.children
? { ...this.state.value }
: disabled
? defaultDisabledValue
: defaultValue,
)
const raw = this.getRawHtml()
const html = this.props.preventEscape ? raw : escapeHtml(raw)
return (
<div className={this.state.expandable ? 'expandable' : ''}>
<pre
Expand All @@ -167,7 +160,7 @@ class Highlight extends React.Component {
onBlur={this.onBlur}
onFocus={this.onFocus}
onInput={this._handleInput}
className={`${className} ${
className={`${className}${embedded ? ' hljs--embedded' : ''} ${
!this.state.value || !this.state.value.__html ? 'empty' : ''
}`}
dangerouslySetInnerHTML={html}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const CodeCard: FC<CodeCardProps> = ({
</span>
</Button>
</div>
<Highlight forceExpanded className={language}>
<Highlight embedded forceExpanded className={language}>
{code}
</Highlight>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,67 +32,11 @@
}
}

// Code cards - theme-adaptive via semantic tokens (light editor in light mode,
// dark in dark mode). Surface, base text and every syntax colour map to
// semantic tokens that flip with the theme, so there's no hardcoded hex and no
// per-theme override. TODO(#6606): lift this palette into the shared `.hljs`
// theme so all code blocks app-wide adapt the same way.
// The card owns the surface + rounding; the code block opts into the shared
// embedded modifier (Highlight's `embedded` prop) for its transparent variant.
&__codecard {
border-radius: 10px;
overflow: hidden;

.hljs {
// Inherit the card surface; base text adapts with the theme.
background: transparent;
border: 0;
border-radius: 0;
color: var(--color-text-default);
// Reserve the scrollbar gutter permanently so hover doesn't shove the
// layout (the global dark-scroll mixin toggles display:none on a
// space-reserving webkit bar otherwise).
overflow-x: scroll;
overflow-y: hidden;

&::-webkit-scrollbar,
&:hover::-webkit-scrollbar {
display: block !important;
}

// Unmapped tokens (tags, attributes, params, punctuation) inherit the
// readable base instead of the global hljs greys; the semantic palette
// below re-colours the specific token types.
* {
color: inherit;
}
}

.hljs-keyword,
.hljs-built_in,
.hljs-literal,
.hljs-selector-tag {
color: var(--color-text-action);
}
.hljs-string,
.hljs-regexp {
color: var(--color-text-success);
}
.hljs-attr,
.hljs-attribute,
.hljs-property,
.hljs-number,
.hljs-type {
color: var(--color-text-info);
}
.hljs-title,
.hljs-name,
.hljs-section {
color: var(--color-text-default);
}
.hljs-comment,
.hljs-quote,
.hljs-meta {
color: var(--color-text-secondary);
}
}

&__codecard-head {
Expand Down
Loading
Loading