Skip to content
Closed
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
77 changes: 77 additions & 0 deletions frontend/documentation/pages/CodeHelp.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Meta, StoryObj } from 'storybook'

import CodeHelp from 'components/CodeHelp'
import Constants from 'common/constants'

// A stand-in environment key for the snippets.
const ENV = 'YOUR_ENVIRONMENT_KEY'

const meta: Meta<typeof CodeHelp> = {
component: CodeHelp,
parameters: {
docs: {
description: {
component:
'The shared code-example component, used across SDK setup, identities, segments and environment docs. These stories mirror those real use cases with the actual `codeHelp` snippets. Use the theme toggle in the toolbar to QA the code block (surface + syntax colours) in light and dark, and the language dropdown to check each SDK.',
},
},
layout: 'padded',
},
title: 'Components/CodeHelp',
}
export default meta

type Story = StoryObj<typeof CodeHelp>

// Installing the SDK (SDK integration page).
export const Install: Story = {
args: {
showInitially: true,
snippets: Constants.codeHelp.INSTALL,
title: 'Installing the SDK',
},
}

// Initialising the client, keyed by the environment (the most common use).
export const Initialise: Story = {
args: {
showInitially: true,
snippets: Constants.codeHelp.INIT(ENV),
title: 'Initialising your project',
},
}

// Creating an identity (identities pages).
export const CreateIdentity: Story = {
args: {
showInitially: true,
snippets: Constants.codeHelp.CREATE_USER(ENV),
title: 'Creating an identity',
},
}

// Setting traits (identities pages).
export const SetTraits: Story = {
args: {
showInitially: true,
snippets: Constants.codeHelp.USER_TRAITS(ENV),
title: 'Setting traits',
},
}

// Default entry point: the collapsed CalloutBar header before it's opened.
export const CollapsedHeader: Story = {
args: {
snippets: Constants.codeHelp.INSTALL,
title: 'Installing the SDK',
},
}

// Embedded with no header and no docs links (e.g. inside another panel).
export const Embedded: Story = {
args: {
hideDocs: true,
hideHeader: true,
snippets: Constants.codeHelp.INIT(ENV),
},
}
1 change: 1 addition & 0 deletions frontend/web/components/CodeHelp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Constants from 'common/constants'
import Utils from 'common/utils/utils'
import Icon from './icons/Icon'
import CalloutBar from './CalloutBar'
import Button from './base/forms/Button'

type Snippets = Record<string, string>

Expand Down
36 changes: 12 additions & 24 deletions frontend/web/components/Highlight.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import hljs from 'highlight.js'
import Button from './base/forms/Button'

function escapeHtml(unsafe) {
if (!unsafe || !unsafe.__html) return unsafe
Expand Down Expand Up @@ -103,14 +104,16 @@ 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 = () => {
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,
innerHTML,
} = this.props
const { children, className, element: Element, innerHTML } = this.props
const props = { className, ref: this.setEl }

if (innerHTML) {
Expand All @@ -125,23 +128,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 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 forceExpanded className={`${language} hljs--embedded`}>
{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
// `.hljs--embedded` modifier for its palette and theme adaptation.
&__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
110 changes: 54 additions & 56 deletions frontend/web/styles/3rdParty/_hljs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,18 @@
font-weight: 500;
}
&.primary {
color: $primary !important;
color: $primary;
}
}
}
}
.hljs {
a {
color: white !important;
color: var(--color-text-action) !important;
text-decoration: underline;
.hljs-regexp,
.hljs-comment {
color: white !important;
color: var(--color-text-action) !important;
}
}
cursor: text;
Expand All @@ -83,9 +83,10 @@
overflow-x: auto;
@include customScrollDark();
padding: 0.5em;
background: $code-bg;
// Theme-adaptive surface: light in light mode, dark in dark, via tokens.
background: var(--color-surface-subtle);
border-radius: $border-radius;
border: 1px solid $basic-alpha-8;
border: 1px solid var(--color-border-default);
&.no-pad {
margin-bottom: 0;
}
Expand All @@ -99,13 +100,13 @@
}
.yaml {
.hljs-bullet {
color: #eee;
color: var(--color-text-default);
}
.hljs-attr {
color: #64c77c;
color: var(--color-text-success);
}
.hljs-string {
color: #4c96ec;
color: var(--color-text-info);
}
}
.hljs-container {
Expand Down Expand Up @@ -177,71 +178,51 @@
}

.hljs {
color: $code-color;
color: var(--color-text-default);
padding: 18px;
// Unmapped tokens inherit the base colour instead of the highlight.js greys;
// the palette below re-colours specific types.
* {
color: inherit;
}
}

.json {
border: none;
}
.hljs-strong,
.hljs-emphasis {
color: #a8a8a2;
}

.hljs-bullet,
.hljs-quote,
.hljs-link,
.hljs-number,
.hljs-regexp,
.hljs-literal {
color: $info;
}

.hljs-code,
.hljs-selector-class {
color: #a6e22e;
.hljs-keyword,
.hljs-built_in,
.hljs-literal,
.hljs-selector-tag {
color: var(--color-text-action);
}

.hljs-emphasis {
font-style: italic;
.hljs-string,
.hljs-regexp {
color: var(--color-text-success);
}

.hljs-keyword,
.hljs-selector-tag,
.hljs-section,
.hljs-attr,
.hljs-attribute,
.hljs-name,
.hljs-variable {
color: $warning;
}

.hljs-params {
color: #b9b9b9;
.hljs-property,
.hljs-number,
.hljs-type {
color: var(--color-text-info);
}

.hljs-string {
color: $success;
.hljs-comment,
.hljs-quote,
.hljs-meta {
color: var(--color-text-secondary);
}

.hljs-subst,
.hljs-type,
.hljs-built_in,
.hljs-builtin-name,
.hljs-symbol,
.hljs-selector-id,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-template-tag,
.hljs-template-variable,
.hljs-addition {
color: $secondary400;
.hljs-emphasis {
font-style: italic;
}

.hljs-comment,
.hljs-deletion,
.hljs-meta {
color: #7f7f7f;
.hljs-strong {
font-weight: bold;
}
.value-editor pre {
overflow: hidden;
Expand Down Expand Up @@ -296,6 +277,23 @@ code.txt {
}

code[contenteditable='false'].hljs {
cursor: default !important;
cursor: default;
@include customScrollDark();
}

// For code inside a container that already provides the surface: transparent,
// no border/radius; the container owns the frame.
.hljs--embedded {
background: transparent;
border: 0;
border-radius: 0;
// Reserve the scrollbar gutter so hover doesn't shift layout; !important
// beats customScrollDark's display:none on non-editable code.
overflow-x: scroll;
overflow-y: hidden;

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