From 5447b244571f3099392b3108116a2732ed17d472 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Thu, 12 Sep 2024 13:40:55 +0200 Subject: [PATCH] docs: simplify CSS docs --- .gitignore | 1 + .../component-variables.template.mdx | 9 +- doc-templates/global-variables.template.mdx | 16 +- .../theming/palette-variables.mdx | 4 +- scripts/generate-docs.ts | 24 +-- scripts/output.ts | 197 ++---------------- scripts/parser.ts | 119 +++-------- 7 files changed, 67 insertions(+), 303 deletions(-) rename doc-templates/palette-variables.template.mdx => docs/theming/palette-variables.mdx (75%) diff --git a/.gitignore b/.gitignore index a82ebef5..65d8574e 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ dist # Generated docs docs/**/*.mdx !docs/theming/introduction.mdx +!docs/theming/palette-variables.mdx # Mac .DS_Store diff --git a/doc-templates/component-variables.template.mdx b/doc-templates/component-variables.template.mdx index e3337061..eb345c5a 100644 --- a/doc-templates/component-variables.template.mdx +++ b/doc-templates/component-variables.template.mdx @@ -23,6 +23,13 @@ CSS variables are the easiest way to customize the theme. The variables are orga - Global - Component -This page contains information about the component variables. +Global variables change the layout/look-and-feel of the whole chat UI, meanwhile component variables change only a part of it (for example message component). + +Component variables can be further grouped in the following ways: + +- **Theme variables** for changing text and background colors, borders and shadows +- **Layout variables** defined for some components (but not all) to change the size of a specific part of a component + +You can find the list of components below: [//]: # '#SLOT-autogenerated-component-variables' diff --git a/doc-templates/global-variables.template.mdx b/doc-templates/global-variables.template.mdx index 0ba57b9c..34704d74 100644 --- a/doc-templates/global-variables.template.mdx +++ b/doc-templates/global-variables.template.mdx @@ -23,21 +23,11 @@ CSS variables are the easiest way to customize the theme. The variables are orga - Global - Component -This page contains information about the global variables. +Global variables change the layout/look-and-feel of the whole chat UI, meanwhile component variables change only a part of it (for example message component). Global variables can be grouped into the following categories: -- Theme: colors, typography and border radiuses -- Layout: spacing (padding and margin) and sizing - -You can read about each category in detail in the tables below. - -## Theme variables - -[//]: # '#SLOT-autogenerated-theme-variables' - -## Layout variables - -[//]: # '#SLOT-autogenerated-layout-variables' +- **Theme**: colors, typography and border radiuses [//]: # '#SLOT-autogenerated-theme-variables' +- **Layout**: spacing (padding and margin) and sizing [//]: # '#SLOT-autogenerated-layout-variables' If you find that these variables are too high-level and you need more granular control, you also have the option to provide [component layer overrides](./component-variables.mdx). diff --git a/doc-templates/palette-variables.template.mdx b/docs/theming/palette-variables.mdx similarity index 75% rename from doc-templates/palette-variables.template.mdx rename to docs/theming/palette-variables.mdx index 3782c486..dd61b47e 100644 --- a/doc-templates/palette-variables.template.mdx +++ b/docs/theming/palette-variables.mdx @@ -20,6 +20,4 @@ import V2Warning from './V2Warning'; A color palette is defined inside the library that used to define default values for the [global theme variables](./global-variables.mdx). If you want to work with the default theme but want to adjust the shades (for example, change `blue500` to a lighter color), you can update the palette variables. However, if you want to change the color scheme of the theme (for example, change the primary color from blue to green), you should take a look at [global theme variables](./global-variables.mdx). -[//]: # '#SLOT-autogenerated-palette-variables' - -Palette variables are defined in: [https://github.com/GetStream/stream-chat-css/blob/main/src-v2/styles/\_palette-variables.scss](https://github.com/GetStream/stream-chat-css/blob/main/src-v2/styles/_palette-variables.scss) +You can find the [list of palette variables on GitHub](https://github.com/GetStream/stream-chat-css/blob/main/src-v2/styles/_palette-variables.scss). diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index fac9ede9..58d11981 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -1,16 +1,12 @@ import * as fs from 'fs'; -import { extractVariables } from './parser'; -import { getComponentVariablesOutput, getPaletteVariablesOutput, getGlobalVariablesOutput } from './output'; +import { getComponentVariablesOutput, getGlobalVariablesOutput } from './output'; +import { extractComponents } from './parser'; -const globalThemeVariables = extractVariables('./src/v2/styles/_global-theme-variables.scss'); -const globalLayoutVariables = extractVariables('./src/v2/styles/_global-layout-variables.scss'); -const componentVariables = extractVariables('./src/v2/**/*-@(theme|layout).scss', globalThemeVariables); -const paletteVariables = extractVariables('./src/v2/styles/_palette-variables.scss'); +const componentInfos = extractComponents('./src/v2/**/*-@(theme|layout).scss'); -const globalThemeVariablesOutput = getGlobalVariablesOutput(globalThemeVariables, 'theme'); -const globalLayoutVariablesOutput = getGlobalVariablesOutput(globalLayoutVariables, 'layout'); -const componentVariablesOutput = getComponentVariablesOutput(componentVariables); -const paletteVariablesOutput = getPaletteVariablesOutput(paletteVariables); +const globalThemeVariablesOutput = getGlobalVariablesOutput('theme'); +const globalLayoutVariablesOutput = getGlobalVariablesOutput('layout'); +const componentVariablesOutput = getComponentVariablesOutput(componentInfos); const updateAutogeneratedSlot = ( templateFilePath: string, @@ -23,13 +19,6 @@ const updateAutogeneratedSlot = ( fs.writeFileSync(targetFilePath, targetContent, 'utf8'); }; -updateAutogeneratedSlot( - `./doc-templates/palette-variables.template.mdx`, - `[//]: # '#SLOT-autogenerated-palette-variables'`, - paletteVariablesOutput, - `./docs/theming/palette-variables.mdx`, -); - updateAutogeneratedSlot( `./doc-templates/global-variables.template.mdx`, `[//]: # '#SLOT-autogenerated-theme-variables'`, @@ -44,7 +33,6 @@ updateAutogeneratedSlot( `./docs/theming/global-variables.mdx`, ); - updateAutogeneratedSlot( `./doc-templates/component-variables.template.mdx`, `[//]: # '#SLOT-autogenerated-component-variables'`, diff --git a/scripts/output.ts b/scripts/output.ts index 17a08631..9212008d 100644 --- a/scripts/output.ts +++ b/scripts/output.ts @@ -1,193 +1,38 @@ import dedent from 'dedent'; import prettier from 'prettier'; -import type { VariableGroup, VariableInfo } from './parser'; +import type { ComponentInfo } from './parser'; import * as packagejson from '../package.json'; -type Column = { - name: string; - key: keyof VariableInfo | 'usedIn'; - type: 'code' | 'text' | 'values'; -}; -type Group = { name: string; regexp: RegExp; columns: Column[]; definedIn?: Function }; - -const row = (v: VariableInfo, group: Group) => { - const usedIn = [...v.referencedIn].map(componentThemeLink).join(', '); - const info = { ...v, usedIn }; - return dedent`${group.columns.map((c) => `| ${getColumn(c, info)}`).join('')}|`; -}; - -const getColumn = (column: Column, info: VariableInfo & { usedIn?: string }) => { - if (column.type === 'values') { - return getValuesColumn(info); - } else { - return getTextOrCodeColumn(column, info); - } -}; - -const getValuesColumn = (info: VariableInfo & { usedIn?: string }) => { - return `${info.values - .map((v) => ``) - .join('')}
\`${v.scope}\`
\`${v.value}\`
`; -}; - -const getTextOrCodeColumn = (column: Column, info: VariableInfo & { usedIn?: string }) => { - return `${column.type === 'code' ? '`' : ''}${info[column.key] || ''}${ - column.type === 'code' ? '`' : '' - }`; -}; - -export const getGlobalVariablesOutput = ( - data: Map, - type: 'theme' | 'layout', -) => { - const nameColumn: Column = { name: 'Name', key: 'name', type: 'code' }; - const valueColumn: Column = { name: 'Value(s)', key: 'values', type: 'values' }; - const descriptionColumn: Column = { name: 'Description', key: 'description', type: 'text' }; - const usedInColumn: Column = { name: 'Used in', key: 'usedIn', type: 'text' }; - - const groups = [ - { - name: 'Colors', - regexp: /color/, - columns: [nameColumn, valueColumn, descriptionColumn, usedInColumn], - }, - { - name: 'Typography', - regexp: /(__font|-text)/, - columns: [nameColumn, valueColumn, descriptionColumn, usedInColumn], - }, - { name: 'Spacing', regexp: /spacing/, columns: [nameColumn, valueColumn, descriptionColumn] }, - { - name: 'Radius', - regexp: /__border-radius/, - columns: [nameColumn, valueColumn, descriptionColumn, usedInColumn], - }, - { name: 'Others', regexp: /.*/, columns: [nameColumn, valueColumn, descriptionColumn] }, - ]; - - const variablesByGroups: Map = new Map(); - - data.forEach((variable) => { - for (const group of groups) { - if (group.regexp.test(variable.name)) { - if (!variablesByGroups.get(group)) { - variablesByGroups.set(group, []); - } - variablesByGroups.get(group)!.push(row(variable, group)); - break; - } - } - }); - - let output = ''; - groups.forEach((group) => { - if (variablesByGroups.get(group)) { - const header = - group.columns.map((c) => `| ${c.name}`).join('') + - `| \n` + - group.columns.map(() => '|-').join('') + - `|`; - output += dedent` - ### ${group.name} - ${header} - ${variablesByGroups.get(group)!.join('\n')}\n\n`; - } - }); - - output += `All global ${type} variables are defined in: [https://github.com/GetStream/stream-chat-css/tree/v${packagejson.version}/src/v2/styles/_global-${type}-variables.scss](https://github.com/GetStream/stream-chat-css/tree/v${packagejson.version}/src/v2/styles/_global-${type}-variables.scss)\n\n`; +export const getGlobalVariablesOutput = (type: 'theme' | 'layout') => { + const output = `([list of global ${type} variables](https://github.com/GetStream/stream-chat-css/tree/v${packagejson.version}/src/v2/styles/_global-${type}-variables.scss))`; return format(output); }; -export const getComponentVariablesOutput = (data: Map) => { - const nameColumn: Column = { name: 'Name', key: 'name', type: 'code' }; - const valueColumn: Column = { name: 'Value(s)', key: 'values', type: 'values' }; - const descriptionColumn: Column = { name: 'Description', key: 'description', type: 'text' }; - - const subgroupDefinitions = [ - { - name: 'Theme variables', - regexp: /color|border|box-shadow|overlay|background/, - columns: [nameColumn, valueColumn, descriptionColumn], - definedIn: componentThemeLink, - }, - { - name: 'Layout variables', - regexp: /.*/, - columns: [nameColumn, valueColumn, descriptionColumn], - definedIn: componentLayoutLink, - }, - ]; - - const componentsGroups: Map> = new Map(); - data.forEach((v) => { - const variableGroup = v.definedIn; - if (variableGroup) { - let existingVariableGroup = Array.from(componentsGroups.keys()).find( - (g) => g.componentName === variableGroup.componentName, - ); - if (!existingVariableGroup) { - componentsGroups.set(variableGroup, new Map()); - existingVariableGroup = variableGroup; - } - const componentGroup = componentsGroups.get(existingVariableGroup)!; - const subgroup = subgroupDefinitions.find((subgroup) => subgroup.regexp.test(v.name)); - if (subgroup) { - if (!componentGroup.get(subgroup)) { - componentGroup.set(subgroup, []); - } - componentGroup.get(subgroup)?.push(row(v, subgroup)); - } - } - }); - - let output = ''; - Array.from(componentsGroups.entries()) - .sort((e1, e2) => e1[0].componentName.localeCompare(e2[0].componentName)) - .forEach(([variableGroup, variablesBySubgroups]) => { - output += dedent` - ## ${variableGroup.componentName}${ - variableGroup.sdkRestriction - ? ` - Only available in ${variableGroup.sdkRestriction} SDK` - : '' - }\n`; - - subgroupDefinitions.forEach((group) => { - if (variablesBySubgroups.get(group)) { - const header = - group.columns.map((c) => `| ${c.name}`).join('') + - `| \n` + - group.columns.map(() => '|-').join('') + - `|`; - output += dedent` - ### ${group.name} - ${header} - ${variablesBySubgroups.get(group)!.join('\n')}\n - Defined in: ${group.definedIn(variableGroup.componentName)}\n\n`; - } - }); +export const getComponentVariablesOutput = (componentInfos: ComponentInfo[]) => { + const header = `Component name | Variables | \n |-|-| \n`; + let output = header; + componentInfos + .sort((e1, e2) => e1.name.localeCompare(e2.name)) + .forEach((componentInfo) => { + output += dedent`\`${componentInfo.name}\`${ + componentInfo.sdkRestriction ? ` (${componentInfo.sdkRestriction} SDK only)` : '' + } | ${componentInfo.variableTypes + .map((t) => + t === 'theme' + ? componentThemeLink(componentInfo.name) + : componentLayoutLink(componentInfo.name), + ) + .join(', ')}|`; + output += `\n`; }); return format(output); }; -export const getPaletteVariablesOutput = (data: Map) => { - const row = (variableInfo: VariableInfo) => { - return `| \`${variableInfo.name}\` | ${getValuesColumn(variableInfo)} |`; - }; - const rows = Array.from(data.values()).map(row); - - let output = dedent` - | Name | Value(s) | - |------|-------| - ${rows.join('\n')}`; - - return format(output); -}; - const componentThemeLink = (componentName: string) => { const pathInRepo = `https://github.com/GetStream/stream-chat-css/tree/v${packagejson.version}/src/v2/styles/`; - return `[${componentName}](${pathInRepo}#COMP#/#COMP#-theme.scss)`.replaceAll( + return `[theme variables](${pathInRepo}#COMP#/#COMP#-theme.scss)`.replaceAll( '#COMP#', componentName, ); @@ -195,7 +40,7 @@ const componentThemeLink = (componentName: string) => { const componentLayoutLink = (componentName: string) => { const pathInRepo = `https://github.com/GetStream/stream-chat-css/tree/v${packagejson.version}/src/v2/styles/`; - return `[${componentName}](${pathInRepo}#COMP#/#COMP#-layout.scss)`.replaceAll( + return `[layout variables](${pathInRepo}#COMP#/#COMP#-layout.scss)`.replaceAll( '#COMP#', componentName, ); diff --git a/scripts/parser.ts b/scripts/parser.ts index 69e10b98..268c833f 100644 --- a/scripts/parser.ts +++ b/scripts/parser.ts @@ -7,27 +7,18 @@ import Parser, { SyntaxNode } from 'tree-sitter'; import CSS from 'tree-sitter-css'; type SDK = 'Angular' | 'React'; -type ComponentName = string; -export type VariableGroup = { - componentName: ComponentName; - sdkRestriction?: SDK; -}; -export type VariableInfo = { +export type ComponentInfo = { name: string; - values: { scope: string; value: string }[]; - theme?: string; - description?: string; - definedIn?: VariableGroup; - blockComment?: string; - referencedIn: Set; + variableTypes: ('theme' | 'layout')[]; + sdkRestriction?: SDK; }; -export const extractVariables = (fromGlob: string, dependencies?: Map) => { +export const extractComponents = (fromGlob: string) => { const parser = new Parser(); parser.setLanguage(CSS); - const componentVariables = new Map(); + const componentInfos: ComponentInfo[] = []; const files = glob.sync(fromGlob); files.forEach((file) => { const [, componentName] = file.split(/.*\/(.*)-(theme|layout|variables)\.scss$/); @@ -42,83 +33,44 @@ export const extractVariables = (fromGlob: string, dependencies?: Map { const ruleSetComment = extractComment(ruleSet); - let sdkRestriction: SDK | undefined = undefined; if (ruleSetComment) { const matches = ruleSetComment.match(/Angular|React/g); if (matches) { sdkRestriction = matches[0] as SDK; } } - const variableGroup = { componentName, sdkRestriction }; - ruleSet.descendantsOfType('declaration').forEach((node) => { + for (let i = 0; i < ruleSet.descendantsOfType('declaration').length; i++) { + const node = ruleSet.descendantsOfType('declaration')[i]; const identifier = node.text; if (identifier.startsWith('--str-chat')) { - const currentVariable = extractVariableInfo( - node, - variableGroup, - ruleSet.firstChild?.text || '', - ); - const seenVariable = componentVariables.get(currentVariable.name); - if (!seenVariable) { - // we see this variable for a very first time, store it in the map - componentVariables.set(currentVariable.name, currentVariable); - } else { - if (!seenVariable.description && currentVariable.description) { - seenVariable.definedIn = currentVariable.definedIn; - seenVariable.description = currentVariable.description; - } - seenVariable.values.push(...currentVariable.values); - } - } - - if (dependencies) { - const value = node.text; - // matches against multiple var() invocations in the same rule - // e.g.: padding: var(--xs-p) var(--xl-p); - const matches = value.match(/var\(([\w\s,-]+)\)/g); - if (matches) { - matches.forEach((match) => { - // capture the variable name, e.g.: "var(--xl-p)" -> "--xl-p", "var(--xl-p, 8px)" -> "--xl-p" - const [, variable] = match.match(/var\(([\w\s-]+)(\)|,)/)!; - const dependantVariable = dependencies.get(variable.trim()); - dependantVariable?.referencedIn.add(componentName); - }); - } + variableType = file.includes('theme') ? 'theme' : 'layout'; + break; } - }); + } }); - }); - - return componentVariables; -}; -const extractVariableInfo = ( - node: SyntaxNode, - variableGroup: VariableGroup, - scope: string, -): VariableInfo => { - // nodes in format: : ... ; - const [name, _, ...declValues] = node.children; - const value = declValues - .slice(0, -1) // omit ; terminator - .map((n) => n.text.replaceAll('\n', '').trim()) - .join(' ') - .trim(); + if (variableType) { + let componentInfo = componentInfos.find((c) => c.name === componentName); + if (!componentInfo) { + componentInfo = { + name: componentName, + variableTypes: [], + }; + componentInfos.push(componentInfo); + } - const description = extractComment(node); + componentInfo.variableTypes.push(variableType); + componentInfo.variableTypes.sort().reverse(); + componentInfo.sdkRestriction = sdkRestriction; + } + }); - const theme = detectTheme(node); - return { - name: name.text.trim(), - values: [{ scope: scope.replace(/\n/g, ''), value }], - theme, - description, - definedIn: variableGroup, - referencedIn: new Set(), - }; + return componentInfos; }; const extractComment = (node: SyntaxNode) => { @@ -132,20 +84,3 @@ const extractComment = (node: SyntaxNode) => { return undefined; } }; - -const detectTheme = (node: SyntaxNode) => { - let theme = undefined; - let n = node; - do { - if (n.type === 'rule_set' && n.firstChild) { - const selector = n.firstChild.text; - if (selector.includes('str-chat__theme-light')) { - theme = 'light'; - } else if (selector.includes('str-chat__theme-dark')) { - theme = 'dark'; - } - } - n = n.parent!; - } while (n != null && !theme); - return theme; -};