diff --git a/frontend/documentation/pages/CodeHelp.stories.tsx b/frontend/documentation/pages/CodeHelp.stories.tsx new file mode 100644 index 000000000000..1cb58d0e31fd --- /dev/null +++ b/frontend/documentation/pages/CodeHelp.stories.tsx @@ -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 = { + 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 + +// 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), + }, +} diff --git a/frontend/web/components/CodeHelp.tsx b/frontend/web/components/CodeHelp.tsx index ad2dbb87e052..500a30beb4ee 100644 --- a/frontend/web/components/CodeHelp.tsx +++ b/frontend/web/components/CodeHelp.tsx @@ -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 diff --git a/frontend/web/components/Highlight.js b/frontend/web/components/Highlight.js index 6f5ae59ccaf4..0cf70993d8c1 100644 --- a/frontend/web/components/Highlight.js +++ b/frontend/web/components/Highlight.js @@ -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 @@ -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) { @@ -125,23 +128,8 @@ class Highlight extends React.Component { return {children} } - 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 (
 = ({
           
         
       
- + {code} diff --git a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss index 55e5df84ecd0..08511e85cb02 100644 --- a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss +++ b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss @@ -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 { diff --git a/frontend/web/styles/3rdParty/_hljs.scss b/frontend/web/styles/3rdParty/_hljs.scss index 2b564e813435..f1af897834aa 100644 --- a/frontend/web/styles/3rdParty/_hljs.scss +++ b/frontend/web/styles/3rdParty/_hljs.scss @@ -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; @@ -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; } @@ -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 { @@ -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; @@ -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; + } +}