diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 54f8d7f7c031..29065d615a96 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -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 = { diff --git a/frontend/common/code-help/create-user/create-user-next.js b/frontend/common/code-help/create-user/create-user-next.js index fcd4b1c5c6e8..cfd0890775f8 100644 --- a/frontend/common/code-help/create-user/create-user-next.js +++ b/frontend/common/code-help/create-user/create-user-next.js @@ -23,7 +23,7 @@ export default function HomePage() { export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } diff --git a/frontend/common/code-help/create-user/create-user-react.js b/frontend/common/code-help/create-user/create-user-react.js index 4de611b66d58..e44396b5e1a0 100644 --- a/frontend/common/code-help/create-user/create-user-react.js +++ b/frontend/common/code-help/create-user/create-user-react.js @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react'; export default function App() { return ( - <FlagsmithProvider + {...Your app} - </FlagsmithProvider> + ); } diff --git a/frontend/common/code-help/init/init-next-app-router.js b/frontend/common/code-help/init/init-next-app-router.js index 3cc974fa91f0..c912e46d5665 100644 --- a/frontend/common/code-help/init/init-next-app-router.js +++ b/frontend/common/code-help/init/init-next-app-router.js @@ -9,9 +9,9 @@ import { FeatureFlagProvider } from "./components/FeatureFlagProvider"; export default async function RootLayout({ children, -}: Readonly<{ +}: Readonly<{ children: React.ReactNode; -}>) { +}>) { await flagsmith.init({ environmentID: "${envId}",${ Constants.isCustomFlagsmithUrl() @@ -21,16 +21,16 @@ export default async function RootLayout({ const serverState = flagsmith.getState(); return ( - <html lang="en"> - <head> - <meta name="viewport" content="initial-scale=1, width=device-width" /> - </head> - <body> - <FeatureFlagProvider serverState={serverState}> + + + + + + {children} - </FeatureFlagProvider> - </body> - </html> + + + ); } @@ -48,13 +48,13 @@ export const FeatureFlagProvider = ({ }: { serverState: IState; children: ReactNode; -}) => { +}) => { const flagsmithInstance = useRef(createFlagsmithInstance()); return ( - <FlagsmithProvider flagsmith={flagsmithInstance.current} serverState={serverState}> - <>{children}</> - </FlagsmithProvider> + + <>{children} + ); }; @@ -69,6 +69,6 @@ export default function HomePage() { const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/common/code-help/init/init-next-pages-router.js b/frontend/common/code-help/init/init-next-pages-router.js index 1e60dc327167..87dcf864b5eb 100644 --- a/frontend/common/code-help/init/init-next-pages-router.js +++ b/frontend/common/code-help/init/init-next-pages-router.js @@ -8,7 +8,7 @@ import { FlagsmithProvider } from '@flagsmith/flagsmith/react'; export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } @@ -43,6 +43,6 @@ export default function HomePage() { const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/common/code-help/init/init-react.js b/frontend/common/code-help/init/init-react.js index 6b1a3412cf0e..8b143701b529 100644 --- a/frontend/common/code-help/init/init-react.js +++ b/frontend/common/code-help/init/init-react.js @@ -3,13 +3,24 @@ import Constants from 'common/constants' export default ( envId, { FEATURE_NAME, FEATURE_NAME_ALT, LIB_NAME, NPM_CLIENT }, -) => `// App root -import ${LIB_NAME} from "${NPM_CLIENT}"; -import { FlagsmithProvider } from '@flagsmith/flagsmith/react'; +) => `import ${LIB_NAME} from "${NPM_CLIENT}"; +import { FlagsmithProvider, useFlags } from '${NPM_CLIENT}/react'; + +export function HomePage() { + const flags = useFlags(['${FEATURE_NAME}','${FEATURE_NAME_ALT}']); // only causes re-render if specified flag values / traits change + const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled + const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value + return ( + <> + {\`${FEATURE_NAME}: \${${FEATURE_NAME}}\`} + {\`${FEATURE_NAME_ALT}: \${${FEATURE_NAME_ALT}}\`} + + ); +} export default function App() { return ( - <FlagsmithProvider + - ); -} - -// Home Page -import ${LIB_NAME} from '${NPM_CLIENT}'; -import { useFlags, useFlagsmith } from '${NPM_CLIENT}/react'; - -export default function HomePage() { - const flags = useFlags(['${FEATURE_NAME}','${FEATURE_NAME_ALT}']); // only causes re-render if specified flag values / traits change - const ${FEATURE_NAME} = flags.${FEATURE_NAME}.enabled - const ${FEATURE_NAME_ALT} = flags.${FEATURE_NAME_ALT}.value - return ( - <>{...}</> + flagsmith={${LIB_NAME}}> + + ); }` diff --git a/frontend/common/code-help/traits/traits-next.js b/frontend/common/code-help/traits/traits-next.js index 1a096ed39dda..d396cb7e2983 100644 --- a/frontend/common/code-help/traits/traits-next.js +++ b/frontend/common/code-help/traits/traits-next.js @@ -23,7 +23,7 @@ export default function HomePage() { }; return ( - <>{...}</> + <>{...} ); } @@ -31,7 +31,7 @@ export default function HomePage() { export default function App({ Component, pageProps, flagsmithState } { return ( - <FlagsmithProvider + - </FlagsmithProvider> + flagsmith={flagsmith}> + + ); } diff --git a/frontend/common/code-help/traits/traits-react.js b/frontend/common/code-help/traits/traits-react.js index c9bf65a6bff2..05df85d95c20 100644 --- a/frontend/common/code-help/traits/traits-react.js +++ b/frontend/common/code-help/traits/traits-react.js @@ -10,7 +10,7 @@ import { FlagsmithProvider } from '${NPM_CLIENT}/react'; export default function App() { return ( - <FlagsmithProvider + {...Your app} - </FlagsmithProvider> + ); } @@ -46,6 +46,6 @@ export default function HomePage() { }; return ( - <>{...}</> + <>{...} ); }` diff --git a/frontend/common/theme/tokens.json b/frontend/common/theme/tokens.json index 7004ce34a887..fcad1cb37f7d 100644 --- a/frontend/common/theme/tokens.json +++ b/frontend/common/theme/tokens.json @@ -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)" }, diff --git a/frontend/common/theme/tokens.ts b/frontend/common/theme/tokens.ts index 2b8839e6feaa..7397d1972cfa 100644 --- a/frontend/common/theme/tokens.ts +++ b/frontend/common/theme/tokens.ts @@ -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)' diff --git a/frontend/documentation/SemanticTokens.stories.tsx b/frontend/documentation/SemanticTokens.stories.tsx index 825481d4eaf6..d378dc4e52d1 100644 --- a/frontend/documentation/SemanticTokens.stories.tsx +++ b/frontend/documentation/SemanticTokens.stories.tsx @@ -22,6 +22,7 @@ const TOKEN_PREFIX = '--color-' const GROUP_LABELS: Record = { border: 'Border', brand: 'Brand', + code: 'Code', danger: 'Danger', icon: 'Icon', info: 'Info', @@ -83,6 +84,7 @@ function readTokens(): TokenGroupData[] { 'brand', 'surface', 'text', + 'code', 'border', 'icon', 'danger', diff --git a/frontend/documentation/TokenReference.generated.stories.tsx b/frontend/documentation/TokenReference.generated.stories.tsx index 15927ca8228f..97c711495103 100644 --- a/frontend/documentation/TokenReference.generated.stories.tsx +++ b/frontend/documentation/TokenReference.generated.stories.tsx @@ -234,6 +234,89 @@ export const AllTokens: StoryObj = { +

Colour: code

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TokenValue
+ --color-code-text + + var(--slate-700) +
+ --color-code-comment + + var(--slate-300) +
+ --color-code-keyword + + var(--purple-600) +
+ --color-code-name + + var(--red-500) +
+ --color-code-literal + + var(--blue-600) +
+ --color-code-string + + var(--green-500) +
+ --color-code-variable + + var(--orange-700) +
+ --color-code-title + + var(--blue-500) +
+ --color-code-builtin + + var(--gold-700) +

Colour: border

diff --git a/frontend/documentation/pages/Highlight.stories.tsx b/frontend/documentation/pages/Highlight.stories.tsx new file mode 100644 index 000000000000..9d1d5866d76b --- /dev/null +++ b/frontend/documentation/pages/Highlight.stories.tsx @@ -0,0 +1,93 @@ +import type { Meta, StoryObj } from 'storybook' + +import Highlight from 'components/Highlight' + +const meta: Meta = { + component: Highlight, + parameters: { + docs: { + description: { + component: + 'The code wrapper: renders a `
` 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
+
+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(['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 (
+    
+      
+    
+  )
+}`
+
+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,
+  },
+}
diff --git a/frontend/web/components/CodeHelp.tsx b/frontend/web/components/CodeHelp.tsx
index ad2dbb87e052..47959a888382 100644
--- a/frontend/web/components/CodeHelp.tsx
+++ b/frontend/web/components/CodeHelp.tsx
@@ -144,7 +144,6 @@ const SnippetItem: FC = ({
       />
        {
+    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 }
@@ -125,23 +134,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 = escapeHtml(raw)
     return (
       
 = ({ code, language = 'bash' }) => (
   
- + {code}
diff --git a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/CodeCard.tsx b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/CodeCard.tsx index 270897ec8742..c0faddc9e182 100644 --- a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/CodeCard.tsx +++ b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/CodeCard.tsx @@ -47,7 +47,7 @@ const CodeCard: FC = ({
- + {code}
diff --git a/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss b/frontend/web/components/pages/onboarding/OnboardingConnectPanel/OnboardingConnectPanel.scss index 55e5df84ecd0..eb5bd7b02c30 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 + // 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 { diff --git a/frontend/web/styles/3rdParty/_hljs.scss b/frontend/web/styles/3rdParty/_hljs.scss index 2b564e813435..2cda10cb75c7 100644 --- a/frontend/web/styles/3rdParty/_hljs.scss +++ b/frontend/web/styles/3rdParty/_hljs.scss @@ -8,8 +8,26 @@ -webkit-transition: $transition; transition: $transition; } + +// Recolour customScrollDark's fixed dark thumb with tokens (it reads as a +// black bar on light surfaces). Include after customScrollDark so these win. +@mixin hljsScrollbar { + &::-webkit-scrollbar-track { + background-color: transparent; + } + &::-webkit-scrollbar-thumb { + background-color: var(--color-border-strong); + &:hover { + background-color: var(--color-text-tertiary); + } + } +} + +// highlight.js and Pylon's chat widget both use .hljs. We win two ways: feed +// our tokens into Pylon's --hljs-* vars, and nest our palette under .hljs to +// out-specify its global rules. Also paints Pylon's chat code, which is fine. .invalid .value-editor .hljs { - border: 2px solid $danger; + border: 2px solid var(--color-border-danger); } .value-editor { position: relative; @@ -20,10 +38,8 @@ } &.code-medium { .hljs { - // Align the single-line editor height with adjacent inputs ($input-height). - // Reduced vertical padding so border + line-height + padding == $input-height, - // while min-height keeps the empty/placeholder state aligned too. The editor - // still grows for multi-line values. + // Match adjacent inputs' height ($input-height); min-height holds the + // empty state, and the editor still grows for multi-line values. padding: 9px 12px 9px 16px; min-height: $input-height; } @@ -46,34 +62,34 @@ opacity: 1; } .language-icon { - color: $body-color; + color: var(--color-text-default); } } span { display: flex; align-items: center; gap: 2px; - color: $text-icon-light-grey; + color: var(--color-text-secondary); cursor: pointer; font-size: $font-caption-sm; line-height: $line-height-xxsm; &.active { - color: $body-color; + color: var(--color-text-default); font-weight: 500; } &.primary { - color: $primary !important; + color: var(--color-text-action); } } } } .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; @@ -82,10 +98,23 @@ display: block; overflow-x: auto; @include customScrollDark(); + @include hljsScrollbar(); padding: 0.5em; - background: $code-bg; + // Redefine Pylon's --hljs-* vars to our code tokens so its injected rules + // paint our theme (belt-and-braces with the palette specificity below). + --hljs-bg: var(--color-surface-subtle); + --hljs-text: var(--color-code-text); + --hljs-comment: var(--color-code-comment); + --hljs-keyword: var(--color-code-keyword); + --hljs-literal: var(--color-code-literal); + --hljs-string: var(--color-code-string); + --hljs-attr: var(--color-code-variable); + --hljs-section: var(--color-code-name); + --hljs-symbol: var(--color-code-title); + --hljs-class: var(--color-code-builtin); + 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; } @@ -93,21 +122,10 @@ font-size: $font-sm; line-height: $line-height-sm; padding-left: 12px; - color: $text-icon-light; + color: var(--color-text-secondary); margin-bottom: 0; } } -.yaml { - .hljs-bullet { - color: #eee; - } - .hljs-attr { - color: #64c77c; - } - .hljs-string { - color: #4c96ec; - } -} .hljs-container { position: relative; &:hover { @@ -177,71 +195,94 @@ } .hljs { - color: $code-color; + color: var(--color-code-text); padding: 18px; -} -.json { - border: none; -} -.hljs-strong, -.hljs-emphasis { - color: #a8a8a2; -} + // Nested under .hljs (0,2,0) to out-specify Pylon's global token rules + // (0,1,0). The catch-all sends unmapped tokens to base; groups re-colour. + [class*='hljs-'] { + color: inherit; + } -.hljs-bullet, -.hljs-quote, -.hljs-link, -.hljs-number, -.hljs-regexp, -.hljs-literal { - color: $info; -} + .hljs-comment, + .hljs-quote { + color: var(--color-code-comment); + font-style: italic; + } -.hljs-code, -.hljs-selector-class { - color: #a6e22e; -} + .hljs-doctag, + .hljs-keyword, + .hljs-formula { + color: var(--color-code-keyword); + } -.hljs-emphasis { - font-style: italic; -} + .hljs-section, + .hljs-deletion, + .hljs-subst { + color: var(--color-code-name); + } -.hljs-keyword, -.hljs-selector-tag, -.hljs-section, -.hljs-attribute, -.hljs-name, -.hljs-variable { - color: $warning; -} + // Markup tag names are deliberately not red (red reads as an error or a + // deletion). The < > punctuation (.hljs-tag) stays base text via the + // catch-all above, so tags render like an IDE: grey brackets, blue name. + .hljs-name, + .hljs-selector-tag { + color: var(--color-code-title); + } -.hljs-params { - color: #b9b9b9; -} + .hljs-literal { + color: var(--color-code-literal); + } -.hljs-string { - color: $success; -} + .hljs-string, + .hljs-regexp, + .hljs-addition, + .hljs-attribute, + .hljs-meta .hljs-string { + color: var(--color-code-string); + } + + .hljs-attr, + .hljs-variable, + .hljs-template-variable, + .hljs-type, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-number { + color: var(--color-code-variable); + } + + .hljs-symbol, + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-title { + color: var(--color-code-title); + } + + .hljs-built_in, + .hljs-title.class_, + .hljs-class .hljs-title { + color: var(--color-code-builtin); + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } -.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-link { + text-decoration: underline; + } } -.hljs-comment, -.hljs-deletion, -.hljs-meta { - color: #7f7f7f; +.json { + border: none; } .value-editor pre { overflow: hidden; @@ -256,18 +297,18 @@ code.txt { font-size: $font-size-base; font-weight: 500; background: transparent !important; - border: 1px solid $input-border-color; + border: 1px solid var(--color-border-default); font-family: 'OpenSans', sans-serif; - color: $body-color; + color: var(--color-text-default); &.empty { font-weight: normal; - color: $input-placeholder-color; + color: var(--color-text-tertiary); } &:focus, &:focus.empty { font-weight: 500; - color: $body-color; - border-color: $primary; + color: var(--color-text-default); + border-color: var(--color-border-action); } } @@ -296,6 +337,26 @@ code.txt { } code[contenteditable='false'].hljs { - cursor: default !important; + cursor: default; @include customScrollDark(); + @include hljsScrollbar(); +} + +// For code inside a container that already provides the surface: transparent, +// no border/radius; the container owns the frame. +.hljs--embedded { + --hljs-bg: transparent; + background: transparent; + border: 0; + border-radius: 0; + // Scroll only when the code overflows. When it does, the webkit rule below + // forces the bar visible over customScrollDark (which display:none-s it on + // hover), so a long snippet's bar doesn't flicker. + overflow-x: auto; + overflow-y: hidden; + + &::-webkit-scrollbar, + &:hover::-webkit-scrollbar { + display: block !important; + } } diff --git a/frontend/web/styles/_tokens.scss b/frontend/web/styles/_tokens.scss index a7546c72afa5..c5b653e2c50b 100644 --- a/frontend/web/styles/_tokens.scss +++ b/frontend/web/styles/_tokens.scss @@ -114,6 +114,17 @@ --color-text-tertiary: var(--slate-300); --color-text-warning: var(--orange-500); + // Code + --color-code-builtin: var(--gold-700); + --color-code-comment: var(--slate-300); + --color-code-keyword: var(--purple-600); + --color-code-literal: var(--blue-600); + --color-code-name: var(--red-500); + --color-code-string: var(--green-500); + --color-code-text: var(--slate-700); + --color-code-title: var(--blue-500); + --color-code-variable: var(--orange-700); + // Border --color-border-action: var(--purple-600); --color-border-danger: var(--red-500); @@ -201,6 +212,15 @@ --color-text-disabled: oklch(from var(--slate-0) l c h / 0.32); --color-text-secondary: var(--slate-300); --color-text-tertiary: oklch(from var(--slate-0) l c h / 0.48); + --color-code-builtin: var(--gold-400); + --color-code-comment: var(--slate-500); + --color-code-keyword: var(--purple-400); + --color-code-literal: var(--blue-400); + --color-code-name: var(--red-400); + --color-code-string: var(--green-400); + --color-code-text: var(--slate-300); + --color-code-title: var(--blue-300); + --color-code-variable: var(--orange-400); --color-border-action: var(--purple-400); --color-border-default: oklch(from var(--slate-0) l c h / 0.16); --color-border-disabled: oklch(from var(--slate-0) l c h / 0.08); diff --git a/frontend/web/styles/_variables.scss b/frontend/web/styles/_variables.scss index 2bde5714f42b..46efbd7ef2da 100644 --- a/frontend/web/styles/_variables.scss +++ b/frontend/web/styles/_variables.scss @@ -312,8 +312,6 @@ $tab-line-color: $primary; //CodeHelp $code-btn-bg: $secondary-alfa-16; $code-btn-description-color: $secondary600; -$code-color: $text-icon-light; -$code-bg: $bg-dark200; //Panels $panel-border-color: $basic-alpha-16;