diff --git a/.github/workflows/sync-figma-to-tokens.yml b/.github/workflows/sync-figma-to-tokens.yml index c3b84e66f..394ad53be 100644 --- a/.github/workflows/sync-figma-to-tokens.yml +++ b/.github/workflows/sync-figma-to-tokens.yml @@ -2,15 +2,11 @@ name: Sync Figma variables to tokens on: workflow_dispatch: inputs: - file_key: - description: 'The file key of the Figma file to be updated' - required: true branch: type: choice description: Which branch to update options: - - design-tokens-alpha - - design-tokens + - master jobs: sync-figma-to-tokens: runs-on: ubuntu-latest @@ -32,18 +28,24 @@ jobs: - name: Install dependencies run: yarn install + working-directory: design-tokens - name: Sync variables in Figma file to tokens run: yarn sync-figma-to-tokens -- --output tokens + working-directory: design-tokens env: - FILE_KEY: ${{ github.event.inputs.file_key }} + FILE_KEY_PRIMITIVE: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_PRIMITIVE }} + FILE_KEY_SEMANTIC: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_SEMANTIC }} + FILE_KEY_COMPONENT: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_COMPONENT }} PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_ACTION_VARIABLES_SYNC_FIGMA_TOKEN }} - + - name: Format token files with prettier + run: yarn prettier tokens --write + working-directory: design-tokens - name: Create Pull Request uses: peter-evans/create-pull-request@v5 with: commit-message: Update tokens from Figma title: Update tokens from Figma - body: 'Update tokens from Figma from file: https://www.figma.com/file/${{ github.event.inputs.file_key }}' + body: 'Update tokens from Figma from tokens files' base: ${{ github.event.inputs.branch }} branch: update-tokens diff --git a/.github/workflows/sync-tokens-to-figma.yml b/.github/workflows/sync-tokens-to-figma.yml index 3da198e1a..aeac818ea 100644 --- a/.github/workflows/sync-tokens-to-figma.yml +++ b/.github/workflows/sync-tokens-to-figma.yml @@ -2,7 +2,9 @@ name: Sync tokens to Figma on: push: branches: - - design-tokens + - master + paths: + - design-tokens/** jobs: sync-tokens-to-figma: @@ -11,7 +13,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: '18.16.0' + node-version: '18.18.0' - name: Set NPM version run: npm install -g npm@9.5.1 @@ -20,12 +22,14 @@ jobs: uses: actions/checkout@v3 - name: Install dependencies - run: npm install + run: yarn install working-directory: design-tokens - name: Sync tokens to Figma file - run: npm run sync-tokens-to-figma + run: yarn sync-tokens-to-figma working-directory: design-tokens env: - FILE_KEY: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY }} + FILE_KEY_PRIMITIVE: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_PRIMITIVE }} + FILE_KEY_SEMANTIC: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_SEMANTIC }} + FILE_KEY_COMPONENT: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_COMPONENT }} PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_ACTION_VARIABLES_SYNC_FIGMA_TOKEN }} diff --git a/.github/workflows/testcafe.yml b/.github/workflows/testcafe.yml index 1f16dc8d0..0a38eeba1 100644 --- a/.github/workflows/testcafe.yml +++ b/.github/workflows/testcafe.yml @@ -19,7 +19,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v1 with: - node-version: '18.17.x' + node-version: '18.18.x' - name: Install latest dependencies run: yarn upgrade working-directory: aries-site diff --git a/.github/workflows/update-design-tokens-alpha-stable.yml b/.github/workflows/update-design-tokens-stable.yml similarity index 53% rename from .github/workflows/update-design-tokens-alpha-stable.yml rename to .github/workflows/update-design-tokens-stable.yml index d7907d2e3..58bbfeb7b 100644 --- a/.github/workflows/update-design-tokens-alpha-stable.yml +++ b/.github/workflows/update-design-tokens-stable.yml @@ -1,11 +1,13 @@ -name: Update design-tokens-alpha-stable +name: Update design-tokens-stable on: push: branches: - - design-tokens-alpha + - master + paths: + - design-tokens/** jobs: - update-design-tokens-alpha-stable: + update-design-tokens-stable: runs-on: ubuntu-latest steps: - name: Install Node.js @@ -26,8 +28,7 @@ jobs: - name: Build run: yarn build working-directory: design-tokens - - - name: Update design-tokens-alpha-stable + - name: Update design-tokens-stable run: | git config --global user.name "Grommet Community Bot" git config --global user.email "grommet@hpe.com" @@ -35,3 +36,12 @@ jobs: working-directory: design-tokens env: GH_TOKEN: ${{ secrets.GH_ACTION_ACCESS_TOKEN }} + # If stable is successfully update, push the same updates to Figma + - name: Sync tokens to Figma file + run: yarn sync-tokens-to-figma + working-directory: design-tokens + env: + FILE_KEY_PRIMITIVE: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_PRIMITIVE }} + FILE_KEY_SEMANTIC: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_SEMANTIC }} + FILE_KEY_COMPONENT: ${{ secrets.GH_ACTION_FIGMA_FILE_KEY_COMPONENT }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_ACTION_VARIABLES_SYNC_FIGMA_TOKEN }} diff --git a/.gitignore b/.gitignore index 066fa5337..4060499dd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ *storybook-static */src/data/search +# yalc +.yalc/ +yalc.lock + #logs npm-debug.log* yarn-debug.log* @@ -25,3 +29,5 @@ yarn-error.log* .tmp report* .DS_Store +.native-web +.tailwind-app diff --git a/.husky/pre-commit b/.husky/pre-commit index eba501413..1a6f48220 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,6 +4,11 @@ __dirname="$(CDPATH= cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" __rootDir="$(CDPATH= cd "$($__dirname "${BASH_SOURCE[0]}")" && pwd)" +echo "..Running design token checks" +cd design-tokens +npm run paddingY:verify +npm run build +cd ${__rootDir} echo "..Running aries-site checks" cd aries-site echo "....Linting checks for aries-site" diff --git a/design-tokens-manager/.eslintrc.cjs b/design-tokens-manager/.eslintrc.cjs new file mode 100644 index 000000000..d6c953795 --- /dev/null +++ b/design-tokens-manager/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/design-tokens-manager/.gitignore b/design-tokens-manager/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/design-tokens-manager/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/design-tokens-manager/README.md b/design-tokens-manager/README.md new file mode 100644 index 000000000..ae8860066 --- /dev/null +++ b/design-tokens-manager/README.md @@ -0,0 +1,3 @@ +# HPE Design Tokens Manager + +This is a tool to manage hpe-design-tokens. diff --git a/design-tokens-manager/index.html b/design-tokens-manager/index.html new file mode 100644 index 000000000..e715474e6 --- /dev/null +++ b/design-tokens-manager/index.html @@ -0,0 +1,12 @@ + + + + + + HPE Design Token Manager + + +
+ + + diff --git a/design-tokens-manager/package.json b/design-tokens-manager/package.json new file mode 100644 index 000000000..713b1f3c6 --- /dev/null +++ b/design-tokens-manager/package.json @@ -0,0 +1,33 @@ +{ + "name": "design-tokens-manager", + "private": true, + "authors": [ + "Taylor Seamans" + ], + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "design-tokens": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "styled-components": "5.3.11" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/design-tokens-manager/public/_redirects b/design-tokens-manager/public/_redirects new file mode 100644 index 000000000..7797f7c6a --- /dev/null +++ b/design-tokens-manager/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 diff --git a/design-tokens-manager/src/App.jsx b/design-tokens-manager/src/App.jsx new file mode 100644 index 000000000..c0d8b8f0d --- /dev/null +++ b/design-tokens-manager/src/App.jsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react'; +import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; +import { Grommet, Header, Button, Box, Text } from 'grommet'; +import { hpe } from 'grommet-theme-hpe'; +import { Github, Moon, Sun } from 'grommet-icons'; +import { Visualizer } from './routes/Visualizer'; +// import { Builder } from './routes/Builder'; +import { Docs } from './routes/Docs'; + +function App() { + const [darkMode, setDarkMode] = useState( + localStorage.getItem('darkMode') === 'true' || false, + ); + useEffect(() => { + if (darkMode) localStorage.setItem('darkMode', 'true'); + else localStorage.setItem('darkMode', 'false'); + }, [darkMode]); + + return ( + + +
+ + +
+ + } /> + {/* } /> */} + } /> + +
+
+ ); +} + +export default App; diff --git a/design-tokens-manager/src/build-token-tree.js b/design-tokens-manager/src/build-token-tree.js new file mode 100644 index 000000000..e6ed8c200 --- /dev/null +++ b/design-tokens-manager/src/build-token-tree.js @@ -0,0 +1,72 @@ +export const buildTokenTree = tokens => { + const tree = {}; + + Object.keys(tokens).forEach(collection => { + let mode = collection; + if ( + collection === 'base' || + collection === 'components' || + collection === 'global' + ) { + mode = 'default'; + } else if (collection.includes('elevation')) + mode = collection.replace('elevation', ''); + + const flat = Object.fromEntries( + Object.keys(tokens[collection]).map(token => [ + token, + tokens[collection][token], + ]), + ); + if (!(mode in tree)) tree[mode] = {}; + tree[mode] = { ...tree[mode], ...flat }; + }); + + Object.keys(tree).forEach(mode => { + Object.keys(tree[mode]).forEach(key => { + // if it's a reference, add it to the "usedBy" for the referenced token + const value = tree[mode][key].original.value; + if (/^{.*}$/.test(value)) { + const token = value.slice(1, -1); + // for color tokens, add it to the used by for "light" and "dark" modes + // for dimension tokens, addit to the used by for "large" and "small" + // otherwise, mode is "default" + const referenceModes = []; + if (mode === 'default') { + if (token.includes('component')) referenceModes.push('default'); + if (tree[mode][key].$type === 'color') + referenceModes.push(...['light', 'dark']); + else if (tree[mode][key].$type === 'number') + referenceModes.push(...['large', 'small']); + } else { + referenceModes.push(tree[mode][token] ? mode : 'default'); + } + referenceModes.forEach(referenceMode => { + // if this is the first reference, create `usedBy` + const reference = tree[referenceMode][`hpe.${token}`]; + if (reference) { + if (!('usedBy' in reference)) { + reference.usedBy = [ + { + name: key, + mode, + }, + ]; + } else if ( + !reference.usedBy.find( + ref => ref.name === key && ref.mode === mode, + ) + ) { + reference.usedBy.push({ + name: key, + mode, + }); + } + } + }); + } + }); + }); + + return tree; +}; diff --git a/design-tokens-manager/src/main.jsx b/design-tokens-manager/src/main.jsx new file mode 100644 index 000000000..265cbed70 --- /dev/null +++ b/design-tokens-manager/src/main.jsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.jsx'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +); diff --git a/design-tokens-manager/src/routes/Builder.jsx b/design-tokens-manager/src/routes/Builder.jsx new file mode 100644 index 000000000..9b366dd4d --- /dev/null +++ b/design-tokens-manager/src/routes/Builder.jsx @@ -0,0 +1,286 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { + Heading, + Form, + FormField, + TextInput, + Page, + PageContent, + Grid, + Button, + Box, + Text, + Anchor, + SelectMultiple, + // ToggleGroup, +} from 'grommet'; + +import { + Previous, + Trash, + // Copy, +} from 'grommet-icons'; + +const colors = ['background', 'foregroundColor', 'borderColor']; +const dimensions = [ + 'paddingX', + 'paddingY', + 'borderRadius', + 'borderWidth', + 'minHeight', +]; + +const Builder = () => { + const [value, setValue] = useState({ + prefix: 'hpe', + component: '', + size: ['medium'], + variants: [{ variant: '' }], + state: ['enabled', 'disabled', 'hover', 'selected'], + property: [...dimensions, ...colors], + }); + + // const [view, setView] = useState('compact'); + const [tokens, setTokens] = useState([]); + const [tokenObject, setTokenObject] = useState({}); + + const addVariant = () => { + const newVariant = { variant: '' }; + const newVariants = [...value.variants, newVariant]; + setValue({ + ...value, + variants: newVariants, + }); + }; + + const removeVariant = index => { + if (value.variants && value.variants.length > 0) { + setValue({ + ...value, + variants: value.variants.filter((v, _idx) => _idx !== index), + }); + } + }; + + const handleSubmit = ({ value: formValue }) => { + const nextTokenObj = {}; + const nextTokens = []; + if (!(formValue.component in tokenObject)) + nextTokenObj[formValue.component] = {}; + formValue.size.forEach(size => { + nextTokenObj[formValue.component][size] = {}; + formValue.variants.forEach(variant => { + nextTokenObj[formValue.component][size][variant.variant] = {}; + formValue.state.forEach(state => { + nextTokenObj[formValue.component][size][variant.variant][state] = {}; + dimensions.forEach(property => { + nextTokenObj[formValue.component][size][variant.variant][state][ + property + ] = { + $type: 'number', + $value: + variant.variant === 'default' && state === 'enabled' + ? 0 + : `{${formValue.component}.${size}.${ + variant.variant ? `default.` : '' + }enabled.${property}}`, + }; + + nextTokens.push( + `${formValue.prefix}.${formValue.component}.${size}.${ + variant.variant ? `${variant.variant}.` : '' + }${state}.${property}`, + ); + }); + }); + }); + }); + + formValue.variants.forEach(variant => { + nextTokenObj[formValue.component][variant.variant] = {}; + formValue.state.forEach(state => { + nextTokenObj[formValue.component][variant.variant][state] = {}; + colors.forEach(property => { + nextTokenObj[formValue.component][variant.variant][state][property] = + { + $type: 'color', + $value: + variant.variant === 'default' && state === 'enabled' + ? '#FFF' + : `{${formValue.component}.${ + variant.variant + ? `${state === 'enabled' ? 'default' : variant}.` + : '' + }enabled.${property}}`, + }; + nextTokens.push( + `${formValue.prefix}.${formValue.component}.${ + variant.variant ? `${variant.variant}.` : '' + }${state}.${property}`, + ); + }); + }); + }); + setTokens(nextTokens); + setTokenObject(nextTokenObj); + }; + + let VariantGroup = null; + if (value.variants !== undefined) { + VariantGroup = value.variants.map((phone, index) => ( + + + + + + + +