Repository: react95-io/React95 Branch: master Commit: 871d5335daeb Files: 246 Total size: 466.6 KB Directory structure: gitextract_rz85rgj9/ ├── .babelrc ├── .codesandbox/ │ └── ci.json ├── .editorconfig ├── .eslintrc.js ├── .firebaserc ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── .storybook/ │ ├── decorators/ │ │ └── withGlobalStyle.tsx │ ├── main.ts │ ├── manager.css │ ├── manager.ts │ ├── preview.ts │ ├── theme-picker/ │ │ ├── ThemeButton.tsx │ │ ├── ThemeList.tsx │ │ ├── ThemeProvider.tsx │ │ ├── constants.ts │ │ └── register.ts │ └── theme.js ├── LICENSE ├── README.md ├── docs/ │ ├── Contributing.stories.mdx │ ├── Getting-Started.stories.mdx │ ├── Submit-your-Project.stories.mdx │ └── Welcome.stories.mdx ├── firebase.json ├── jest.config.js ├── package.json ├── rollup.config.js ├── src/ │ ├── Anchor/ │ │ ├── Anchor.spec.tsx │ │ ├── Anchor.stories.tsx │ │ └── Anchor.tsx │ ├── AppBar/ │ │ ├── AppBar.spec.tsx │ │ ├── AppBar.stories.tsx │ │ └── AppBar.tsx │ ├── Avatar/ │ │ ├── Avatar.spec.tsx │ │ ├── Avatar.stories.tsx │ │ └── Avatar.tsx │ ├── Button/ │ │ ├── Button.spec.tsx │ │ ├── Button.stories.tsx │ │ └── Button.tsx │ ├── Checkbox/ │ │ ├── Checkbox.spec.tsx │ │ ├── Checkbox.stories.tsx │ │ └── Checkbox.tsx │ ├── ColorInput/ │ │ ├── ColorInput.spec.tsx │ │ ├── ColorInput.stories.tsx │ │ └── ColorInput.tsx │ ├── Counter/ │ │ ├── Counter.spec.tsx │ │ ├── Counter.stories.tsx │ │ ├── Counter.tsx │ │ └── Digit.tsx │ ├── DatePicker/ │ │ ├── DatePicker.stories.tsx │ │ └── DatePicker.tsx │ ├── Frame/ │ │ ├── Frame.spec.tsx │ │ ├── Frame.stories.tsx │ │ └── Frame.tsx │ ├── GroupBox/ │ │ ├── GroupBox.spec.tsx │ │ ├── GroupBox.stories.tsx │ │ └── GroupBox.tsx │ ├── Handle/ │ │ ├── Handle.spec.tsx │ │ ├── Handle.stories.tsx │ │ └── Handle.tsx │ ├── Hourglass/ │ │ ├── Hourglass.spec.tsx │ │ ├── Hourglass.stories.tsx │ │ ├── Hourglass.tsx │ │ └── base64hourglass.tsx │ ├── MenuList/ │ │ ├── MenuList.spec.tsx │ │ ├── MenuList.stories.tsx │ │ ├── MenuList.tsx │ │ ├── MenuListItem.spec.tsx │ │ └── MenuListItem.tsx │ ├── Monitor/ │ │ ├── Monitor.spec.tsx │ │ ├── Monitor.stories.tsx │ │ └── Monitor.tsx │ ├── NumberInput/ │ │ ├── NumberInput.spec.tsx │ │ ├── NumberInput.stories.tsx │ │ └── NumberInput.tsx │ ├── ProgressBar/ │ │ ├── ProgressBar.spec.tsx │ │ ├── ProgressBar.stories.tsx │ │ └── ProgressBar.tsx │ ├── Radio/ │ │ ├── Radio.spec.tsx │ │ ├── Radio.stories.tsx │ │ └── Radio.tsx │ ├── ScrollView/ │ │ ├── ScrollView.spec.tsx │ │ ├── ScrollView.stories.tsx │ │ └── ScrollView.tsx │ ├── Select/ │ │ ├── Select.spec.tsx │ │ ├── Select.stories.data.ts │ │ ├── Select.stories.tsx │ │ ├── Select.styles.tsx │ │ ├── Select.tsx │ │ ├── Select.types.ts │ │ ├── SelectNative.spec.tsx │ │ ├── SelectNative.tsx │ │ ├── useSelectCommon.tsx │ │ └── useSelectState.ts │ ├── Separator/ │ │ ├── Separator.spec.tsx │ │ ├── Separator.stories.tsx │ │ └── Separator.tsx │ ├── Slider/ │ │ ├── Slider.spec.tsx │ │ ├── Slider.stories.tsx │ │ └── Slider.tsx │ ├── Table/ │ │ ├── Table.spec.tsx │ │ ├── Table.stories.tsx │ │ ├── Table.tsx │ │ ├── TableBody.spec.tsx │ │ ├── TableBody.tsx │ │ ├── TableDataCell.spec.tsx │ │ ├── TableDataCell.tsx │ │ ├── TableHead.spec.tsx │ │ ├── TableHead.tsx │ │ ├── TableHeadCell.spec.tsx │ │ ├── TableHeadCell.tsx │ │ ├── TableRow.spec.tsx │ │ └── TableRow.tsx │ ├── Tabs/ │ │ ├── Tab.spec.tsx │ │ ├── Tab.tsx │ │ ├── TabBody.spec.tsx │ │ ├── TabBody.tsx │ │ ├── Tabs.spec.tsx │ │ ├── Tabs.stories.tsx │ │ └── Tabs.tsx │ ├── TextInput/ │ │ ├── TextInput.spec.tsx │ │ ├── TextInput.stories.tsx │ │ └── TextInput.tsx │ ├── Toolbar/ │ │ ├── Toolbar.spec.tsx │ │ └── Toolbar.tsx │ ├── Tooltip/ │ │ ├── Tooltip.spec.tsx │ │ ├── Tooltip.stories.tsx │ │ └── Tooltip.tsx │ ├── TreeView/ │ │ ├── TreeView.spec.tsx │ │ ├── TreeView.stories.tsx │ │ └── TreeView.tsx │ ├── Window/ │ │ ├── Window.spec.tsx │ │ ├── Window.stories.tsx │ │ ├── Window.tsx │ │ ├── WindowContent.spec.tsx │ │ ├── WindowContent.tsx │ │ ├── WindowHeader.spec.tsx │ │ └── WindowHeader.tsx │ ├── assets/ │ │ ├── fonts/ │ │ │ └── src/ │ │ │ ├── ms-sans-serif/ │ │ │ │ ├── license.txt │ │ │ │ └── readme.txt │ │ │ └── ms-sans-serif-bold/ │ │ │ ├── license.txt │ │ │ └── readme.txt │ │ └── images/ │ │ └── logo.psd │ ├── common/ │ │ ├── SwitchBase.ts │ │ ├── constants.ts │ │ ├── hooks/ │ │ │ ├── useControlledOrUncontrolled.ts │ │ │ ├── useEventCallback.ts │ │ │ ├── useForkRef.spec.tsx │ │ │ ├── useForkRef.ts │ │ │ ├── useId.spec.ts │ │ │ ├── useId.ts │ │ │ └── useIsFocusVisible.ts │ │ ├── index.ts │ │ ├── styleReset.ts │ │ ├── system.ts │ │ ├── themes/ │ │ │ ├── aiee.ts │ │ │ ├── ash.ts │ │ │ ├── azureOrange.ts │ │ │ ├── bee.ts │ │ │ ├── blackAndWhite.ts │ │ │ ├── blue.ts │ │ │ ├── brick.ts │ │ │ ├── candy.ts │ │ │ ├── cherry.ts │ │ │ ├── coldGray.ts │ │ │ ├── counterStrike.ts │ │ │ ├── darkTeal.ts │ │ │ ├── denim.ts │ │ │ ├── eggplant.ts │ │ │ ├── fxDev.ts │ │ │ ├── highContrast.ts │ │ │ ├── honey.ts │ │ │ ├── hotChocolate.ts │ │ │ ├── hotdogStand.ts │ │ │ ├── index.ts │ │ │ ├── lilac.ts │ │ │ ├── lilacRoseDark.ts │ │ │ ├── maple.ts │ │ │ ├── marine.ts │ │ │ ├── matrix.ts │ │ │ ├── millenium.ts │ │ │ ├── modernDark.ts │ │ │ ├── molecule.ts │ │ │ ├── monochrome.ts │ │ │ ├── ninjaTurtles.ts │ │ │ ├── olive.ts │ │ │ ├── original.ts │ │ │ ├── pamelaAnderson.ts │ │ │ ├── peggysPastels.ts │ │ │ ├── plum.ts │ │ │ ├── polarized.ts │ │ │ ├── powerShell.ts │ │ │ ├── rainyDay.ts │ │ │ ├── raspberry.ts │ │ │ ├── redWine.ts │ │ │ ├── rose.ts │ │ │ ├── seawater.ts │ │ │ ├── shelbiTeal.ts │ │ │ ├── slate.ts │ │ │ ├── solarizedDark.ts │ │ │ ├── solarizedLight.ts │ │ │ ├── spruce.ts │ │ │ ├── stormClouds.ts │ │ │ ├── theSixtiesUSA.ts │ │ │ ├── tokyoDark.ts │ │ │ ├── toner.ts │ │ │ ├── tooSexy.ts │ │ │ ├── travel.ts │ │ │ ├── types.ts │ │ │ ├── vaporTeal.ts │ │ │ ├── vermillion.ts │ │ │ ├── violetDark.ts │ │ │ ├── vistaesqueMidnight.ts │ │ │ ├── water.ts │ │ │ ├── white.ts │ │ │ ├── windows1.ts │ │ │ └── wmii.ts │ │ └── utils/ │ │ ├── events.spec.tsx │ │ ├── events.ts │ │ ├── index.spec.ts │ │ └── index.ts │ ├── index.ts │ ├── legacy/ │ │ ├── Bar.tsx │ │ ├── Cutout.tsx │ │ ├── Desktop.tsx │ │ ├── Divider.tsx │ │ ├── Fieldset.tsx │ │ ├── List.tsx │ │ ├── ListItem.tsx │ │ ├── NumberField.tsx │ │ ├── Panel.tsx │ │ ├── Progress.tsx │ │ ├── TextField.tsx │ │ └── Tree.tsx │ └── types.ts ├── test/ │ ├── setup-test.ts │ └── utils.tsx ├── tsconfig.build.index.json ├── tsconfig.build.themes.json ├── tsconfig.json └── types/ ├── globals.d.ts └── themes.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "sourceType": "unambiguous", "presets": [ [ "@babel/preset-env", { "shippedProposals": true, "loose": true } ], "@babel/preset-typescript" ], "plugins": [ "@babel/plugin-transform-shorthand-properties", "@babel/plugin-transform-block-scoping", [ "@babel/plugin-proposal-decorators", { "legacy": true } ], [ "@babel/plugin-proposal-class-properties", { "loose": true } ], [ "@babel/plugin-proposal-private-property-in-object", { "loose": true } ], [ "@babel/plugin-proposal-private-methods", { "loose": true } ], "@babel/plugin-proposal-export-default-from", "@babel/plugin-syntax-dynamic-import", [ "@babel/plugin-proposal-object-rest-spread", { "loose": true, "useBuiltIns": true } ], "@babel/plugin-transform-classes", "@babel/plugin-transform-arrow-functions", "@babel/plugin-transform-parameters", "@babel/plugin-transform-destructuring", "@babel/plugin-transform-spread", "@babel/plugin-transform-for-of", "babel-plugin-macros", "@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-nullish-coalescing-operator" ] } ================================================ FILE: .codesandbox/ci.json ================================================ { "buildCommand": "build:prod", "node": "16", "sandboxes": [ "react95-template-xkfj0" ] } ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = yes insert_final_newline = yes ================================================ FILE: .eslintrc.js ================================================ module.exports = { extends: [ 'plugin:@typescript-eslint/recommended', 'airbnb', 'plugin:prettier/recommended', 'plugin:react-hooks/recommended' ], parser: '@typescript-eslint/parser', plugins: ['react', 'prettier'], env: { browser: true, es6: true, jest: true }, rules: { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_\\d*$' } ], 'import/extensions': ['error', { js: 'never', ts: 'never', tsx: 'never' }], 'import/no-unresolved': [ 'error', // TODO: Remove ../../test/utils when TypeScript migration is complete { ignore: ['react95', '../../test/utils'] } ], 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 'import/prefer-default-export': 'off', 'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }], 'jsx-a11y/label-has-for': 'off', 'no-nested-ternary': 'off', 'prettier/prettier': 'error', 'react/forbid-prop-types': 'off', 'react/jsx-filename-extension': [ 'warn', { extensions: ['.js', '.jsx', '.tsx'] } ], 'react/jsx-props-no-spreading': 'off', 'react/no-array-index-key': 'off', 'react/prop-types': 'off', 'react/require-default-props': 'off', 'react/static-property-placement': ['error', 'static public field'] }, overrides: [ { files: ['*.spec.@(js|jsx|ts|tsx)', '*.stories.@(js|jsx|ts|tsx)'], rules: { 'no-console': 'off' } }, { files: ['*.@(ts|tsx)'], rules: { // This is handled by @typescript-eslint/no-unused-vars 'no-undef': 'off' } } ], settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'] }, 'import/resolver': { typescript: {} } } }; ================================================ FILE: .firebaserc ================================================ { "projects": { "default": "react95-storybook" } } ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: arturbien patreon: arturbien open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel custom: https://www.paypal.me/react95 ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: jobs: lint: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Lint run: yarn run lint type-check: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Type Check run: yarn run typescript test: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Test run: yarn run test:ci build-library: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Build library run: yarn run build build-storybook: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Build Storybook run: yarn run build:storybook ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master - next - beta - alpha - '*.x' # maintenance releases jobs: release-library: runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Build library run: yarn run build - name: Deploy library run: yarn run semantic-release || true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} release-storybook: needs: - release-library if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - name: Git Checkout uses: actions/checkout@v2 - name: Setup node uses: actions/setup-node@v3 with: node-version: 16 - name: Cache packages uses: actions/cache@v3 with: key: node_modules-v4-${{ hashFiles('yarn.lock') }} path: |- node_modules */node_modules restore-keys: 'node_modules-v4-' - name: Yarn install run: yarn install --ignore-optional --frozen-lockfile - name: Build Storybook run: yarn run build:storybook - name: Deploy Storybook run: ./node_modules/.bin/firebase deploy --token=$FIREBASE_TOKEN env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # library build /cjs /esm /themes /images /fonts /dist # storybook /storybook /.firebase # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # JetBrains IDEs .idea/* ================================================ FILE: .prettierrc ================================================ { "arrowParens": "avoid", "bracketSpacing": true, "htmlWhitespaceSensitivity": "css", "insertPragma": false, "jsxBracketSameLine": false, "jsxSingleQuote": true, "printWidth": 80, "proseWrap": "preserve", "quoteProps": "as-needed", "requirePragma": false, "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "none", "useTabs": false } ================================================ FILE: .storybook/decorators/withGlobalStyle.tsx ================================================ import { DecoratorFn } from '@storybook/react'; import React from 'react'; import { createGlobalStyle } from 'styled-components'; import ms_sans_serif from '../../src/assets/fonts/dist/ms_sans_serif.woff2'; import ms_sans_serif_bold from '../../src/assets/fonts/dist/ms_sans_serif_bold.woff2'; import styleReset from '../../src/common/styleReset'; const GlobalStyle = createGlobalStyle` ${styleReset} @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif}') format('woff2'); font-weight: 400; font-style: normal } @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif_bold}') format("woff2"); font-weight: bold; font-style: normal } html, body, #root { height: 100%; } #root > * { height: 100%; box-sizing: border-box; } body { font-family: 'ms_sans_serif', 'sans-serif'; } `; export const withGlobalStyle: DecoratorFn = story => ( <> {story()} ); ================================================ FILE: .storybook/main.ts ================================================ import type { StorybookConfig } from '@storybook/react/types'; import type { PropItem } from 'react-docgen-typescript'; const path = require('path'); const storybookConfig: StorybookConfig = { stories: ['../@(docs|src)/**/*.stories.@(tsx|mdx)'], addons: [ { name: '@storybook/addon-docs', options: { sourceLoaderOptions: { injectStoryParameters: false } } }, '@storybook/addon-storysource', './theme-picker/register.ts' ], core: { builder: 'webpack5' }, features: { babelModeV7: true, storyStoreV7: true, modernInlineRender: true, postcss: false }, typescript: { check: false, checkOptions: {}, reactDocgen: 'react-docgen-typescript', reactDocgenTypescriptOptions: { shouldExtractLiteralValuesFromEnum: true, propFilter: (prop: PropItem) => prop.parent ? !/node_modules/.test(prop.parent.fileName) : true } }, webpackFinal: config => { config.resolve = { ...config.resolve, alias: { ...config.resolve?.alias, react95: path.resolve(__dirname, '../src/index') } }; return config; } }; module.exports = storybookConfig; ================================================ FILE: .storybook/manager.css ================================================ /* Remove from the sidebar menu stories that contains "unstable" */ a[data-item-id$='-unstable'].sidebar-item, a[data-item-id*='-unstable-'].sidebar-item, button[data-item-id$='-unstable'].sidebar-item, button[data-item-id$='-unstable-'].sidebar-item { display: none !important; } ================================================ FILE: .storybook/manager.ts ================================================ import './manager.css'; import { addons } from '@storybook/addons'; import theme from './theme'; addons.setConfig({ theme }); ================================================ FILE: .storybook/preview.ts ================================================ import { DecoratorFn, Parameters } from '@storybook/react'; import { withGlobalStyle } from './decorators/withGlobalStyle'; import { withThemesProvider } from './theme-picker/ThemeProvider'; export const decorators: DecoratorFn[] = [withGlobalStyle, withThemesProvider]; export const parameters: Parameters = { layout: 'fullscreen', options: { storySort: { order: [ 'Docs', [ 'Welcome to React95', 'Getting Started', 'Contributing', 'Submit your Project' ], 'Controls', 'Environment', 'Layout', 'Typography', 'Other' ] } } }; ================================================ FILE: .storybook/theme-picker/ThemeButton.tsx ================================================ import React, { useCallback } from 'react'; import { ThemeProvider } from 'styled-components'; import { Button } from '../../src/Button/Button'; import { Theme } from '../../src/types'; export function ThemeButton({ active, onChoose, theme }: { active: boolean; onChoose: (themeName: string) => void; theme: Theme; }) { const handleClick = useCallback(() => { onChoose(theme.name); }, []); return ( ); } ================================================ FILE: .storybook/theme-picker/ThemeList.tsx ================================================ import { useAddonState } from '@storybook/api'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import themes from '../../src/common/themes'; import { Theme } from '../../src/types'; import { THEMES_ID } from './constants'; import { ThemeButton } from './ThemeButton'; const { original, rainyDay, vaporTeal, theSixtiesUSA, olive, tokyoDark, rose, plum, matrix, travel, ...otherThemes } = themes; const themeList = [ original, rainyDay, vaporTeal, theSixtiesUSA, olive, tokyoDark, rose, plum, matrix, travel, ...Object.values(otherThemes) ]; type ThemesProps = { active?: boolean; }; const Wrapper = styled.div<{ theme: Theme }>` display: grid; padding: 1em; gap: 1em; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); grid-template-rows: repeat(auto-fill, 40px); background-color: ${({ theme }) => theme.material}; `; export function ThemeList({ active }: ThemesProps) { const [themeName, setThemeName] = useAddonState(THEMES_ID, 'original'); const handleChoose = useCallback( (newThemeName: string) => { setThemeName(newThemeName); }, [setThemeName] ); if (!active) { return <>; } return ( {themeList.map(theme => ( ))} ); } ================================================ FILE: .storybook/theme-picker/ThemeProvider.tsx ================================================ import { useAddonState } from '@storybook/client-api'; import { DecoratorFn } from '@storybook/react'; import React from 'react'; import { ThemeProvider } from 'styled-components'; import themes from '../../src/common/themes/index'; import { THEMES_ID } from './constants'; export const withThemesProvider: DecoratorFn = story => { const [themeName] = useAddonState(THEMES_ID, 'original'); return ( {story()} ); }; ================================================ FILE: .storybook/theme-picker/constants.ts ================================================ export const THEMES_ID = 'storybook/themes'; ================================================ FILE: .storybook/theme-picker/register.ts ================================================ import addons, { makeDecorator, types } from '@storybook/addons'; import { THEMES_ID } from './constants'; import { ThemeList } from './ThemeList'; addons.register(THEMES_ID, () => { addons.addPanel(`${THEMES_ID}/panel`, { title: 'Themes', type: types.PANEL, render: ThemeList }); }); export default makeDecorator({ name: 'withThemesProvider', parameterName: 'theme', wrapper: (getStory, context) => getStory(context) }); ================================================ FILE: .storybook/theme.js ================================================ import { create } from '@storybook/theming'; import brandImage from './logo.png'; export default create({ base: 'light', brandTitle: 'React95', brandUrl: 'https://react95.io', brandImage, brandTarget: '_self', // UI appBg: '#dfdfdf', appContentBg: '#ffffff', appBorderColor: '#848584', appBorderRadius: 0, // Typography fontBase: '"ms_sans_serif", sans-serif', fontCode: 'monospace', // Text colors textColor: '#0a0a0a', textInverseColor: 'rgba(255,255,255,0.9)', // Toolbar default and active colors barTextColor: '#c6c6c6', barSelectedColor: '#fefefe', barBg: '#060084', // Form colors inputBg: '#ffffff', inputBorder: '#dfdfdf', inputTextColor: '#0a0a0a', inputBorderRadius: 0 }); ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Artur Bień Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

React95

NPM release status React95 version React95 license React95 license

Components - Demo app - React Native - Slack - PayPal donation 💰

Refreshed Windows95 UI components for your modern React apps.
Built with styled-components 💅

![hero](https://user-images.githubusercontent.com/28541613/81947711-28b05580-9601-11ea-964a-c3a6de998496.png) ### Support - [Become a backer or sponsor on Patreon](https://www.patreon.com/arturbien) - [One-time donation via PayPal](https://www.paypal.me/react95) ## Getting Started First, install component library and styled-components in your project directory: ```sh # yarn $ yarn add react95 styled-components # npm $ npm install react95 styled-components ``` Apply style reset, wrap your app with ThemeProvider with theme of your choice... and you are ready to go! 🚀 ```jsx import React from 'react'; import { createGlobalStyle, ThemeProvider } from 'styled-components'; import { MenuList, MenuListItem, Separator, styleReset } from 'react95'; // pick a theme of your choice import original from 'react95/dist/themes/original'; // original Windows95 font (optionally) import ms_sans_serif from 'react95/dist/fonts/ms_sans_serif.woff2'; import ms_sans_serif_bold from 'react95/dist/fonts/ms_sans_serif_bold.woff2'; const GlobalStyles = createGlobalStyle` ${styleReset} @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif}') format('woff2'); font-weight: 400; font-style: normal } @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif_bold}') format('woff2'); font-weight: bold; font-style: normal } body { font-family: 'ms_sans_serif'; } `; const App = () => (
🎤 Sing 💃🏻 Dance 😴 Sleep
); export default App; ``` ### Submit your project Apps built with React95 will be featured on the official React95 [website](https://react95.io) 🤟🏻 ### Contributing Any help from UI / UX designers would be EXTREMELY appreciated. The challenge is to come up with new component designs / layouts that are broadly used in modern UIs, that weren't present back in 95. If you want to help with the project, feel free to open pull requests and submit issues or component proposals. Let's bring this UI back to life ♥️ ================================================ FILE: docs/Contributing.stories.mdx ================================================ import { Meta } from '@storybook/addon-docs'; # Contributing Any help from UI/UX designers would be EXTREMELY appreciated. The challenge is to come up with new component designs/layouts that are broadly used in modern UIs, that weren't present back in 95. If you want to help with the project, feel free to [open pull requests][1], [submit issues or component proposals][2] and join our [Slack channels][3]! Let's bring this UI back to life ♥️ [1]: https://github.com/arturbien/react95/pulls [2]: https://github.com/arturbien/React95/issues [3]: https://join.slack.com/t/react95/shared_invite/enQtOTA1NzEyNjAyNTc4LWYxZjU3NWRiMWJlMGJiMjhkNzE2MDA3ZmZjZDc1YmY0ODdlZjMwZDA1NWJiYWExYmY1NTJmNmE4OWVjNWFhMTE ================================================ FILE: docs/Getting-Started.stories.mdx ================================================ import { Meta } from '@storybook/addon-docs'; # Installation React95 is available as an [npm package](https://www.npmjs.com/package/react95). ## npm To install and save your `package.json` dependencies, run: ```sh # yarn yarn add react95 styled-components # npm npm install -S react95 styled-components ``` In order to have `react95` working properly, you'll also need [styled-components 💅](https://github.com/styled-components/styled-components), this way you can use custom themes and get the best of the library 🙂 ## Usage Apply style reset, wrap your app content with ThemeProvider with theme of your choice... and you are ready to go! 🚀 ```jsx import React from 'react'; import { MenuList, MenuListItem, Separator, styleReset } from 'react95'; import { createGlobalStyle, ThemeProvider } from 'styled-components'; /* Pick a theme of your choice */ import original from 'react95/dist/themes/original'; /* Original Windows95 font (optional) */ import ms_sans_serif from 'react95/dist/fonts/ms_sans_serif.woff2'; import ms_sans_serif_bold from 'react95/dist/fonts/ms_sans_serif_bold.woff2'; const GlobalStyles = createGlobalStyle` ${styleReset} @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif}') format('woff2'); font-weight: 400; font-style: normal } @font-face { font-family: 'ms_sans_serif'; src: url('${ms_sans_serif_bold}') format('woff2'); font-weight: bold; font-style: normal } body, input, select, textarea { font-family: 'ms_sans_serif'; } `; const App = () => (
🎤 Sing 💃🏻 Dance 😴 Sleep
); export default App; ``` ================================================ FILE: docs/Submit-your-Project.stories.mdx ================================================ import { Meta } from '@storybook/addon-docs'; # Submit your Project Apps built with React95 will be featured on the official React95 [website](https://react95.io) 🤟🏻 In order to submit your project, just drop a comment on [this issue](https://github.com/arturbien/React95/issues/25)! ================================================ FILE: docs/Welcome.stories.mdx ================================================ import { Meta } from '@storybook/addon-docs'; # Welcome to React95 NPM   React95 version   React95 license

Components  -  Demo app  -  Website  -  Slack  -  PayPal donation 💰

**Refreshed** Windows 95 UI components for your modern React apps. Built with [styled-components](https://github.com/styled-components/styled-components) 💅. ![hero](https://user-images.githubusercontent.com/28541613/81947711-28b05580-9601-11ea-964a-c3a6de998496.png) ### Getting Started Check out our [getting started](?path=/story/docs-getting-started--page) docs! ### Motivation Create modern mobile/web applications with the retro and old school Windows 95 style. Our goal is not to exactly recreate Windows95 components, but to provide a solid component library for current scenarios. ### Support - [Become a backer or sponsor on Patreon](https://www.patreon.com/arturbien) - [One-time donation via PayPal](https://www.paypal.me/react95) ================================================ FILE: firebase.json ================================================ { "hosting": { "public": "storybook", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } ================================================ FILE: jest.config.js ================================================ module.exports = { globals: { 'ts-jest': { diagnostics: false, isolatedModules: true } }, coverageReporters: ['text', 'html'], preset: 'ts-jest/presets/default-esm', setupFilesAfterEnv: ['/test/setup-test.ts'], testEnvironment: 'jsdom' }; ================================================ FILE: package.json ================================================ { "name": "react95", "version": "0.0.0-development", "description": "Refreshed Windows95 UI components for modern web apps - React95", "keywords": [ "react", "styled-components", "windows95", "components", "vaporwave" ], "author": "Artur Bień (https://www.linkedin.com/in/arturbien/)", "funding": [ { "type": "paypal", "url": "https://www.paypal.me/react95" }, { "type": "patreon", "url": "https://www.patreon.com/arturbien" } ], "license": "MIT", "repository": "git@github.com:arturbien/React95.git", "homepage": "https://react95.io", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "files": [ "/dist" ], "publishConfig": { "access": "public" }, "scripts": { "start": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9009 --no-open", "build:storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true build-storybook -o ./storybook", "build": "rm -rf dist && yarn run build:prod", "build:dev": "cross-env NODE_ENV=development rollup -c", "build:prod": "cross-env NODE_ENV=production rollup -c", "test": "jest ./src", "test:ci": "jest ./src --maxWorkers=2", "test:watch": "jest ./src --watch", "test:coverage": "jest ./src --coverage", "typescript": "tsc --noEmit", "lint": "eslint --ext .js,.ts,.tsx src", "lint:fix": "yarn run lint --fix", "semantic-release": "semantic-release", "cz": "git-cz" }, "peerDependencies": { "react": ">= 16.8.0", "react-dom": ">= 16.8.0", "styled-components": ">= 5.3.3" }, "devDependencies": { "@babel/core": "^7.18.9", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.18.10", "@babel/plugin-proposal-export-default-from": "^7.18.10", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.18.9", "@babel/plugin-proposal-optional-chaining": "^7.18.9", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.18.6", "@babel/plugin-transform-block-scoping": "^7.18.9", "@babel/plugin-transform-classes": "^7.18.9", "@babel/plugin-transform-destructuring": "^7.18.9", "@babel/plugin-transform-for-of": "^7.18.8", "@babel/plugin-transform-parameters": "^7.18.8", "@babel/plugin-transform-shorthand-properties": "^7.18.6", "@babel/plugin-transform-spread": "^7.18.9", "@babel/preset-env": "^7.18.10", "@babel/preset-typescript": "^7.18.6", "@rollup/plugin-typescript": "^8.3.4", "@storybook/addon-docs": "6.5.10", "@storybook/addon-storysource": "6.5.10", "@storybook/builder-webpack5": "^6.5.10", "@storybook/manager-webpack5": "^6.5.10", "@storybook/react": "6.5.10", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", "@types/jest": "^28.1.6", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@types/styled-components": "^5.1.25", "@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/parser": "^5.32.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-polyfill-corejs3": "^0.5.3", "commitizen": "^4.2.5", "cross-env": "^7.0.3", "cz-conventional-changelog": "^3.3.0", "esbuild": "^0.14.53", "eslint": "^8.21.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^3.4.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "firebase-tools": "^11.4.2", "husky": "^8.0.1", "jest": "^28.1.3", "jest-environment-jsdom": "^28.1.3", "jest-styled-components": "^7.0.8", "lint-staged": "^13.0.3", "prettier": "^2.7.1", "react": "^17.0.2", "react-docgen-typescript": "^2.2.2", "react-dom": "^17.0.2", "rimraf": "^3.0.2", "rollup": "^2.77.2", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-esbuild": "^4.9.1", "rollup-plugin-replace": "^2.2.0", "semantic-release": "^19.0.3", "styled-components": "^5.3.5", "ts-jest": "^28.0.7", "typescript": "^4.7.4", "webpack": "5" }, "dependencies": {}, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.js": [ "eslint --fix", "prettier --write", "git add" ] }, "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" } } } ================================================ FILE: rollup.config.js ================================================ import typescript from '@rollup/plugin-typescript'; import copy from 'rollup-plugin-copy'; import esbuild from 'rollup-plugin-esbuild'; import replace from 'rollup-plugin-replace'; const NODE_ENV = process.env.NODE_ENV || 'development'; const baseBundle = { external: id => !/^[./]/.test(id), plugins: [ replace({ 'process.env.NODE_ENV': JSON.stringify(NODE_ENV) }), esbuild() ] }; export default [ { ...baseBundle, input: ['./src/index.ts', './src/types.ts'], output: [ { dir: 'dist', entryFileNames: '[name].js', exports: 'auto', format: 'cjs', preserveModules: true, preserveModulesRoot: 'src' }, { dir: 'dist', entryFileNames: '[name].mjs', exports: 'auto', format: 'es', preserveModules: true, preserveModulesRoot: 'src' } ], plugins: [ ...baseBundle.plugins, typescript({ tsconfig: './tsconfig.build.index.json', declaration: true, declarationDir: 'dist' }) ] }, { ...baseBundle, input: './src/common/themes/index.ts', output: { dir: 'dist/themes', exports: 'default', format: 'cjs', preserveModules: true, preserveModulesRoot: 'src/common/themes' }, plugins: [ ...baseBundle.plugins, copy({ targets: [ { src: './src/assets/fonts/dist/*', dest: './dist/fonts' }, { src: './src/assets/images/*', dest: './dist/images' } ] }), typescript({ tsconfig: './tsconfig.build.themes.json', declaration: true, declarationDir: 'dist/themes' }) ] } ]; ================================================ FILE: src/Anchor/Anchor.spec.tsx ================================================ import React from 'react'; import { render } from '@testing-library/react'; import { Anchor } from './Anchor'; const defaultProps = { children: '', href: '' }; describe('', () => { it('should render href', () => { const { container } = render( ); const anchorEl = container.firstChild; expect(anchorEl).toHaveAttribute('href', 'http://yoda.com'); }); it('should render children', () => { const { container } = render( You shall pass ); const anchorEl = container.firstChild; expect(anchorEl).toHaveTextContent('You shall pass'); }); it('should render custom style', () => { const { container } = render( ); const anchorEl = container.firstChild; expect(anchorEl).toHaveAttribute('style', 'color: papayawhip;'); }); it('should render custom props', () => { const customProps = { target: '_blank' }; const { container } = render(); const anchorEl = container.firstChild; expect(anchorEl).toHaveAttribute('target', '_blank'); }); }); ================================================ FILE: src/Anchor/Anchor.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Anchor } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.material}; `; export default { title: 'Typography/Anchor', component: Anchor, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return (

Everybody likes{' '} https://expensive.toys

); } Default.story = { name: 'default' }; ================================================ FILE: src/Anchor/Anchor.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { CommonStyledProps } from '../types'; type AnchorProps = { children: React.ReactNode; underline?: boolean; } & React.AnchorHTMLAttributes & CommonStyledProps; const StyledAnchor = styled.a<{ underline: boolean }>` color: ${({ theme }) => theme.anchor}; font-size: inherit; text-decoration: ${({ underline }) => (underline ? 'underline' : 'none')}; &:visited { color: ${({ theme }) => theme.anchorVisited}; } `; const Anchor = forwardRef( ({ children, underline = true, ...otherProps }: AnchorProps, ref) => { return ( {children} ); } ); Anchor.displayName = 'Anchor'; export { Anchor, AnchorProps }; ================================================ FILE: src/AppBar/AppBar.spec.tsx ================================================ import { render } from '@testing-library/react'; import React from 'react'; import { AppBar } from './AppBar'; const defaultProps = { children: '' }; describe('', () => { it('should render header', () => { const { container } = render(); const headerEl = container.firstElementChild; expect(headerEl && headerEl.tagName).toBe('HEADER'); }); it('should render children', () => { const { container } = render(A nice app bar); const headerEl = container.firstElementChild; expect(headerEl).toHaveTextContent('A nice app bar'); }); it('should render fixed prop properly', () => { const { container, rerender } = render(); const headerEl = container.firstElementChild; expect(headerEl).toHaveStyleRule('position', 'fixed'); rerender(); expect(headerEl).toHaveStyleRule('position', 'absolute'); }); it('should render position prop properly', () => { const { container } = render( ); const headerEl = container.firstElementChild; expect(headerEl).toHaveStyleRule('position', 'sticky'); }); it('should custom style', () => { const { container } = render( ); const headerEl = container.firstElementChild; expect(headerEl).toHaveAttribute('style', 'background-color: papayawhip;'); }); it('should render custom props', () => { const customProps = { title: 'cool-header' }; const { container } = render(); const headerEl = container.firstElementChild; expect(headerEl).toHaveAttribute('title', 'cool-header'); }); }); ================================================ FILE: src/AppBar/AppBar.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React, { useState } from 'react'; import { AppBar, Button, MenuList, MenuListItem, Separator, TextInput, Toolbar } from 'react95'; import styled from 'styled-components'; import logoIMG from '../assets/images/logo.png'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; `; export default { title: 'Environment/AppBar', component: AppBar, decorators: [story => {story()}] } as ComponentMeta; export function Default() { const [open, setOpen] = useState(false); return (
{open && ( setOpen(false)} > 👨‍💻 Profile 📁 My account 🔙 Logout )}
); } Default.story = { name: 'default' }; ================================================ FILE: src/AppBar/AppBar.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { CSSProperties } from 'styled-components'; import { createBorderStyles, createBoxStyles } from '../common'; import { CommonStyledProps } from '../types'; type AppBarProps = { children: React.ReactNode; /** @deprecated Use `position` instead */ fixed?: boolean; position?: CSSProperties['position']; } & React.HTMLAttributes & CommonStyledProps; const StyledAppBar = styled.header` ${createBorderStyles()}; ${createBoxStyles()}; position: ${props => props.position ?? (props.fixed ? 'fixed' : 'absolute')}; top: 0; right: 0; left: auto; display: flex; flex-direction: column; width: 100%; `; const AppBar = forwardRef( ({ children, fixed = true, position = 'fixed', ...otherProps }, ref) => { return ( {children} ); } ); AppBar.displayName = 'AppBar'; export { AppBar, AppBarProps }; ================================================ FILE: src/Avatar/Avatar.spec.tsx ================================================ import { render } from '@testing-library/react'; import React from 'react'; import { renderWithTheme, theme } from '../../test/utils'; import { Avatar } from './Avatar'; describe('', () => { it('should render component', () => { const { container } = render(); expect(container).toBeInTheDocument(); }); it('should render children', () => { const { container } = render(Avatar children); const avatarEl = container.firstElementChild; expect(avatarEl && avatarEl.innerHTML).toBe('Avatar children'); }); it('should handle border properly', () => { const { container, rerender } = renderWithTheme( ); const avatarEl = container.firstElementChild; expect(avatarEl).toHaveStyleRule( 'border-top', `2px solid ${theme.borderDark}` ); rerender(); expect(avatarEl).not.toHaveStyleRule('border-top', ''); }); it('should handle square properly', () => { const { container, rerender } = render(); const avatarEl = container.firstElementChild; expect(avatarEl).toHaveStyleRule('border-radius', '0'); rerender(); expect(avatarEl).toHaveStyleRule('border-radius', '50%'); }); it('should render with source', async () => { const catGif = 'https://cdn2.thecatapi.com/images/1ac.gif'; const { findByAltText } = render(); const imageEl = (await findByAltText('cat avatar')) as HTMLImageElement; expect(imageEl && imageEl.src).toBe(catGif); }); it('should render source with priority over children', async () => { const catGif = 'https://cdn2.thecatapi.com/images/1ac.gif'; const { queryByText } = render( Cats are cool ); const content = await queryByText(/cats are cool/i); expect(content).toBeNull(); }); describe('prop: size', () => { it('should set proper size', () => { const { container } = renderWithTheme(); const avatarEl = container.firstElementChild; expect(avatarEl).toHaveStyleRule('width', '85%'); expect(avatarEl).toHaveStyleRule('height', '85%'); }); it('when passed a number, sets size in px', () => { const { container } = renderWithTheme(); const avatarEl = container.firstElementChild; expect(avatarEl).toHaveStyleRule('width', '25px'); expect(avatarEl).toHaveStyleRule('height', '25px'); }); }); }); ================================================ FILE: src/Avatar/Avatar.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Avatar } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.material}; & > div > * { margin-right: 1rem; } `; export default { title: 'Other/Avatar', component: Avatar, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return (
AK 🚀
); } Default.story = { name: 'default' }; ================================================ FILE: src/Avatar/Avatar.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { getSize } from '../common/utils'; import { CommonStyledProps } from '../types'; type AvatarProps = { alt?: string; children?: React.ReactNode; noBorder?: boolean; size?: string | number; square?: boolean; src?: string; } & React.HTMLAttributes & CommonStyledProps; const StyledAvatar = styled.div< Pick & { size?: string } >` display: inline-block; box-sizing: border-box; object-fit: contain; ${({ size }) => ` height: ${size}; width: ${size}; `} border-radius: ${({ square }) => (square ? 0 : '50%')}; overflow: hidden; ${({ noBorder, theme }) => !noBorder && ` border-top: 2px solid ${theme.borderDark}; border-left: 2px solid ${theme.borderDark}; border-bottom: 2px solid ${theme.borderLightest}; border-right: 2px solid ${theme.borderLightest}; background: ${theme.material}; `} ${({ src }) => !src && ` display: flex; align-items: center; justify-content: space-around; font-weight: bold; font-size: 1rem; `} `; const StyledAvatarImg = styled.img` display: block; object-fit: contain; width: 100%; height: 100%; `; const Avatar = forwardRef( ( { alt = '', children, noBorder = false, size = 35, square = false, src, ...otherProps }, ref ) => { return ( {src ? : children} ); } ); Avatar.displayName = 'Avatar'; export { Avatar, AvatarProps }; ================================================ FILE: src/Button/Button.spec.tsx ================================================ import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { renderWithTheme, theme } from '../../test/utils'; import { blockSizes } from '../common/system'; import { Button } from './Button'; const defaultProps = { children: 'click me' }; describe('





); } Default.story = { name: 'default' }; export function Raised() { return (






); } Raised.story = { name: 'raised' }; export function Flat() { return (

When you want to use Buttons on a light background (like scrollable content), just use the flat variant:

); } Flat.story = { name: 'flat' }; const imageSrc = 'https://image.freepik.com/foto-gratuito/la-frutta-fresca-del-kiwi-tagliata-a-meta-con-la-decorazione-completa-del-pezzo-e-bella-sulla-tavola-di-legno_47436-1.jpg'; export function Thin() { const [open, setOpen] = useState(false); return ( 🥝 Kiwi.app
{open && ( setOpen(false)} > Copy link Facebook Twitter Instagram MySpace )}
kiwi
); } Thin.story = { name: 'thin' }; ================================================ FILE: src/Button/Button.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { createBorderStyles, createBoxStyles, createDisabledTextStyles, createFlatBoxStyles, createHatchedBackground, focusOutline } from '../common'; import { blockSizes } from '../common/system'; import { noOp } from '../common/utils'; import { CommonStyledProps, Sizes } from '../types'; type ButtonProps = { active?: boolean; children?: React.ReactNode; disabled?: boolean; fullWidth?: boolean; onClick?: React.ButtonHTMLAttributes['onClick']; onTouchStart?: React.ButtonHTMLAttributes['onTouchStart']; primary?: boolean; size?: Sizes; square?: boolean; type?: string; } & ( | { variant?: 'default' | 'raised' | 'flat' | 'thin'; } | { /** @deprecated Use `thin` */ variant?: 'menu'; } ) & Omit< React.ButtonHTMLAttributes, 'disabled' | 'onClick' | 'onTouchStart' | 'type' > & CommonStyledProps; type StyledButtonProps = Pick< ButtonProps, | 'active' | 'disabled' | 'fullWidth' | 'primary' | 'size' | 'square' | 'variant' >; const commonButtonStyles = css` position: relative; display: inline-flex; align-items: center; justify-content: center; height: ${({ size = 'md' }) => blockSizes[size]}; width: ${({ fullWidth, size = 'md', square }) => fullWidth ? '100%' : square ? blockSizes[size] : 'auto'}; padding: ${({ square }) => (square ? 0 : `0 10px`)}; font-size: 1rem; user-select: none; &:active { padding-top: ${({ disabled }) => !disabled && '2px'}; } padding-top: ${({ active, disabled }) => active && !disabled && '2px'}; &:after { content: ''; position: absolute; display: block; top: 0; left: 0; height: 100%; width: 100%; } &:not(:disabled) { cursor: pointer; } font-family: inherit; `; export const StyledButton = styled.button` ${({ active, disabled, primary, theme, variant }) => variant === 'flat' ? css` ${createFlatBoxStyles()} ${primary ? ` border: 2px solid ${theme.checkmark}; outline: 2px solid ${theme.flatDark}; outline-offset: -4px; ` : ` border: 2px solid ${theme.flatDark}; outline: 2px solid transparent; outline-offset: -4px; `} &:focus:after, &:active:after { ${!active && !disabled && focusOutline} outline-offset: -4px; } ` : variant === 'menu' || variant === 'thin' ? css` ${createBoxStyles()}; border: 2px solid transparent; &:hover, &:focus { ${!disabled && !active && createBorderStyles({ style: 'buttonThin' })} } &:active { ${!disabled && createBorderStyles({ style: 'buttonThinPressed' })} } ${active && createBorderStyles({ style: 'buttonThinPressed' })} ${disabled && createDisabledTextStyles()} ` : css` ${createBoxStyles()}; border: none; ${disabled && createDisabledTextStyles()} ${active ? createHatchedBackground({ mainColor: theme.material, secondaryColor: theme.borderLightest }) : ''} &:before { box-sizing: border-box; content: ''; position: absolute; ${primary ? css` left: 2px; top: 2px; width: calc(100% - 4px); height: calc(100% - 4px); outline: 2px solid ${theme.borderDarkest}; ` : css` left: 0; top: 0; width: 100%; height: 100%; `} ${active ? createBorderStyles({ style: variant === 'raised' ? 'window' : 'button', invert: true }) : createBorderStyles({ style: variant === 'raised' ? 'window' : 'button', invert: false })} } &:active:before { ${!disabled && createBorderStyles({ style: variant === 'raised' ? 'window' : 'button', invert: true })} } &:focus:after, &:active:after { ${!disabled && focusOutline} outline-offset: -8px; } &:active:focus:after, &:active:after { top: ${active ? '0' : '1px'}; } `} ${commonButtonStyles} `; const Button = forwardRef( ( { onClick, disabled = false, children, type = 'button', fullWidth = false, size = 'md', square = false, active = false, onTouchStart = noOp, primary = false, variant = 'default', ...otherProps }, ref ) => { return ( {children} ); } ); Button.displayName = 'Button'; export { Button, ButtonProps }; ================================================ FILE: src/Checkbox/Checkbox.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { Checkbox } from './Checkbox'; describe('', () => { describe('label', () => { it('renders', () => { const labelText = 'Swag'; const { getByLabelText } = renderWithTheme( ); expect(getByLabelText(labelText)).toBeInTheDocument(); }); }); describe('prop: onChange', () => { it('should call onChange when uncontrolled', () => { const handleChange = jest.fn(event => event.target.checked); const { getByRole } = renderWithTheme( ); getByRole('checkbox').click(); expect(handleChange).toHaveBeenCalledTimes(1); // event.target.check is true expect(handleChange.mock.results[0].value).toBe(true); }); it('should call onChange when controlled', () => { const checked = true; const handleChange = jest.fn(event => event.target.checked); const { getByRole } = renderWithTheme( ); getByRole('checkbox').click(); expect(handleChange).toHaveBeenCalledTimes(1); expect(handleChange.mock.results[0].value).toBe(!checked); }); }); describe('prop: disabled', () => { it('should disable checkbox', () => { const handleChange = jest.fn(); const { getByRole } = renderWithTheme( ); const checkbox = getByRole('checkbox'); expect(checkbox).toHaveAttribute('disabled'); checkbox.click(); expect(handleChange).not.toHaveBeenCalled(); }); it('should be overridden by props', () => { const { getByRole, rerender } = renderWithTheme(); rerender(); const checkbox = getByRole('checkbox'); expect(checkbox).not.toHaveAttribute('disabled'); }); }); describe('prop: indeterminate', () => { it('renders indeterminate state', () => { const { getByRole } = renderWithTheme(); const checkbox = getByRole('checkbox'); // don't set native 'indeterminate' attribute because // different browsers treat it differently // instead we're setting 'data-indeterminate' attribute expect(checkbox).toHaveAttribute('data-indeterminate'); expect(checkbox).not.toHaveAttribute('indeterminate'); expect(getByRole('presentation').firstChild).toHaveAttribute( 'data-testid', 'indeterminateIcon' ); }); it('replaces checked icon', () => { const { getByRole, rerender } = renderWithTheme(); expect(getByRole('checkbox')).toHaveAttribute( 'data-indeterminate', 'false' ); rerender(); expect(getByRole('checkbox')).toHaveAttribute( 'data-indeterminate', 'true' ); expect(getByRole('presentation').firstChild).toHaveAttribute( 'data-testid', 'indeterminateIcon' ); }); }); describe('controlled', () => { it('should check the checkbox', () => { const { getByRole, rerender } = renderWithTheme( ); rerender(); const checkbox = getByRole('checkbox') as HTMLInputElement; expect(checkbox.checked).toBe(true); expect(getByRole('checkbox')).toHaveAttribute('checked'); expect(getByRole('presentation').firstChild).toHaveAttribute( 'data-testid', 'checkmarkIcon' ); }); it('should uncheck the checkbox', () => { const { getByRole, rerender } = renderWithTheme(); rerender(); const checkbox = getByRole('checkbox') as HTMLInputElement; expect(checkbox.checked).toBe(false); expect(getByRole('checkbox')).not.toHaveAttribute('checked'); expect(getByRole('presentation').firstChild).toBeNull(); // check if proper icon was rendered }); }); describe('uncontrolled', () => { it('can change checked state uncontrolled starting from defaultChecked', () => { const { getByRole } = renderWithTheme(); const checkbox = getByRole('checkbox') as HTMLInputElement; expect(checkbox.checked).toBe(true); checkbox.click(); expect(checkbox.checked).toBe(false); checkbox.click(); expect(checkbox.checked).toBe(true); }); }); }); ================================================ FILE: src/Checkbox/Checkbox.stories.tsx ================================================ import React, { useState } from 'react'; import styled from 'styled-components'; import { ComponentMeta } from '@storybook/react'; import { Checkbox, GroupBox, ScrollView } from 'react95'; const Wrapper = styled.div` background: ${({ theme }) => theme.material}; padding: 5rem; #cutout { background: ${({ theme }) => theme.canvas}; padding: 1rem; width: 250px; display: flex; align-items: center; justify-content: space-around; } `; export default { title: 'Controls/Checkbox', component: Checkbox, decorators: [story => {story()}] } as ComponentMeta; export function Default() { const [state, setState] = useState({ cheese: true, bacon: false, broccoli: false }); const { cheese, bacon, broccoli } = state; const ingredientsArr = Object.values(state).map(val => (val ? 1 : 0)); const possibleIngredients = Object.keys(state).length; const chosenIngredients = ingredientsArr.reduce((a, b) => a + b, 0); const isIndeterminate = ![0, possibleIngredients].includes(chosenIngredients); const toggleAll = () => { console.log(ingredientsArr); if (isIndeterminate) { setState({ cheese: true, bacon: true, broccoli: true }); } else if (ingredientsArr[0] === 1) { setState({ cheese: false, bacon: false, broccoli: false }); } else { setState({ cheese: true, bacon: true, broccoli: true }); } }; const toggleIngredient = (e: React.ChangeEvent) => { const value = e.target.value as 'cheese' | 'bacon' | 'broccoli'; setState({ ...state, [value]: !state[value] }); }; return (


); } Default.story = { name: 'default' }; export function Flat() { const [state, setState] = useState({ cheese: true, bacon: false, broccoli: false }); const { cheese, bacon, broccoli } = state; const ingredientsArr = Object.values(state).map(val => (val ? 1 : 0)); const possibleIngredients = Object.keys(state).length; const chosenIngredients = ingredientsArr.reduce((a, b) => a + b, 0); const isIndeterminate = ![0, possibleIngredients].includes(chosenIngredients); const toggleAll = () => { console.log(ingredientsArr); if (isIndeterminate) { setState({ cheese: true, bacon: true, broccoli: true }); } else if (ingredientsArr[0] === 1) { setState({ cheese: false, bacon: false, broccoli: false }); } else { setState({ cheese: true, bacon: true, broccoli: true }); } }; const toggleIngredient = (e: React.ChangeEvent) => { const value = e.target.value as 'cheese' | 'bacon' | 'broccoli'; setState({ ...state, [value]: !state[value] }); }; return (


); } Flat.story = { name: 'flat' }; ================================================ FILE: src/Checkbox/Checkbox.tsx ================================================ import React, { forwardRef, useCallback } from 'react'; import styled, { css } from 'styled-components'; import { createHatchedBackground } from '../common'; import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled'; import { LabelText, size, StyledInput, StyledLabel } from '../common/SwitchBase'; import { noOp } from '../common/utils'; import { StyledScrollView } from '../ScrollView/ScrollView'; import { CommonThemeProps } from '../types'; type CheckboxProps = { checked?: boolean; className?: string; defaultChecked?: boolean; disabled?: boolean; indeterminate?: boolean; label?: number | string; name?: string; onChange?: React.ChangeEventHandler; style?: React.CSSProperties; value?: number | string; variant?: 'default' | 'flat'; } & Omit< React.InputHTMLAttributes, | 'checked' | 'className' | 'defaultChecked' | 'disabled' | 'label' | 'name' | 'onChange' | 'style' | 'value' >; type CheckmarkProps = { $disabled: boolean; variant: 'default' | 'flat'; }; const sharedCheckboxStyles = css` width: ${size}px; height: ${size}px; display: flex; align-items: center; justify-content: space-around; margin-right: 0.5rem; `; const StyledCheckbox = styled(StyledScrollView)` ${sharedCheckboxStyles} width: ${size}px; height: ${size}px; background: ${({ $disabled, theme }) => $disabled ? theme.material : theme.canvas}; &:before { box-shadow: none; } `; const StyledFlatCheckbox = styled.div` position: relative; box-sizing: border-box; display: inline-block; background: ${({ $disabled, theme }) => $disabled ? theme.flatLight : theme.canvas}; ${sharedCheckboxStyles} width: ${size - 4}px; height: ${size - 4}px; outline: none; border: 2px solid ${({ theme }) => theme.flatDark}; background: ${({ $disabled, theme }) => $disabled ? theme.flatLight : theme.canvas}; `; const CheckmarkIcon = styled.span.attrs(() => ({ 'data-testid': 'checkmarkIcon' }))` display: inline-block; position: relative; width: 100%; height: 100%; &:after { content: ''; display: block; position: absolute; left: 50%; top: calc(50% - 1px); width: 3px; height: 7px; border: solid ${({ $disabled, theme }) => $disabled ? theme.checkmarkDisabled : theme.checkmark}; border-width: 0 3px 3px 0; transform: translate(-50%, -50%) rotate(45deg); border-color: ${p => p.$disabled ? p.theme.checkmarkDisabled : p.theme.checkmark}; } `; const IndeterminateIcon = styled.span.attrs(() => ({ 'data-testid': 'indeterminateIcon' }))` display: inline-block; position: relative; width: 100%; height: 100%; &:after { content: ''; display: block; width: 100%; height: 100%; ${({ $disabled, theme }) => createHatchedBackground({ mainColor: $disabled ? theme.checkmarkDisabled : theme.checkmark })} background-position: 0px 0px, 2px 2px; } `; const CheckboxComponents = { flat: StyledFlatCheckbox, default: StyledCheckbox }; const Checkbox = forwardRef( ( { checked, className = '', defaultChecked = false, disabled = false, indeterminate = false, label = '', onChange = noOp, style = {}, value, variant = 'default', ...otherProps }, ref ) => { const [state, setState] = useControlledOrUncontrolled({ defaultValue: defaultChecked, onChange, readOnly: otherProps.readOnly ?? disabled, value: checked }); const handleChange = useCallback( (e: React.ChangeEvent) => { const newState = e.target.checked; setState(newState); onChange(e); }, [onChange, setState] ); const CheckboxComponent = CheckboxComponents[variant]; let Icon = null; if (indeterminate) { Icon = IndeterminateIcon; } else if (state) { Icon = CheckmarkIcon; } return ( {Icon && } {label && {label}} ); } ); Checkbox.displayName = 'Checkbox'; export { Checkbox, CheckboxProps }; ================================================ FILE: src/ColorInput/ColorInput.spec.tsx ================================================ import { fireEvent } from '@testing-library/react'; import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { ColorInput } from './ColorInput'; function rgb2hex(str: string) { const rgb = str.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); function hex(x: string) { return `0${parseInt(x, 10).toString(16)}`.slice(-2); } return rgb ? `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}` : ''; } describe('', () => { it('should call handlers', () => { const color = '#f0f0dd'; const onChange = jest.fn(); const { container } = renderWithTheme(); const input = container.querySelector(`[type="color"]`) as HTMLInputElement; fireEvent.change(input, { target: { value: color } }); expect(onChange).toBeCalledTimes(1); }); it('should properly pass value to input element', () => { const color = '#f0f0dd'; const { container } = renderWithTheme(); const input = container.querySelector(`[type="color"]`) as HTMLInputElement; expect(input.value).toBe(color); }); it('should display current color', () => { const color = '#f0f0dd'; const { getByRole } = renderWithTheme(); const colorPreview = getByRole('presentation'); const displayedColor = window.getComputedStyle( colorPreview, null ).background; const displayedColorHex = rgb2hex(displayedColor); expect(displayedColorHex).toBe(color); }); describe('prop: disabled', () => { it('should render disabled input', () => { const { container } = renderWithTheme(); const input = container.querySelector(`[type="color"]`); expect(input).toHaveAttribute('disabled'); }); it('should be overridden by props', () => { const { container, rerender } = renderWithTheme(); rerender(); const input = container.querySelector(`[type="color"]`); expect(input).not.toHaveAttribute('disabled'); }); }); }); ================================================ FILE: src/ColorInput/ColorInput.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import styled from 'styled-components'; import { ColorInput, ScrollView } from 'react95'; const Wrapper = styled.div` background: ${({ theme }) => theme.material}; padding: 5rem; & > span { margin-left: 1rem; margin-right: 0.5rem; } #cutout { background: ${({ theme }) => theme.canvas}; display: inline-block; } .content { padding: 1rem; & > div { margin: 1rem 0; } & > div > span { margin-right: 0.5rem; } } `; export default { title: 'Controls/ColorInput', component: ColorInput, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return ( <> enabled: disabled: ); } Default.story = { name: 'default' }; export function Flat() { return (
enabled:
disabled:
); } Flat.story = { name: 'flat' }; ================================================ FILE: src/ColorInput/ColorInput.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { StyledButton } from '../Button/Button'; import { focusOutline } from '../common'; import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled'; import { noOp } from '../common/utils'; import { Separator } from '../Separator/Separator'; import { CommonStyledProps } from '../types'; type ColorInputProps = { defaultValue?: string; disabled?: boolean; onChange?: React.ChangeEventHandler; value?: string; variant?: 'default' | 'flat'; } & Omit< React.InputHTMLAttributes, 'defaultValue' | 'disabled' | 'onChange' | 'value' > & CommonStyledProps; const Trigger = styled(StyledButton)` padding-left: 8px; `; const StyledSeparator = styled(Separator)` height: 21px; position: relative; top: 0; `; export const StyledColorInput = styled.input` box-sizing: border-box; width: 100%; height: 100%; position: absolute; left: 0; top: 0; opacity: 0; z-index: 1; cursor: pointer; &:disabled { cursor: default; } `; // TODO replace with SVG icon const ColorPreview = styled.div<{ color: string; $disabled: boolean; }>` box-sizing: border-box; height: 19px; display: inline-block; width: 35px; margin-right: 5px; background: ${({ color }) => color}; ${({ $disabled }) => $disabled ? css` border: 2px solid ${({ theme }) => theme.materialTextDisabled}; filter: drop-shadow( 1px 1px 0px ${({ theme }) => theme.materialTextDisabledShadow} ); ` : css` border: 2px solid ${({ theme }) => theme.materialText}; `} ${StyledColorInput}:focus:not(:active) + &:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; ${focusOutline} outline-offset: -8px; } `; const ChevronIcon = styled.span< Required> & { $disabled: boolean; } >` width: 0px; height: 0px; border-left: 6px solid transparent; border-right: 6px solid transparent; display: inline-block; margin-left: 6px; ${({ $disabled }) => $disabled ? css` border-top: 6px solid ${({ theme }) => theme.materialTextDisabled}; filter: drop-shadow( 1px 1px 0px ${({ theme }) => theme.materialTextDisabledShadow} ); ` : css` border-top: 6px solid ${({ theme }) => theme.materialText}; `} &:after { content: ''; box-sizing: border-box; position: absolute; top: ${({ variant }) => (variant === 'flat' ? '6px' : '8px')}; right: 8px; width: 16px; height: 19px; } `; // TODO make sure all aria and role attributes are in place const ColorInput = forwardRef( ( { value, defaultValue, onChange = noOp, disabled = false, variant = 'default', ...otherProps }, ref ) => { const [valueDerived, setValueState] = useControlledOrUncontrolled({ defaultValue, onChange, readOnly: otherProps.readOnly ?? disabled, value }); const handleChange = (e: React.ChangeEvent) => { const color = e.target.value; setValueState(color); onChange(e); }; return ( // we only need button styles, so we display // it as a div and reset type attribute {variant === 'default' && } ); } ); ColorInput.displayName = 'ColorInput'; export { ColorInput, ColorInputProps }; ================================================ FILE: src/Counter/Counter.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { Counter } from './Counter'; describe('', () => { it('should render', () => { const { container } = renderWithTheme(); const counter = container.firstElementChild; expect(counter).toBeInTheDocument(); }); it('should handle custom style', () => { const { container } = renderWithTheme( ); const counter = container.firstElementChild; expect(counter).toHaveAttribute('style', 'background-color: papayawhip;'); }); it('should handle custom props', () => { const customProps: React.HTMLAttributes = { title: 'potatoe' }; const { container } = renderWithTheme(); const counter = container.firstElementChild; expect(counter).toHaveAttribute('title', 'potatoe'); }); describe('prop: minLength', () => { it('renders correct number of digits', () => { const { container } = renderWithTheme( ); const counter = container.firstElementChild; expect(counter && counter.childElementCount).toBe(7); }); it('value length takes priority if bigger than minLength', () => { const { container } = renderWithTheme( ); const counter = container.firstElementChild; expect(counter && counter.childElementCount).toBe(4); }); }); }); ================================================ FILE: src/Counter/Counter.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React, { useState } from 'react'; import { Button, Counter, Frame } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; .counter-wrapper { display: flex; margin-top: 1rem; } .counter-wrapper button { margin-left: 0.5rem; height: 51px; } .wrapper { padding: 1rem; } `; export default { title: 'Other/Counter', component: Counter, decorators: [story => {story()}] } as ComponentMeta; export function Default() { const [count, setCount] = useState(13); const handleClick = () => setCount(count + 1); return (
); } Default.story = { name: 'default' }; ================================================ FILE: src/Counter/Counter.tsx ================================================ import React, { forwardRef, useMemo } from 'react'; import styled from 'styled-components'; import { createBorderStyles } from '../common'; import { CommonStyledProps, Sizes } from '../types'; import { Digit } from './Digit'; type CounterProps = { minLength?: number; size?: Sizes | 'xl'; value?: number; } & React.HTMLAttributes & CommonStyledProps; const CounterWrapper = styled.div` ${createBorderStyles({ style: 'status' })} display: inline-flex; background: #000000; `; const pixelSizes = { sm: 1, md: 2, lg: 3, xl: 4 }; const Counter = forwardRef( ({ value = 0, minLength = 3, size = 'md', ...otherProps }, ref) => { const digits = useMemo( () => value.toString().padStart(minLength, '0').split(''), [minLength, value] ); return ( {digits.map((digit, i) => ( ))} ); } ); Counter.displayName = 'Counter'; export { Counter, CounterProps }; ================================================ FILE: src/Counter/Digit.tsx ================================================ import React from 'react'; import styled, { css } from 'styled-components'; import { createHatchedBackground } from '../common'; import { CommonStyledProps } from '../types'; type DigitProps = { pixelSize?: number; digit?: number | string; } & React.HTMLAttributes & CommonStyledProps; const DigitWrapper = styled.div>>` position: relative; --react95-digit-primary-color: #ff0102; --react95-digit-secondary-color: #740201; --react95-digit-bg-color: #000000; ${({ pixelSize }) => css` width: ${11 * pixelSize}px; height: ${21 * pixelSize}px; margin: ${pixelSize}px; span, span:before, span:after { box-sizing: border-box; display: inline-block; position: absolute; } span.active, span.active:before, span.active:after { background: var(--react95-digit-primary-color); } span:not(.active), span:not(.active):before, span:not(.active):after { ${createHatchedBackground({ mainColor: 'var(--react95-digit-bg-color)', secondaryColor: 'var(--react95-digit-secondary-color)', pixelSize })} } span.horizontal, span.horizontal:before, span.horizontal:after { height: ${pixelSize}px; border-left: ${pixelSize}px solid var(--react95-digit-bg-color); border-right: ${pixelSize}px solid var(--react95-digit-bg-color); } span.horizontal.active, span.horizontal.active:before, span.horizontal.active:after { height: ${pixelSize}px; border-left: ${pixelSize}px solid var(--react95-digit-primary-color); border-right: ${pixelSize}px solid var(--react95-digit-primary-color); } span.horizontal { left: ${pixelSize}px; width: ${9 * pixelSize}px; } span.horizontal:before { content: ''; width: 100%; top: ${pixelSize}px; left: ${0}px; } span.horizontal:after { content: ''; width: calc(100% - ${pixelSize * 2}px); top: ${2 * pixelSize}px; left: ${pixelSize}px; } span.horizontal.top { top: 0; } span.horizontal.bottom { bottom: 0; transform: rotateX(180deg); } span.center, span.center:before, span.center:after { height: ${pixelSize}px; border-left: ${pixelSize}px solid var(--react95-digit-bg-color); border-right: ${pixelSize}px solid var(--react95-digit-bg-color); } span.center.active, span.center.active:before, span.center.active:after { border-left: ${pixelSize}px solid var(--react95-digit-primary-color); border-right: ${pixelSize}px solid var(--react95-digit-primary-color); } span.center { top: 50%; transform: translateY(-50%); left: ${pixelSize}px; width: ${9 * pixelSize}px; } span.center:before, span.center:after { content: ''; width: 100%; } span.center:before { top: ${pixelSize}px; } span.center:after { bottom: ${pixelSize}px; } span.vertical, span.vertical:before, span.vertical:after { width: ${pixelSize}px; border-top: ${pixelSize}px solid var(--react95-digit-bg-color); border-bottom: ${pixelSize}px solid var(--react95-digit-bg-color); } span.vertical { height: ${11 * pixelSize}px; } span.vertical.left { left: 0; } span.vertical.right { right: 0; transform: rotateY(180deg); } span.vertical.top { top: 0px; } span.vertical.bottom { bottom: 0px; } span.vertical:before { content: ''; height: 100%; top: ${0}px; left: ${pixelSize}px; } span.vertical:after { content: ''; height: calc(100% - ${pixelSize * 2}px); top: ${pixelSize}px; left: ${pixelSize * 2}px; } `} `; const segments = [ 'horizontal top', 'center', 'horizontal bottom', 'vertical top left', 'vertical top right', 'vertical bottom left', 'vertical bottom right' ]; const digitActiveSegments = [ [1, 0, 1, 1, 1, 1, 1], // 0 [0, 0, 0, 0, 1, 0, 1], // 1 [1, 1, 1, 0, 1, 1, 0], // 2 [1, 1, 1, 0, 1, 0, 1], // 3 [0, 1, 0, 1, 1, 0, 1], // 4 [1, 1, 1, 1, 0, 0, 1], // 5 [1, 1, 1, 1, 0, 1, 1], // 6 [1, 0, 0, 0, 1, 0, 1], // 7 [1, 1, 1, 1, 1, 1, 1], // 8 [1, 1, 1, 1, 1, 0, 1] // 9 ]; function Digit({ digit = 0, pixelSize = 2, ...otherProps }: DigitProps) { const segmentClasses = digitActiveSegments[Number(digit)].map((isActive, i) => isActive ? `${segments[i]} active` : segments[i] ); return ( {segmentClasses.map((className, i) => ( ))} ); } export { Digit, DigitProps }; ================================================ FILE: src/DatePicker/DatePicker.stories.tsx ================================================ /* eslint-disable camelcase, react/jsx-pascal-case */ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { DatePicker__UNSTABLE } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; `; export default { title: 'DatePicker__UNSTABLE', component: DatePicker__UNSTABLE, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return console.log(date)} />; } Default.story = { name: 'default' }; ================================================ FILE: src/DatePicker/DatePicker.tsx ================================================ import React, { forwardRef, useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { Button } from '../Button/Button'; import { NumberInput } from '../NumberInput/NumberInput'; import { ScrollView } from '../ScrollView/ScrollView'; import { Select } from '../Select/Select'; import { Toolbar } from '../Toolbar/Toolbar'; import { Window, WindowContent, WindowHeader } from '../Window/Window'; type DatePickerProps = { className?: string; date?: string; onAccept?: (chosenDate: string) => void; onCancel?: React.MouseEventHandler; shadow?: boolean; }; const Calendar = styled(ScrollView)` width: 234px; margin: 1rem 0; background: ${({ theme }) => theme.canvas}; `; const WeekDays = styled.div` display: flex; background: ${({ theme }) => theme.materialDark}; color: #dfe0e3; `; const Dates = styled.div` display: flex; flex-wrap: wrap; `; const DateItem = styled.div` text-align: center; height: 1.5em; line-height: 1.5em; width: 14.28%; `; const DateItemContent = styled.span<{ active: boolean }>` cursor: pointer; background: ${({ active, theme }) => active ? theme.hoverBackground : 'transparent'}; color: ${({ active, theme }) => active ? theme.canvasTextInvert : theme.canvasText}; &:hover { border: 2px dashed ${({ theme, active }) => (active ? 'none' : theme.materialDark)}; } `; const months = [ { value: 0, label: 'January' }, { value: 1, label: 'February' }, { value: 2, label: 'March' }, { value: 3, label: 'April' }, { value: 4, label: 'May' }, { value: 5, label: 'June' }, { value: 6, label: 'July' }, { value: 7, label: 'August' }, { value: 8, label: 'September' }, { value: 9, label: 'October' }, { value: 10, label: 'November' }, { value: 11, label: 'December' } ]; function daysInMonth(year: number, month: number) { return new Date(year, month + 1, 0).getDate(); } function dayIndex(year: number, month: number, day: number) { return new Date(year, month, day).getDay(); } function convertDateToState(stringDate: string) { const date = new Date(Date.parse(stringDate)); const day = date.getUTCDate(); const month = date.getUTCMonth(); const year = date.getUTCFullYear(); return { day, month, year }; } const DatePicker = forwardRef( ( { className, date: initialDate = new Date().toISOString(), onAccept, onCancel, shadow = true }, ref ) => { const [date, setDate] = useState(() => convertDateToState(initialDate)); const { year, month, day } = date; const handleMonthSelect = useCallback( ({ value: monthSelected }: { value: number }) => { setDate(currentDate => ({ ...currentDate, month: monthSelected })); }, [] ); const handleYearSelect = useCallback((yearSelected: number) => { setDate(currentDate => ({ ...currentDate, year: yearSelected })); }, []); const handleDaySelect = useCallback((daySelected: number) => { setDate(currentDate => ({ ...currentDate, day: daySelected })); }, []); const handleAccept = useCallback(() => { const chosenDate = [date.year, date.month + 1, date.day] .map(part => String(part).padStart(2, '0')) .join('-'); onAccept?.(chosenDate); }, [date.day, date.month, date.year, onAccept]); const dayPickerItems = useMemo(() => { const items: React.ReactNode[] = Array.from({ length: 42 }); const firstDayIndex = dayIndex(year, month, 1); let itemDay = day; const daysNumber = daysInMonth(year, month); itemDay = itemDay < daysNumber ? itemDay : daysNumber; items.forEach((_, i) => { if (i >= firstDayIndex && i < daysNumber + firstDayIndex) { const dayNumber = i - firstDayIndex + 1; items[i] = ( { handleDaySelect(dayNumber); }} > {dayNumber} ); } else { items[i] = ; } }); return items; }, [day, handleDaySelect, month, year]); return ( 📆 Date ', () => { it('should be able to mount the component', () => { const { container } = renderWithTheme( ); const button = screen.getByTestId('select-button'); expect(button).toBeInTheDocument(); // we render styled.button, but as='div' // because it's used only for aesthetic purposes expect(button.tagName).not.toBe('BUTTON'); expect(button.firstChild).toHaveAttribute('data-testid', 'select-icon'); }); it('the trigger is in tab order', () => { renderWithTheme(); }); it('should have an input with [type="hidden"] and string value by default', () => { const { container } = renderWithTheme( ); const trigger = screen.getByRole('button'); fireEvent.focus(trigger); fireEvent.blur(trigger); expect(handleBlur).toHaveBeenCalledTimes(1); }); it('should ignore onBlur when the menu opens', () => { // mousedown calls focus while click opens moving the focus to an item // this means the trigger is blurred immediately const handleBlur = jest.fn(); renderWithTheme( ); const o = screen.getAllByRole('option'); expect(o[0]).toHaveAttribute('data-value', '10'); expect(o[1]).toHaveAttribute('data-value', '20'); }); it('should call onClose when user clicks outside of component', async () => { const handleClose = jest.fn(); renderWithTheme(
); fireEvent.mouseDown(screen.getByRole('button')); expect(screen.getByRole('listbox')).toBeInTheDocument(); fireEvent.mouseDown(screen.getByRole('button')); expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); describe('prop: inputProps', () => { it('should apply additional props to trigger element', () => { renderWithTheme( ); const listbox = screen.getByRole('listbox') as HTMLElement; expect( listbox.getAttribute('style')?.includes('max-height: 220px') ).toBeTruthy(); }); }); describe('prop: onClose, onFocus, onKeyDown, onOpen', () => { it('passes event through', () => { const handler = jest.fn(); renderWithTheme( <> ); fireEvent.mouseDown(screen.getByRole('button')); const option = screen.getAllByRole('option')[1]; fireEvent.mouseEnter(option); fireEvent.click(option); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith(options[1], { fromEvent: expect.anything() }); }); }); describe('prop: readOnly', () => { it('should not trigger any event with readOnly', () => { renderWithTheme( ); const o = screen.getAllByRole('option'); expect(o[0]).not.toHaveAttribute('aria-selected'); expect(o[1]).toHaveAttribute('aria-selected', 'true'); expect(o[2]).not.toHaveAttribute('aria-selected'); }); it('should select only the option that matches the object', () => { const obj1 = { id: 1 }; const obj2 = { id: 2 }; renderWithTheme( ); expect(screen.getByRole('button')).toHaveTextContent('object-label'); }); }); describe('prop: open (controlled)', () => { // TODO add more tests it('should be open when initially true', () => { renderWithTheme(); const trigger = screen.getByRole('button'); // If clicked by the right/middle mouse button, no options list should be opened fireEvent.mouseDown(trigger, { button: 1 }); expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); fireEvent.mouseDown(trigger, { button: 2 }); expect(screen.queryByRole('listbox')).not.toBeInTheDocument(); }); }); describe('prop: formatDisplay', () => { it('should use the prop to render the value', () => { const formatDisplay = (x: SelectOption) => `0b${Number(x.value).toString(2)}`; renderWithTheme( ); expect(ref.current?.node).toHaveProperty('tagName', 'INPUT'); }); it('should be able focus the trigger imperatively', () => { const ref = React.createRef(); renderWithTheme( ); expect(screen.getByRole('button')).toHaveAttribute( 'data-test', 'SelectDisplay' ); }); }); describe('keyboard', () => { it.each(['Space', 'ArrowUp', 'ArrowDown', 'Home', 'End'])( `should open menu when pressed %s key on select`, code => { renderWithTheme( ); const button = screen.getByRole('button'); fireEvent.mouseDown(button); const listbox = screen.getByRole('listbox'); expect(screen.getByRole('option', { name: 'ten' })).toBeInTheDocument(); fireEvent.keyDown(listbox, { code: 'ArrowDown' }); expect(screen.getByRole('option', { name: 'twenty' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'Escape' }); await waitFor(() => { expect(onClose).toHaveBeenCalled(); }); expect(listbox).not.toBeInTheDocument(); expect(button).toHaveFocus(); }); it.each(['Enter', 'Space', 'Tab'])( 'selects the active option by pressing %s, closes menu and maintains focus', async keyCode => { const onClose = jest.fn(); const onKeyDown = jest.fn(); renderWithTheme( // eslint-disable-next-line jsx-a11y/no-static-element-interactions
); const button = screen.getByRole('button'); const eventOptions = [ { code: 'Enter' }, { code: 'Escape' }, { code: 'Tab' }, { code: 'Tab', shiftKey: true } ]; eventOptions.forEach(eventOption => { fireEvent.keyDown(button, eventOption); expect(onKeyDown).toHaveBeenCalledWith( expect.objectContaining({ defaultPrevented: false }) ); }); }); it('passes through keyDown events when modifier keys are pressed', () => { const onKeyDown = jest.fn(); renderWithTheme( // eslint-disable-next-line jsx-a11y/no-static-element-interactions
); const button = screen.getByRole('button'); fireEvent.mouseDown(button); const listbox = screen.getByRole('listbox'); expect(screen.getByRole('option', { name: 'ten' })).toBeInTheDocument(); fireEvent.keyDown(listbox, { code: 'ArrowDown' }); expect(screen.getByRole('option', { name: 'twenty' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'ArrowUp' }); expect(screen.getByRole('option', { name: 'ten' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'End' }); expect(screen.getByRole('option', { name: 'thirty' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'Home' }); expect(screen.getByRole('option', { name: 'ten' })).toHaveFocus(); }); it('cycles through options when pressing the same key (open menu)', async () => { renderWithTheme(); const button = screen.getByRole('button'); fireEvent.focus(button); fireEvent.keyDown(button, { code: 'KeyT' }); fireEvent.keyDown(button, { code: 'KeyT' }); expect(button).toHaveTextContent('twenty'); fireEvent.keyDown(button, { code: 'KeyT' }); expect(button).toHaveTextContent('thirty'); fireEvent.keyDown(button, { code: 'KeyT' }); expect(button).toHaveTextContent('ten'); }); it('switches to search after cycling', async () => { renderWithTheme(); const button = screen.getByRole('button'); fireEvent.mouseDown(button); const listbox = screen.getByRole('listbox'); expect(screen.getByRole('option', { name: 'ten' })).toBeInTheDocument(); fireEvent.keyDown(listbox, { code: 'KeyT' }); expect(screen.getByRole('option', { name: 'ten' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'KeyH' }); expect(screen.getByRole('option', { name: 'thirty' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'KeyT' }); expect(screen.getByRole('option', { name: 'ten' })).toHaveFocus(); fireEvent.keyDown(listbox, { code: 'KeyT' }); expect(screen.getByRole('option', { name: 'twenty' })).toHaveFocus(); }); it('moves to specific option when typing', async () => { renderWithTheme(); const button = screen.getByRole('button'); fireEvent.mouseDown(button); const listbox = screen.getByRole('listbox'); expect(screen.getByRole('option', { name: 'ten' })).toBeInTheDocument(); fireEvent.keyDown(listbox, { code: 'KeyT' }); fireEvent.keyDown(listbox, { code: 'KeyH' }); fireEvent.keyDown(listbox, { code: 'KeyI' }); fireEvent.keyDown(listbox, { code: 'KeyR' }); expect(screen.getByRole('option', { name: 'thirty' })).toHaveFocus(); jest.runAllTimers(); fireEvent.keyDown(listbox, { code: 'KeyT' }); expect(screen.getByRole('option', { name: 'ten' })).toHaveFocus(); jest.useRealTimers(); }); }); describe('accessibility', () => { it('sets aria-expanded="true" when the listbox is displayed', () => { // since we make the rest of the UI inaccessible when open this doesn't // technically matter. This is only here in case we keep the rest accessible renderWithTheme(); expect(screen.getByRole('button')).toHaveAttribute( 'aria-expanded', 'false' ); }); it('indicates that activating the button displays a listbox', () => { renderWithTheme(); expect(screen.getByRole('listbox')).toBeVisible(); }); it('the listbox is focusable', () => { renderWithTheme(); const o = screen.getAllByRole('option'); expect(o[0]).toHaveTextContent('ten'); expect(o[1]).toHaveTextContent('twenty'); }); it('indicates the selected option', () => { renderWithTheme( ); expect(screen.getByRole('button')).not.toHaveAttribute('aria-labelledby'); }); it('is labelled by itself when it has an id which is preferred over name', () => { renderWithTheme( <> Chose first option: ); const triggers = screen.getAllByRole('button'); expect(triggers[0]).toHaveAttribute('aria-labelledby', 'select-1-label'); expect(triggers[1]).toHaveAttribute('aria-labelledby', 'select-2-label'); }); }); }); ================================================ FILE: src/Select/Select.stories.data.ts ================================================ export const PokemonOptions = [ 'Bulbasaur', 'Ivysaur', 'Venusaur', 'Charmander', 'Charmeleon', 'Charizard', 'Squirtle', 'Wartortle', 'Blastoise', 'Caterpie', 'Metapod', 'Butterfree', 'Weedle', 'Kakuna', 'Beedrill', 'Pidgey', 'Pidgeotto', 'Pidgeot', 'Rattata', 'Raticate', 'Spearow', 'Fearow', 'Ekans', 'Arbok', 'Pikachu', 'Raichu', 'Sandshrew', 'Sandslash', 'Nidoran♀', 'Nidorina', 'Nidoqueen', 'Nidoran♂', 'Nidorino', 'Nidoking', 'Clefairy', 'Clefable', 'Vulpix', 'Ninetales', 'Jigglypuff', 'Wigglytuff', 'Zubat', 'Golbat', 'Oddish', 'Gloom', 'Vileplume', 'Paras', 'Parasect', 'Venonat', 'Venomoth', 'Diglett', 'Dugtrio', 'Meowth', 'Persian', 'Psyduck', 'Golduck', 'Mankey', 'Primeape', 'Growlithe', 'Arcanine', 'Poliwag', 'Poliwhirl', 'Poliwrath', 'Abra', 'Kadabra', 'Alakazam', 'Machop', 'Machoke', 'Machamp', 'Bellsprout', 'Weepinbell', 'Victreebel', 'Tentacool', 'Tentacruel', 'Geodude', 'Graveler', 'Golem', 'Ponyta', 'Rapidash', 'Slowpoke', 'Slowbro', 'Magnemite', 'Magneton', 'Farfetch’d', 'Doduo', 'Dodrio', 'Seel', 'Dewgong', 'Grimer', 'Muk', 'Shellder', 'Cloyster', 'Gastly', 'Haunter', 'Gengar', 'Onix', 'Drowzee', 'Hypno', 'Krabby', 'Kingler', 'Voltorb', 'Electrode', 'Exeggcute', 'Exeggutor', 'Cubone', 'Marowak', 'Hitmonlee', 'Hitmonchan', 'Lickitung', 'Koffing', 'Weezing', 'Rhyhorn', 'Rhydon', 'Chansey', 'Tangela', 'Kangaskhan', 'Horsea', 'Seadra', 'Goldeen', 'Seaking', 'Staryu', 'Starmie', 'Mr. Mime', 'Scyther', 'Jynx', 'Electabuzz', 'Magmar', 'Pinsir', 'Tauros', 'Magikarp', 'Gyarados', 'Lapras', 'Ditto', 'Eevee', 'Vaporeon', 'Jolteon', 'Flareon', 'Porygon', 'Omanyte', 'Omastar', 'Kabuto', 'Kabutops', 'Aerodactyl', 'Snorlax', 'Articuno', 'Zapdos', 'Moltres', 'Dratini', 'Dragonair', 'Dragonite', 'Mewtwo', 'Mew' ].map((label, index) => ({ value: index + 1, label })); ================================================ FILE: src/Select/Select.stories.tsx ================================================ /* eslint-disable no-console */ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { GroupBox, ScrollView, Select, SelectNative, Window, WindowContent } from 'react95'; import styled from 'styled-components'; import { PokemonOptions } from './Select.stories.data'; import { SelectOption } from './Select.types'; const options = PokemonOptions; const nativeOptions = options.map(option => ({ ...option, value: String(option.value) })); const Wrapper = styled.div` background: ${({ theme }) => theme.material}; padding: 5rem; fieldset, fieldset { margin-bottom: 2rem; } legend + * { margin-bottom: 1rem; } #default-selects { width: 200px; } #cutout > div { width: 250px; padding: 1rem; background: ${({ theme }) => theme.canvas}; & > p { margin-bottom: 2rem; } } `; const onChange = ( selectedOption: SelectOption, changeOptions: { fromEvent: React.SyntheticEvent | Event } ) => console.log(selectedOption, changeOptions.fromEvent); export default { title: 'Controls/Select', component: Select, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return (
console.log('native blur')} onFocus={() => console.log('native focus')} />
); } Default.story = { name: 'default' }; export function Flat() { return (

When you want to use Select on a light background (like scrollable content), just use the flat variant:

); } Flat.story = { name: 'flat' }; export function CustomDisplayFormatting() { return ( {displayLabel} {DropdownButton} {isEnabled && open && ( {optionsContent} )} ); } /* eslint-disable no-use-before-define */ const Select = forwardRef(SelectInner) as ( props: SelectProps & { ref?: React.ForwardedRef } ) => ReturnType>; /* eslint-enable no-use-before-define */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Select.displayName = 'Select'; export * from './SelectNative'; export { Select, SelectProps }; ================================================ FILE: src/Select/Select.types.ts ================================================ import React from 'react'; import { HTMLDataAttributes } from '../types'; type SelectChangeEventTargetValue = { value: T; name: string | undefined }; export type SelectChangeEvent = | (Omit, 'target'> & { target: Omit< React.ChangeEvent['target'], 'name' | 'value' > & SelectChangeEventTargetValue; }) | (Omit & { target: Omit & SelectChangeEventTargetValue; }); export type SelectOption = { label?: string; value: T; }; export type SelectRef = Pick & { node: HTMLInputElement | null; }; export type SelectVariants = 'default' | 'flat'; export type SelectFormatDisplayCallback = ( option: SelectOption ) => string; export type SelectCommonProps = { 'aria-label'?: string; 'aria-labelledby'?: string; className?: string; defaultValue?: T; disabled?: boolean; name?: string; onChange?: ( selectedOption: SelectOption, options: { fromEvent: Event | React.SyntheticEvent; } ) => void; options?: (SelectOption | null | undefined)[]; readOnly?: boolean; shadow?: boolean; style?: React.CSSProperties; value?: T; variant?: SelectVariants; width?: React.CSSProperties['width']; }; export type SelectInnerProps = { formatDisplay?: SelectFormatDisplayCallback; inputProps?: React.HTMLAttributes & HTMLDataAttributes; /** @deprecated Use `aria-labelledby` instead */ labelId?: string; menuMaxHeight?: string | number; onClose?: (options: { fromEvent: Event | React.SyntheticEvent }) => void; onOpen?: (options: { fromEvent: Event | React.SyntheticEvent }) => void; open?: boolean; } & Pick< React.HTMLAttributes, 'onBlur' | 'onFocus' | 'onKeyDown' | 'onMouseDown' > & SelectCommonProps; ================================================ FILE: src/Select/SelectNative.spec.tsx ================================================ // Bsased on https://github.com/mui-org/material-ui import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { SelectOption } from './Select.types'; import { SelectNative } from './SelectNative'; const options: SelectOption[] = [ { label: 'ten', value: '10' }, { label: 'twenty', value: '20' }, { label: 'thirty', value: '30' } ]; describe('', () => { describe('prop: native', () => { it('renders a {marks && marks.map(m => ( {m.label && ( {m.label} )} ))} ); } ); Slider.displayName = 'Slider'; export { Slider, SliderProps }; ================================================ FILE: src/Table/Table.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { Table } from './Table'; describe('', () => { it('renders Table', () => { const { container } = renderWithTheme(
); const table = container.firstElementChild; expect(table).toBeInTheDocument(); }); it('renders table element', () => { const { getByRole } = renderWithTheme(
); expect(getByRole('table')).toBeInTheDocument(); }); it('renders children', () => { const { getByTestId } = renderWithTheme(
); expect(getByTestId('children')).toBeInTheDocument(); }); }); ================================================ FILE: src/Table/Table.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Table, TableBody, TableDataCell, TableHead, TableHeadCell, TableRow, Window, WindowContent, WindowHeader } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; `; export default { title: 'Controls/Table', component: Table, subcomponents: { Table, TableBody, TableHead, TableRow, TableHeadCell, TableDataCell }, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return ( Pokedex.exe Type Name Level 🌿 Bulbasaur 64 🔥 Charizard 209 Pikachu 82
); } Default.story = { name: 'default' }; ================================================ FILE: src/Table/Table.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { StyledScrollView } from '../ScrollView/ScrollView'; import { CommonStyledProps } from '../types'; type TableProps = { children?: React.ReactNode; } & React.TableHTMLAttributes & CommonStyledProps; const StyledTable = styled.table` display: table; width: 100%; border-collapse: collapse; border-spacing: 0; font-size: 1rem; `; const Wrapper = styled(StyledScrollView)` &:before { box-shadow: none; } `; const Table = forwardRef( ({ children, ...otherProps }, ref) => { return ( {children} ); } ); Table.displayName = 'Table'; export * from './TableBody'; export * from './TableDataCell'; export * from './TableHead'; export * from './TableHeadCell'; export * from './TableRow'; export { Table, TableProps }; ================================================ FILE: src/Table/TableBody.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TableBody } from './TableBody'; describe('', () => { function mountInTable(node: React.ReactNode) { const { container, getByTestId } = renderWithTheme({node}
); return { tbody: container.querySelector('table')?.firstElementChild, getByTestId }; } it('renders TableBody', () => { const { tbody } = mountInTable(); expect(tbody).toBeInTheDocument(); expect(tbody?.tagName).toBe('TBODY'); }); it('renders children', () => { const children = ; const { getByTestId } = mountInTable({children}); expect(getByTestId('tr')).toBeInTheDocument(); }); }); ================================================ FILE: src/Table/TableBody.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { insetShadow } from '../common'; import { CommonStyledProps } from '../types'; type TableBodyProps = { children?: React.ReactNode; } & React.HTMLAttributes & CommonStyledProps; const StyledTableBody = styled.tbody` background: ${({ theme }) => theme.canvas}; display: table-row-group; box-shadow: ${insetShadow}; overflow-y: auto; `; const TableBody = forwardRef( function TableBody({ children, ...otherProps }, ref) { return ( {children} ); } ); TableBody.displayName = 'TableBody'; export { TableBody, TableBodyProps }; ================================================ FILE: src/Table/TableDataCell.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TableDataCell } from './TableDataCell'; describe('', () => { function mountInTable(node: React.ReactNode) { const { container, getByText } = renderWithTheme( {node}
); return { td: container.querySelector('tr')?.firstElementChild, getByText }; } it('renders TableDataCell', () => { const { td } = mountInTable(); expect(td?.tagName).toBe('TD'); }); it('renders children', () => { const { getByText } = mountInTable(children); expect(getByText('children')).toBeInTheDocument(); }); }); ================================================ FILE: src/Table/TableDataCell.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { CommonStyledProps } from '../types'; type TableDataCellProps = { children?: React.ReactNode; } & React.HTMLAttributes & CommonStyledProps; const StyledTd = styled.td` padding: 0 8px; `; const TableDataCell = forwardRef( function TableDataCell({ children, ...otherProps }, ref) { return ( {children} ); } ); TableDataCell.displayName = 'TableDataCell'; export { TableDataCell, TableDataCellProps }; ================================================ FILE: src/Table/TableHead.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TableHead } from './TableHead'; describe('', () => { function mountInTable(node: React.ReactNode) { const { container, getByTestId } = renderWithTheme({node}
); return { tbody: container.querySelector('table')?.firstElementChild, getByTestId }; } it('renders TableHead', () => { const { tbody } = mountInTable(); expect(tbody).toBeInTheDocument(); expect(tbody?.tagName).toBe('THEAD'); }); it('renders children', () => { const children = ; const { getByTestId } = mountInTable({children}); expect(getByTestId('tr')).toBeInTheDocument(); }); }); ================================================ FILE: src/Table/TableHead.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { CommonStyledProps } from '../types'; type TableHeadProps = { children?: React.ReactNode; } & React.HTMLAttributes & CommonStyledProps; const StyledTableHead = styled.thead` display: table-header-group; `; const TableHead = forwardRef( function TableHead({ children, ...otherProps }, ref) { return ( {children} ); } ); TableHead.displayName = 'TableHead'; export { TableHead, TableHeadProps }; ================================================ FILE: src/Table/TableHeadCell.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TableHeadCell } from './TableHeadCell'; describe('', () => { function mountInTable(node: React.ReactNode) { const { container, getByText } = renderWithTheme( {node}
); return { th: container.querySelector('tr')?.firstElementChild as HTMLElement, getByText }; } it('renders TableHeadCell', () => { const { th } = mountInTable(); expect(th?.tagName).toBe('TH'); }); it('renders children', () => { const { getByText } = mountInTable(children); expect(getByText('children')).toBeInTheDocument(); }); describe('prop: sort', () => { it('should render without aria-sort attribute by default', () => { const { th } = mountInTable(); expect(th).not.toHaveAttribute('aria-sort'); }); it('should render aria-sort="ascending" when prop sort="asc" provided', () => { const { th } = mountInTable(); expect(th).toHaveAttribute('aria-sort', 'ascending'); }); it('should render aria-sort="descending" when prop sort="desc" provided', () => { const { th } = mountInTable(); expect(th).toHaveAttribute('aria-sort', 'descending'); }); }); describe('prop: disabled', () => { it('should disable th element', () => { const handleChange = jest.fn(); const { th } = mountInTable( ); expect(th).toHaveAttribute('aria-disabled', 'true'); th?.click?.(); expect(handleChange).not.toHaveBeenCalled(); }); }); }); ================================================ FILE: src/Table/TableHeadCell.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { createBorderStyles, createDisabledTextStyles } from '../common'; import { noOp } from '../common/utils'; import { CommonStyledProps } from '../types'; type TableHeadCellProps = { children?: React.ReactNode; disabled?: boolean; sort?: 'asc' | 'desc' | null; } & React.TdHTMLAttributes & CommonStyledProps; const StyledHeadCell = styled.th<{ $disabled: boolean }>` position: relative; padding: 0 8px; display: table-cell; vertical-align: inherit; background: ${({ theme }) => theme.material}; cursor: default; user-select: none; &:before { box-sizing: border-box; content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; ${createBorderStyles()} border-left: none; border-top: none; } ${({ $disabled }) => !$disabled && css` &:active { &:before { ${createBorderStyles({ invert: true, style: 'window' })} border-left: none; border-top: none; padding-top: 2px; } & > div { position: relative; top: 2px; } } `} color: ${({ theme }) => theme.materialText}; ${({ $disabled }) => $disabled && createDisabledTextStyles()} &:hover { color: ${({ theme }) => theme.materialText}; ${({ $disabled }) => $disabled && createDisabledTextStyles()} } `; const TableHeadCell = forwardRef( function TableHeadCell( { disabled = false, children, onClick, onTouchStart = noOp, sort, ...otherProps }, ref ) { const ariaSort = sort === 'asc' ? 'ascending' : sort === 'desc' ? 'descending' : undefined; return (
{children}
); } ); TableHeadCell.displayName = 'TableHeadCell'; export { TableHeadCell, TableHeadCellProps }; ================================================ FILE: src/Table/TableRow.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TableRow } from './TableRow'; describe('', () => { function mountInTable(node: React.ReactNode) { const { container, getByTestId } = renderWithTheme( {node}
); return { tr: container.querySelector('tbody')?.firstElementChild, getByTestId }; } it('renders TableRow', () => { const { tr } = mountInTable(); expect(tr?.tagName).toBe('TR'); }); it('renders children', () => { const children = ; const { getByTestId } = mountInTable({children}); expect(getByTestId('td')).toBeInTheDocument(); }); }); ================================================ FILE: src/Table/TableRow.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { blockSizes } from '../common/system'; type TableRowProps = { children?: React.ReactNode; } & React.HTMLAttributes; const StyledTr = styled.tr` color: inherit; display: table-row; height: calc(${blockSizes.md} - 2px); line-height: calc(${blockSizes.md} - 2px); vertical-align: middle; outline: none; color: ${({ theme }) => theme.canvasText}; &:hover { background: ${({ theme }) => theme.hoverBackground}; color: ${({ theme }) => theme.canvasTextInvert}; } `; const TableRow = forwardRef( function TableRow({ children, ...otherProps }, ref) { return ( {children} ); } ); TableRow.displayName = 'TableRow'; export { TableRow, TableRowProps }; ================================================ FILE: src/Tabs/Tab.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { Tab } from './Tab'; describe('', () => { describe('prop: children', () => { it('should render passed child', () => { const child = 'Hey there!'; const { getByText } = renderWithTheme({child}); expect(getByText(child)).toBeInTheDocument(); }); }); describe('prop: selected', () => { it('should render with correct aria attribute', () => { const { getByRole } = renderWithTheme(); expect(getByRole('tab')).toHaveAttribute('aria-selected', 'true'); }); }); describe('prop: onClick', () => { it('should be called when a click is triggered', () => { const handleClick = jest.fn(); const { getByRole } = renderWithTheme(); getByRole('tab').click(); expect(handleClick).toHaveBeenCalledTimes(1); }); }); }); ================================================ FILE: src/Tabs/Tab.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { createBorderStyles, createBoxStyles, focusOutline } from '../common'; import { blockSizes } from '../common/system'; import { CommonStyledProps } from '../types'; type TabProps = { children?: React.ReactNode; // eslint-disable-next-line @typescript-eslint/no-explicit-any onClick?: (value: any, event: React.MouseEvent) => void; selected?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any value?: any; } & Omit, 'onClick' | 'value'> & CommonStyledProps; const StyledTab = styled.button` ${createBoxStyles()} ${createBorderStyles()} position: relative; display: inline-flex; align-items: center; justify-content: center; font-size: 1rem; height: ${blockSizes.md}; line-height: ${blockSizes.md}; padding: 0 8px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; margin: 0 0 -2px 0; cursor: default; color: ${({ theme }) => theme.materialText}; user-select: none; font-family: inherit; &:focus:after, &:active:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; ${focusOutline} outline-offset: -6px; } ${props => props.selected && ` z-index: 1; height: calc(${blockSizes.md} + 4px); top: -4px; margin-bottom: -6px; padding: 0 16px; margin-left: -8px; &:not(:last-child) { margin-right: -8px; } `} &:before { content: ''; position: absolute; width: calc(100% - 4px); height: 6px; background: ${({ theme }) => theme.material}; bottom: -4px; left: 2px; } `; const Tab = forwardRef( ({ value, onClick, selected = false, children, ...otherProps }, ref) => { return ( ) => onClick?.(value, e) } ref={ref} role='tab' {...otherProps} > {children} ); } ); Tab.displayName = 'Tab'; export { Tab, TabProps }; ================================================ FILE: src/Tabs/TabBody.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TabBody } from './TabBody'; describe('', () => { describe('prop: children', () => { it('should render passed child', () => { const child = 'Hey there!'; const { getByText } = renderWithTheme({child}); expect(getByText(child)).toBeInTheDocument(); }); }); }); ================================================ FILE: src/Tabs/TabBody.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { createBorderStyles, createBoxStyles } from '../common'; import { CommonStyledProps } from '../types'; type TabBodyProps = { children: React.ReactNode; } & React.HTMLAttributes & CommonStyledProps; const StyledTabBody = styled.div` ${createBoxStyles()} ${createBorderStyles()} position: relative; display: block; height: 100%; padding: 16px; font-size: 1rem; `; const TabBody = forwardRef( ({ children, ...otherProps }, ref) => { return ( {children} ); } ); TabBody.displayName = 'TabBody'; export { TabBody, TabBodyProps }; ================================================ FILE: src/Tabs/Tabs.spec.tsx ================================================ import { fireEvent } from '@testing-library/react'; import React from 'react'; import { Tab } from '..'; import { renderWithTheme } from '../../test/utils'; import { Tabs } from './Tabs'; describe('', () => { describe('prop: children', () => { it('should accept a null child', () => { const { getAllByRole } = renderWithTheme( {null} ); expect(getAllByRole('tab').length).toBe(1); }); }); describe('prop: value', () => { const tabs = ( ); it('should pass selected prop to children', () => { const { getAllByRole } = renderWithTheme(tabs); const tabElements = getAllByRole('tab'); expect(tabElements[0]).toHaveAttribute('aria-selected', 'false'); expect(tabElements[1]).toHaveAttribute('aria-selected', 'true'); }); it('should accept any value as selected tab value', () => { const tab0 = {}; const tab1 = {}; expect(tab0).not.toBe(tab1); const { getAllByRole } = renderWithTheme( ); const tabElements = getAllByRole('tab'); expect(tabElements[0]).toHaveAttribute('aria-selected', 'true'); expect(tabElements[1]).toHaveAttribute('aria-selected', 'false'); }); }); describe('prop: onChange', () => { it('should call onChange when clicking', () => { const handleChange = jest.fn(); const { getAllByRole } = renderWithTheme( ); fireEvent.click(getAllByRole('tab')[1]); expect(handleChange).toBeCalledTimes(1); expect(handleChange.mock.calls[0][0]).toBe(1); }); }); describe('prop: rows', () => { it('should render specified number of rows', () => { const tabs = ( {/* row 1 */} {/* row 2 */} {/* row 3 */} {/* row 4 */} ); const { getAllByTestId } = renderWithTheme(tabs); const rowElements = getAllByTestId('tab-row'); expect(rowElements.length).toBe(4); }); it('row containing currently selected tab should be at the bottom (last row)', () => { const tabs = ( ); const { container, getAllByTestId } = renderWithTheme(tabs); const rowElements = getAllByTestId('tab-row'); const selectedTab = container.querySelector('[aria-selected=true]'); expect(rowElements?.pop()?.contains(selectedTab)).toBe(true); }); }); }); ================================================ FILE: src/Tabs/Tabs.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React, { useState } from 'react'; import { Anchor, Checkbox, GroupBox, NumberInput, Tab, TabBody, Tabs, Window, WindowContent, WindowHeader } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; `; export default { title: 'Controls/Tabs', component: Tabs, subcomponents: { Tab, TabBody }, decorators: [story => {story()}] } as ComponentMeta; export function Default() { const [state, setState] = useState({ activeTab: 0 }); const handleChange = ( value: number, event: React.MouseEvent ) => { console.log({ value, event }); setState({ activeTab: value }); }; const { activeTab } = state; return ( store.exe Shoes Accesories Clothing {activeTab === 0 && (
Amount:

null} defaultChecked />
)} {activeTab === 1 && (
Accesories stuff here
)} {activeTab === 2 && (
Clothing stuff here
)}
); } Default.story = { name: 'default' }; export function MultiRow() { const [state, setState] = useState({ activeTab: 'Shoes' }); const handleChange = (value: string) => setState({ activeTab: value }); const { activeTab } = state; return ( store.exe Shoes Accesories Clothing Cars Electronics Art Perfumes Games Food

Currently active tab: {activeTab}


Keep in mind that multi row tabs are{' '} REALLY bad UX . We've added them just because it was a thing back in the day, but there are better ways to handle navigation with many options.

); } MultiRow.story = { name: 'multi row' }; ================================================ FILE: src/Tabs/Tabs.tsx ================================================ import React, { forwardRef, useMemo } from 'react'; import styled from 'styled-components'; import { noOp } from '../common/utils'; import { CommonStyledProps } from '../types'; import { TabProps } from './Tab'; type TabsProps = { value?: TabProps['value']; onChange?: TabProps['onClick']; children?: React.ReactNode; rows?: number; } & Omit, 'onChange' | 'value'> & CommonStyledProps; const StyledTabs = styled.div<{ isMultiRow: boolean }>` position: relative; ${({ isMultiRow, theme }) => isMultiRow && ` button { flex-grow: 1; } button:last-child:before { border-right: 2px solid ${theme.borderDark}; } `} `; const Row = styled.div.attrs(() => ({ 'data-testid': 'tab-row' }))` position: relative; display: flex; flex-wrap: no-wrap; text-align: left; left: 8px; width: calc(100% - 8px); &:not(:first-child):before { content: ''; position: absolute; right: 0; left: 0; height: 100%; border-right: 2px solid ${({ theme }) => theme.borderDarkest}; border-left: 2px solid ${({ theme }) => theme.borderLightest}; } `; function splitToChunks(array: T[], parts: number) { const result = []; for (let i = parts; i > 0; i -= 1) { result.push(array.splice(0, Math.ceil(array.length / i))); } return result; } const Tabs = forwardRef( ({ value, onChange = noOp, children, rows = 1, ...otherProps }, ref) => { // split tabs into equal rows and assign key to each row const tabRowsToRender = useMemo(() => { const childrenWithProps = React.Children.map(children, child => { if (!React.isValidElement(child)) { return null; } const tabProps = { selected: child.props.value === value, onClick: onChange }; return React.cloneElement(child, tabProps); }) ?? []; const tabRows = splitToChunks(childrenWithProps, rows).map((tabs, i) => ({ key: i, tabs })); // move row containing currently selected tab to the bottom const currentlySelectedRowIndex = tabRows.findIndex(tabRow => tabRow.tabs.some(tab => tab.props.selected) ); tabRows.push(tabRows.splice(currentlySelectedRowIndex, 1)[0]); return tabRows; }, [children, onChange, rows, value]); return ( 1} role='tablist' ref={ref} > {tabRowsToRender.map(row => ( {row.tabs} ))} ); } ); Tabs.displayName = 'Tabs'; export * from './Tab'; export * from './TabBody'; export { Tabs, TabsProps }; ================================================ FILE: src/TextInput/TextInput.spec.tsx ================================================ // Pretty much straight out copied from https://github.com/mui-org/material-ui 😂 import React from 'react'; import { fireEvent } from '@testing-library/react'; import { renderWithTheme } from '../../test/utils'; import { TextInput } from './TextInput'; describe('', () => { it('should render an inside the div', () => { const { container } = renderWithTheme(); const input = container.querySelector('input'); expect(input).toHaveAttribute('type', 'text'); expect(input).not.toHaveAttribute('required'); }); it('should fire event callbacks', () => { const handleChange = jest.fn(); const handleFocus = jest.fn(); const handleBlur = jest.fn(); const handleKeyUp = jest.fn(); const handleKeyDown = jest.fn(); const { getByRole } = renderWithTheme( ); const input = getByRole('textbox'); // simulating user input: gain focus, key input (keydown, (input), change, keyup), blur input.focus(); expect(handleFocus).toHaveBeenCalledTimes(1); fireEvent.keyDown(document.activeElement as HTMLInputElement, { key: 'a' }); expect(handleKeyDown).toHaveBeenCalledTimes(1); fireEvent.change(input, { target: { value: 'a' } }); expect(handleChange).toHaveBeenCalledTimes(1); fireEvent.keyUp(document.activeElement as HTMLInputElement, { key: 'a' }); expect(handleKeyUp).toHaveBeenCalledTimes(1); input.blur(); expect(handleBlur).toHaveBeenCalledTimes(1); }); it('should considered [] as controlled', () => { const { getByRole } = renderWithTheme(); const input = getByRole('textbox'); expect(input).toHaveProperty('value', ''); fireEvent.change(input, { target: { value: 'do not work' } }); expect(input).toHaveProperty('value', ''); }); it('should forwardRef to native input', () => { const inputRef = React.createRef(); const { getByRole } = renderWithTheme(); const input = getByRole('textbox'); expect(inputRef.current).toBe(input); }); describe('multiline', () => { it('should render textarea when passed the multiline prop', () => { const { container } = renderWithTheme(); const textarea = container.querySelector('textarea'); expect(textarea).not.toBe(null); }); it('should forward rows prop', () => { const { container } = renderWithTheme(); const textarea = container.querySelector('textarea'); expect(textarea).toHaveAttribute('rows', '3'); }); }); describe('prop: disabled', () => { it('should render a disabled ', () => { const { container } = renderWithTheme(); const input = container.querySelector('input'); expect(input).toHaveAttribute('disabled'); }); it('should be overridden by props', () => { const { getByRole, rerender } = renderWithTheme(); rerender(); const input = getByRole('textbox'); expect(input).not.toHaveAttribute('disabled'); }); }); describe('prop: variant', () => { it('should be "default" by default', () => { const { getByTestId } = renderWithTheme(); expect(getByTestId('variant-default')).toBeInTheDocument(); }); it('should handle "flat" variant', () => { const { getByTestId } = renderWithTheme(); expect(getByTestId('variant-flat')).toBeInTheDocument(); }); }); describe('prop: fullWidth', () => { it('should make component take 100% width', () => { const { container } = renderWithTheme(); expect( window.getComputedStyle(container.firstChild as HTMLInputElement).width ).toBe('100%'); }); }); }); ================================================ FILE: src/TextInput/TextInput.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React, { useState } from 'react'; import { Button, ScrollView, TextInput } from 'react95'; import styled from 'styled-components'; const loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sollicitudin, ante vel porttitor posuere, tellus nisi interdum ipsum, non bibendum ante risus ut purus. Curabitur vel posuere odio. Vivamus rutrum, nunc et ullamcorper sagittis, tellus ligula maximus quam, id dapibus sapien metus lobortis diam. Proin luctus, dolor in finibus feugiat, lacus enim gravida sem, quis aliquet tellus leo nec enim. Morbi varius bibendum augue quis venenatis. Curabitur ut elit augue. Pellentesque posuere enim a mattis interdum. Donec sodales convallis turpis, a vulputate elit. Suspendisse potenti.`; const onChange = ( e: React.ChangeEvent ) => console.log(e.target.value); const Wrapper = styled.div` background: ${({ theme }) => theme.material}; padding: 5rem; #cutout { padding: 1rem; width: 400px; background: ${({ theme }) => theme.canvas}; } `; export default { title: 'Controls/TextInput', component: TextInput, decorators: [story => {story()}] } as ComponentMeta; export function Default() { const [state, setState] = useState({ value: '' }); const handleChange = (e: React.ChangeEvent) => setState({ value: e.target.value }); const reset = () => setState({ value: '' }); return (



); } Default.story = { name: 'default' }; export function Flat() { return (

When you want to add input field on a light background (like scrollable content), just use the flat variant:





); } Flat.story = { name: 'flat' }; ================================================ FILE: src/TextInput/TextInput.tsx ================================================ import React, { forwardRef, useMemo } from 'react'; import styled, { css } from 'styled-components'; import { createDisabledTextStyles, createFlatBoxStyles, createScrollbars } from '../common'; import { blockSizes } from '../common/system'; import { noOp } from '../common/utils'; import { StyledScrollView } from '../ScrollView/ScrollView'; import { CommonStyledProps, CommonThemeProps } from '../types'; type TextInputInputProps = { multiline?: false | undefined; onChange?: React.ChangeEventHandler; /** @default text */ type?: React.HTMLInputTypeAttribute; } & Omit< React.InputHTMLAttributes, 'className' | 'disabled' | 'style' | 'type' >; type TextInputTextAreaProps = { multiline: true; onChange?: React.ChangeEventHandler; } & Omit< React.TextareaHTMLAttributes, 'className' | 'disabled' | 'style' | 'type' >; type TextInputProps = { className?: string; disabled?: boolean; fullWidth?: boolean; multiline?: boolean; shadow?: boolean; style?: React.CSSProperties; variant?: 'default' | 'flat'; } & (TextInputInputProps | TextInputTextAreaProps) & CommonStyledProps; type WrapperProps = Pick & CommonThemeProps; const sharedWrapperStyles = css` display: flex; align-items: center; width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; min-height: ${blockSizes.md}; `; const Wrapper = styled(StyledScrollView).attrs({ 'data-testid': 'variant-default' })` ${sharedWrapperStyles} background: ${({ $disabled, theme }) => $disabled ? theme.material : theme.canvas}; `; const FlatWrapper = styled.div.attrs({ 'data-testid': 'variant-flat' })` ${createFlatBoxStyles()} ${sharedWrapperStyles} position: relative; `; type InputProps = Pick; const sharedInputStyles = css` display: block; box-sizing: border-box; width: 100%; height: 100%; outline: none; border: none; background: none; font-size: 1rem; min-height: 27px; font-family: inherit; color: ${({ theme }) => theme.canvasText}; ${({ disabled, variant }) => variant !== 'flat' && disabled && createDisabledTextStyles()} `; const StyledTextInput = styled.input` ${sharedInputStyles} padding: 0 8px; `; const StyledTextArea = styled.textarea` ${sharedInputStyles} padding: 8px; resize: none; ${({ variant }) => createScrollbars(variant)} `; const TextInput = forwardRef< HTMLInputElement | HTMLTextAreaElement, TextInputProps >( ( { className, disabled = false, fullWidth, onChange = noOp, shadow = true, style, variant = 'default', ...otherProps }, ref ) => { const WrapperComponent = variant === 'flat' ? FlatWrapper : Wrapper; const field = useMemo( () => otherProps.multiline ? ( ) : ( ), [disabled, onChange, otherProps, ref, variant] ); return ( {field} ); } ); TextInput.displayName = 'TextInput'; export { TextInput, TextInputProps }; ================================================ FILE: src/Toolbar/Toolbar.spec.tsx ================================================ import { render } from '@testing-library/react'; import React from 'react'; import { Toolbar } from './Toolbar'; describe('', () => { it('should render', () => { const { container } = render(); const toolbar = container.firstChild; expect(toolbar).toBeInTheDocument(); }); it('should render children', () => { const { container } = render(A nice app bar); const toolbar = container.firstChild; expect(toolbar).toHaveTextContent('A nice app bar'); }); describe('prop: noPadding', () => { it('should apply 0 padding', () => { const { container } = render(); const toolbar = container.firstChild; expect(toolbar).toHaveStyleRule('padding', '0'); }); }); }); ================================================ FILE: src/Toolbar/Toolbar.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; type ToolbarProps = { children?: React.ReactNode; noPadding?: boolean; } & React.HTMLAttributes; const StyledToolbar = styled.div` position: relative; display: flex; align-items: center; padding: ${props => (props.noPadding ? '0' : '4px')}; `; const Toolbar = forwardRef(function Toolbar( { children, noPadding = false, ...otherProps }, ref ) { return ( {children} ); }); Toolbar.displayName = 'Toolbar'; export { Toolbar }; ================================================ FILE: src/Tooltip/Tooltip.spec.tsx ================================================ import { fireEvent, render, waitFor } from '@testing-library/react'; import React from 'react'; import { Tooltip, TooltipProps } from './Tooltip'; const getProps = ( props: Partial = {} ): Omit => ({ className: props.className, disableFocusListener: props.disableFocusListener, disableMouseListener: props.disableMouseListener, enterDelay: props.enterDelay !== undefined ? props.enterDelay : 0, leaveDelay: props.leaveDelay !== undefined ? props.leaveDelay : 0, onBlur: jest.fn(), onClose: jest.fn(), onFocus: jest.fn(), onMouseEnter: jest.fn(), onMouseLeave: jest.fn(), onOpen: jest.fn(), style: props.style, text: 'I am the tooltip' }); const renderTooltip = (props: Omit) => (
Kid
); describe('', () => { describe('render', () => { it('should render wrapper element', () => { const { getByTestId } = render(renderTooltip(getProps())); const wrapper = getByTestId('tooltip-wrapper'); expect(wrapper).toBeInTheDocument(); expect(wrapper.tagName).toBe('DIV'); }); it('should render inner tooltip', () => { const { getByTestId } = render(renderTooltip(getProps())); const tip = getByTestId('tooltip'); expect(tip).toBeInTheDocument(); expect(tip.tagName).toBe('SPAN'); }); it('should render children', () => { const { getByText } = render(renderTooltip(getProps())); const children = getByText('Kid'); expect(children).toBeInTheDocument(); expect(children.tagName).toBe('DIV'); }); it('should render tooltip with provided className', () => { const { getByTestId } = render( renderTooltip( getProps({ className: 'my-tip' }) ) ); const tip = getByTestId('tooltip'); expect(tip.className.includes('my-tip')).toBeTruthy(); }); }); describe('transition delays', () => { beforeEach(() => { jest.useFakeTimers({ legacyFakeTimers: true }); }); afterEach(() => { jest.useRealTimers(); }); it('should respect enterDelay', async () => { const { getByTestId } = render( renderTooltip( getProps({ enterDelay: 5 }) ) ); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.focus(wrapper); expect(window.setTimeout).toHaveBeenCalledWith(expect.any(Function), 5); }); it('should respect leaveDelay', async () => { const { getByTestId } = render( renderTooltip( getProps({ leaveDelay: 6 }) ) ); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.blur(wrapper); expect(window.setTimeout).toHaveBeenCalledWith(expect.any(Function), 6); }); }); describe('event callbacks', () => { it('should handle onFocus events, and call onOpen', async () => { const props = getProps(); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.focus(wrapper); await waitFor(() => { expect(props.onFocus).toHaveBeenCalled(); }); await waitFor(() => { expect(props.onOpen).toHaveBeenCalled(); }); }); it('should handle onBlur events, and call onClose', async () => { const props = getProps(); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.focus(wrapper); await waitFor(() => { expect(props.onFocus).toHaveBeenCalled(); }); fireEvent.blur(wrapper); await waitFor(() => { expect(props.onBlur).toHaveBeenCalled(); }); await waitFor(() => { expect(props.onClose).toHaveBeenCalled(); }); }); it('should handle onMouseEnter events, and call onOpen', async () => { const props = getProps(); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.mouseEnter(wrapper); await waitFor(() => { expect(props.onMouseEnter).toHaveBeenCalled(); }); await waitFor(() => { expect(props.onOpen).toHaveBeenCalled(); }); }); it('should handle onMouseLeave events, and call onClose', async () => { const props = getProps(); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.mouseEnter(wrapper); await waitFor(() => { expect(props.onMouseEnter).toHaveBeenCalled(); }); fireEvent.mouseLeave(wrapper); await waitFor(() => { expect(props.onMouseLeave).toHaveBeenCalled(); }); await waitFor(() => { expect(props.onClose).toHaveBeenCalled(); }); }); it('should not handle onFocus events when disableFocusListener is true', () => { const props = getProps({ disableFocusListener: true }); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.focus(wrapper); expect(props.onFocus).not.toHaveBeenCalled(); }); it('should not handle onBlur events when disableFocusListener is true', () => { const props = getProps({ disableFocusListener: true }); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.blur(wrapper); expect(props.onBlur).not.toHaveBeenCalled(); }); it('should not handle onMouseEnter events when disableMouseListener is true', () => { const props = getProps({ disableMouseListener: true }); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.mouseEnter(wrapper); expect(props.onMouseEnter).not.toHaveBeenCalled(); }); it('should not handle onMouseLeave events when disableMouseListener is true', () => { const props = getProps({ disableMouseListener: true }); const { getByTestId } = render(renderTooltip(props)); const wrapper = getByTestId('tooltip-wrapper'); fireEvent.mouseLeave(wrapper); expect(props.onMouseLeave).not.toHaveBeenCalled(); }); }); }); ================================================ FILE: src/Tooltip/Tooltip.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Button, Tooltip } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; `; export default { title: 'Controls/Tooltip', component: Tooltip, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return ( ); } Default.story = { name: 'default' }; ================================================ FILE: src/Tooltip/Tooltip.tsx ================================================ import React, { forwardRef, useState } from 'react'; import styled from 'styled-components'; import { shadow } from '../common'; import { isReactFocusEvent, isReactMouseEvent } from '../common/utils/events'; import { CommonStyledProps } from '../types'; type TooltipPosition = 'top' | 'bottom' | 'left' | 'right'; type TooltipProps = { children: React.ReactNode; className?: string; disableFocusListener?: boolean; disableMouseListener?: boolean; enterDelay?: number; leaveDelay?: number; onBlur?: React.FocusEventHandler; onClose?: ( event: React.FocusEvent | React.MouseEvent ) => void; onFocus?: React.FocusEventHandler; onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; onOpen?: ( event: React.FocusEvent | React.MouseEvent ) => void; style?: React.CSSProperties; text: string; position?: TooltipPosition; } & Omit< React.HTMLAttributes, 'onBlur' | 'onClose' | 'onFocus' | 'onMouseEnter' | 'onMouseLeave' | 'onOpen' > & CommonStyledProps; const positioningStyles: Record = { top: `top: -4px; left: 50%; transform: translate(-50%, -100%);`, bottom: `bottom: -4px; left: 50%; transform: translate(-50%, 100%);`, left: `left: -4px; top: 50%; transform: translate(-100%, -50%);`, right: `right: -4px; top: 50%; transform: translate(100%, -50%);` }; const Tip = styled.span<{ position: TooltipPosition; show: boolean }>` position: absolute; z-index: 1; display: ${props => (props.show ? 'block' : 'none')}; padding: 4px; border: 2px solid ${({ theme }) => theme.borderDarkest}; background: ${({ theme }) => theme.tooltip}; box-shadow: ${shadow}; text-align: center; font-size: 1rem; ${props => positioningStyles[props.position]} `; const Wrapper = styled.div` position: relative; display: inline-block; white-space: nowrap; `; const Tooltip = forwardRef( ( { className, children, disableFocusListener = false, disableMouseListener = false, enterDelay = 1000, leaveDelay = 0, onBlur, onClose, onFocus, onMouseEnter, onMouseLeave, onOpen, style, text, position = 'top', ...otherProps }, ref ) => { const [show, setShow] = useState(false); const [openTimer, setOpenTimer] = useState(); const [closeTimer, setCloseTimer] = useState(); const isUsingFocus = !disableFocusListener; const isUsingMouse = !disableMouseListener; const handleOpen = ( event: React.FocusEvent | React.MouseEvent ) => { window.clearTimeout(openTimer); window.clearTimeout(closeTimer); const timer = window.setTimeout(() => { setShow(true); onOpen?.(event); }, enterDelay); setOpenTimer(timer); }; const handleEnter = ( event: React.FocusEvent | React.MouseEvent ) => { event.persist(); if (isReactFocusEvent(event)) { onFocus?.(event); } else if (isReactMouseEvent(event)) { onMouseEnter?.(event); } handleOpen(event); }; const handleClose = ( event: React.FocusEvent | React.MouseEvent ) => { window.clearTimeout(openTimer); window.clearTimeout(closeTimer); const timer = window.setTimeout(() => { setShow(false); onClose?.(event); }, leaveDelay); setCloseTimer(timer); }; const handleLeave = ( event: React.FocusEvent | React.MouseEvent ) => { event.persist(); if (isReactFocusEvent(event)) { onBlur?.(event); } else if (isReactMouseEvent(event)) { onMouseLeave?.(event); } handleClose(event); }; // set callbacks for onBlur and onFocus, unless disableFocusListener is true const blurCb = isUsingFocus ? handleLeave : undefined; const focusCb = isUsingFocus ? handleEnter : undefined; // set callbacks for onMouseEnter and onMouseLeave, unless disableMouseListener is true const mouseEnterCb = isUsingMouse ? handleEnter : undefined; const mouseLeaveCb = isUsingMouse ? handleLeave : undefined; // set the wrapper's tabIndex for focus events, unless disableFocusListener is true const tabIndex = isUsingFocus ? 0 : undefined; return ( {text} {children} ); } ); Tooltip.displayName = 'Tooltip'; export { Tooltip, TooltipProps }; ================================================ FILE: src/TreeView/TreeView.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { TreeView } from './TreeView'; const categories = [ { id: 'beverages', label: 'Beverages', icon: <>🥤, items: [ { id: 'juices', label: 'Juices', icon: <>🧃, items: [ { id: 'apple-juice', label: 'Apple juice', icon: <>🍎 }, { id: 'orange-juice', label: 'Orange juice', icon: <>🍊 }, { id: 'strawberry-juice', label: 'Strawberry juice', icon: <>🍓 } ] }, { id: 'coffee', label: 'Coffee', icon: <>☕, items: [ { id: 'latte', label: 'Latte', icon: <>☕ }, { id: 'espresso', label: 'Espresso', icon: <>☕ } ] } ] }, { id: 'dairy', label: 'Dairy', icon: <>🧈, items: [ { id: 'cheeses', label: 'Cheeses', icon: <>🧀, items: [ { id: 'goat-cheese', label: 'Goat cheese', icon: <>🧀 }, { id: 'camembert-cheese', label: 'Camembert', icon: <>🧀 }, { id: 'cheddar-cheese', label: 'Cheddar', icon: <>🧀 } ] }, { id: 'milk', label: 'Milk', icon: <>🥛, items: [ { id: 'cow-milk', label: 'Cow Milk', icon: <>🐄 }, { id: 'soya-milk', label: 'Soya milk', icon: <>🥛 }, { id: 'oat-milk', label: 'Oat milk', icon: <>🥛 } ] } ] } ]; describe('', () => { describe('prop: onNodeSelect', () => { it('should call onNodeSelect when uncontrolled', () => { const onNodeSelect = jest.fn((_, id) => id); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeSelect).toHaveBeenCalledTimes(1); expect(onNodeSelect.mock.results[0].value).toBe('beverages'); }); it('should call onNodeSelect when controlled', () => { const onNodeSelect = jest.fn((_, id) => id); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeSelect).toHaveBeenCalledTimes(1); expect(onNodeSelect.mock.results[0].value).toBe('beverages'); }); }); describe('prop: onNodeToggle', () => { it('should call onNodeToggle when uncontrolled', () => { const onNodeToggle = jest.fn((_, ids) => ids); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeToggle).toHaveBeenCalledTimes(1); expect(onNodeToggle.mock.results[0].value).toStrictEqual(['beverages']); }); it('should call onNodeToggle when controlled', () => { const onNodeToggle = jest.fn((_, ids) => ids); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeToggle).toHaveBeenCalledTimes(1); expect(onNodeToggle.mock.results[0].value).toStrictEqual([ 'dairy', 'beverages' ]); }); }); describe('prop: disabled', () => { it('should disable the whole tree', () => { const onNodeSelect = jest.fn((_, id) => id); const onNodeToggle = jest.fn((_, ids) => ids); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeSelect).not.toHaveBeenCalled(); expect(onNodeToggle).not.toHaveBeenCalled(); }); it('should disable a tree item', () => { const onNodeSelect = jest.fn((_, id) => id); const onNodeToggle = jest.fn((_, ids) => ids); const modifiedTree = categories.map((item, index) => index !== 0 ? item : { ...item, disabled: true } ); const { getByText } = renderWithTheme( ); getByText('Beverages').click(); expect(onNodeSelect).not.toHaveBeenCalled(); expect(onNodeToggle).not.toHaveBeenCalled(); getByText('Dairy').click(); expect(onNodeSelect).toHaveBeenCalledTimes(1); expect(onNodeToggle).toHaveBeenCalledTimes(1); }); }); }); ================================================ FILE: src/TreeView/TreeView.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React, { useCallback, useState } from 'react'; import { GroupBox, TreeLeaf, TreeView } from 'react95'; import styled from 'styled-components'; import { Button } from '../Button/Button'; const Wrapper = styled.div` background: ${({ theme }) => theme.material}; padding: 5rem; #cutout { background: ${({ theme }) => theme.canvas}; padding: 1rem; width: 250px; display: flex; align-items: center; justify-content: space-around; } `; const Panel = styled.div` padding: 2rem; `; export default { title: 'Controls/TreeView', component: TreeView, decorators: [story => {story()}] } as ComponentMeta; const categories = [ { id: 'beverages', label: 'Beverages', icon: <>🥤, items: [ { id: 'juices', label: 'Juices', icon: <>🧃, items: [ { id: 'apple-juice', label: 'Apple juice', icon: <>🍎 }, { id: 'orange-juice', label: 'Orange juice', icon: <>🍊 }, { id: 'strawberry-juice', label: 'Strawberry juice', icon: <>🍓 } ] }, { id: 'coffee', label: 'Coffee', icon: <>☕, items: [ { id: 'latte', label: 'Latte', icon: <>☕ }, { id: 'espresso', label: 'Espresso', icon: <>☕ } ] } ] }, { id: 'dairy', label: 'Dairy', icon: <>🧈, items: [ { id: 'cheeses', label: 'Cheeses', icon: <>🧀, items: [ { id: 'goat-cheese', label: 'Goat cheese', icon: <>🧀 }, { id: 'camembert-cheese', label: 'Camembert', icon: <>🧀 }, { id: 'cheddar-cheese', label: 'Cheddar', icon: <>🧀 } ] }, { id: 'milk', label: 'Milk', icon: <>🥛, items: [ { id: 'cow-milk', label: 'Cow Milk', icon: <>🐄 }, { id: 'soya-milk', label: 'Soya milk', icon: <>🥛 }, { id: 'oat-milk', label: 'Oat milk', icon: <>🥛 } ] } ] } ]; const allIds: string[] = []; function getIds(item: TreeLeaf) { allIds.push(item.id); // eslint-disable-next-line no-unused-expressions item.items?.forEach(getIds); } categories.forEach(getIds); export function Basic() { return (
); } Basic.story = { name: 'basic' }; export function Controlled() { const [selected, setSelected] = useState('oat-milk'); const [expanded, setExpanded] = useState(['dairy', 'milk']); const handleExpandClick = useCallback(() => { setExpanded(oldExpanded => (oldExpanded.length === 0 ? allIds : [])); }, []); return (
setSelected(id)} onNodeToggle={(_, ids) => setExpanded(ids)} expanded={expanded} selected={selected} />
); } Controlled.story = { name: 'controlled' }; export function Disabled() { return (
); } Disabled.story = { name: 'disabled' }; export function DisabledTreeItems() { function disableSecondItem(items: TreeLeaf[]): TreeLeaf[] { return items.map((item, index) => ({ ...item, items: item.items ? disableSecondItem(item.items) : undefined, disabled: index === 1 })); } const modifiedTree = disableSecondItem(categories); return (
); } DisabledTreeItems.story = { name: 'disabled tree items' }; ================================================ FILE: src/TreeView/TreeView.tsx ================================================ import React, { forwardRef, useCallback } from 'react'; import styled, { css } from 'styled-components'; import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled'; import { LabelText, StyledLabel } from '../common/SwitchBase'; import { CommonStyledProps } from '../types'; type TreeLeaf = { disabled?: boolean; icon?: React.ReactNode; id: T; items?: TreeLeaf[]; label?: string; }; type TreeViewProps = { className?: string; defaultExpanded?: T[]; defaultSelected?: T; disabled?: boolean; expanded?: T[]; onNodeSelect?: (event: React.MouseEvent, id: T) => void; onNodeToggle?: ( event: React.MouseEvent, expandedIds: T[] ) => void; selected?: T; style?: React.CSSProperties; tree: TreeLeaf[]; } & CommonStyledProps; type TreeBranchProps = { className: string | undefined; disabled: boolean; expanded: T[]; innerRef?: React.Ref; level: number; select: (event: React.MouseEvent, item: TreeLeaf) => void; selected: T | undefined; style: React.CSSProperties | undefined; tree: TreeLeaf[]; } & CommonStyledProps; const Text = styled(LabelText)` white-space: nowrap; `; const focusedElementStyles = css<{ $disabled: boolean }>` :focus { outline: none; } ${({ $disabled }) => !$disabled ? css` cursor: pointer; :focus { ${Text} { background: ${({ theme }) => theme.hoverBackground}; color: ${({ theme }) => theme.materialTextInvert}; outline: 2px dotted ${({ theme }) => theme.focusSecondary}; } } ` : `cursor: default;`} `; const TreeWrapper = styled.ul<{ isRootLevel: boolean }>` position: relative; isolation: isolate; ${({ isRootLevel }) => isRootLevel && css` &:before { content: ''; position: absolute; top: 20px; bottom: 0; left: 5.5px; width: 1px; border-left: 2px dashed ${({ theme }) => theme.borderDark}; } `} ul { padding-left: 19.5px; } li { position: relative; &:before { content: ''; position: absolute; top: 17.5px; left: 5.5px; width: 22px; border-top: 2px dashed ${({ theme }) => theme.borderDark}; font-size: 12px; } } `; const TreeItem = styled.li<{ hasItems: boolean; isRootLevel: boolean }>` position: relative; padding-left: ${({ hasItems }) => (!hasItems ? '13px' : '0')}; ${({ isRootLevel }) => !isRootLevel ? css` &:last-child { &:after { content: ''; position: absolute; z-index: 1; top: 19.5px; bottom: 0; left: 1.5px; width: 10px; background: ${({ theme }) => theme.material}; } } ` : css` &:last-child { &:after { content: ''; position: absolute; top: 19.5px; left: 1px; bottom: 0; width: 10px; background: ${({ theme }) => theme.material}; } } `} & > details > ul { &:after { content: ''; position: absolute; top: -18px; bottom: 0; left: 25px; border-left: 2px dashed ${({ theme }) => theme.borderDark}; } } `; const Details = styled.details` position: relative; z-index: 2; &[open] > summary:before { content: '-'; } `; const Summary = styled.summary` position: relative; z-index: 1; display: inline-flex; align-items: center; color: ${({ theme }) => theme.materialText}; user-select: none; padding-left: 18px; ${focusedElementStyles}; &::-webkit-details-marker { display: none; } &:before { content: '+'; position: absolute; left: 0; display: block; width: 8px; height: 9px; border: 2px solid #808080; padding-left: 1px; background-color: #fff; line-height: 8px; text-align: center; } `; const TitleWithIcon = styled(StyledLabel)` position: relative; z-index: 1; background: none; border: 0; font-family: inherit; padding-top: 8px; padding-bottom: 8px; margin: 0; ${focusedElementStyles}; `; const Icon = styled.span` display: flex; align-items: center; justify-content: center; width: 16px; height: 16px; margin-right: 6px; `; function toggleItem(state: T[], id: T) { return state.includes(id) ? state.filter(item => item !== id) : [...state, id]; } function preventDefault(event: React.SyntheticEvent) { event.preventDefault(); } function TreeBranch({ className, disabled, expanded, innerRef, level, select, selected, style, tree = [] }: TreeBranchProps) { const isRootLevel = level === 0; const renderLeaf = useCallback( (item: TreeLeaf) => { const hasItems = Boolean(item.items && item.items.length > 0); const isMenuShown = expanded.includes(item.id); const isNodeDisabled = (disabled || item.disabled) ?? false; const onClickSummary = !isNodeDisabled ? (event: React.MouseEvent) => select(event, item) : preventDefault; const onClickLeaf = !isNodeDisabled ? (event: React.MouseEvent) => select(event, item) : preventDefault; const isSelected = selected === item.id; const icon = {item.icon}; return ( {!hasItems ? ( {icon} {item.label} ) : (
{icon} {item.label} {isMenuShown && ( )}
)}
); }, [className, disabled, expanded, isRootLevel, level, select, selected, style] ); return ( {tree.map(renderLeaf)} ); } function TreeInner( { className, defaultExpanded = [], defaultSelected, disabled = false, expanded, onNodeSelect, onNodeToggle, selected, style, tree = [] }: TreeViewProps, ref: React.ForwardedRef ) { const [expandedInternal, setExpandedInternal] = useControlledOrUncontrolled({ defaultValue: defaultExpanded, onChange: onNodeToggle, onChangePropName: 'onNodeToggle', value: expanded, valuePropName: 'expanded' }); const [selectedInternal, setSelectedInternal] = useControlledOrUncontrolled({ defaultValue: defaultSelected, onChange: onNodeSelect, onChangePropName: 'onNodeSelect', value: selected, valuePropName: 'selected' }); const toggleMenu = useCallback( (event: React.MouseEvent, id: T) => { if (onNodeToggle) { const newState = toggleItem(expandedInternal, id); onNodeToggle(event, newState); } setExpandedInternal(previouslyExpandedIds => toggleItem(previouslyExpandedIds, id) ); }, [expandedInternal, onNodeToggle, setExpandedInternal] ); const select = useCallback( (event: React.MouseEvent, id: T) => { setSelectedInternal(id); if (onNodeSelect) { onNodeSelect(event, id); } }, [onNodeSelect, setSelectedInternal] ); const handleSelect = useCallback( (event: React.MouseEvent, item: TreeLeaf) => { event.preventDefault(); select(event, item.id); if (item.items && item.items.length) { toggleMenu(event, item.id); } }, [select, toggleMenu] ); return ( ); } /* eslint-disable no-use-before-define */ const TreeView = forwardRef(TreeInner) as ( // eslint-disable-next-line no-use-before-define props: TreeViewProps & { ref?: React.ForwardedRef } ) => ReturnType>; /* eslint-enable no-use-before-define */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore TreeView.displayName = 'TreeView'; export { TreeView, TreeViewProps, TreeLeaf }; ================================================ FILE: src/Window/Window.spec.tsx ================================================ import React, { createRef } from 'react'; import { renderWithTheme } from '../../test/utils'; import { Window } from './Window'; describe('', () => { it('renders Window', () => { const { container } = renderWithTheme(); const window = container.firstChild; expect(window).toBeInTheDocument(); }); it('renders children', () => { const textContent = 'Hi there!'; const { getByText } = renderWithTheme( {textContent} ); expect(getByText(textContent)).toBeInTheDocument(); }); describe('prop: resizable', () => { it('does not render resize handle by default', () => { const { queryByTestId } = renderWithTheme(); expect(queryByTestId('resizeHandle')).not.toBeInTheDocument(); }); it('renders resize handle when set to true', () => { const { queryByTestId } = renderWithTheme(); expect(queryByTestId('resizeHandle')).toBeInTheDocument(); }); it('passes resizeRef to the resizable element', () => { const ref = createRef(); const { queryByTestId } = renderWithTheme( ); expect(queryByTestId('resizeHandle')).toBe(ref.current); }); }); }); ================================================ FILE: src/Window/Window.stories.tsx ================================================ import { ComponentMeta } from '@storybook/react'; import React from 'react'; import { Button, Frame, Toolbar, Window, WindowContent, WindowHeader } from 'react95'; import styled from 'styled-components'; const Wrapper = styled.div` padding: 5rem; background: ${({ theme }) => theme.desktopBackground}; .window-title { display: flex; align-items: center; justify-content: space-between; } .close-icon { display: inline-block; width: 16px; height: 16px; margin-left: -1px; margin-top: -1px; transform: rotateZ(45deg); position: relative; &:before, &:after { content: ''; position: absolute; background: ${({ theme }) => theme.materialText}; } &:before { height: 100%; width: 3px; left: 50%; transform: translateX(-50%); } &:after { height: 3px; width: 100%; left: 0px; top: 50%; transform: translateY(-50%); } } .window { width: 400px; min-height: 200px; } .window:nth-child(2) { margin: 2rem; } .footer { display: block; margin: 0.25rem; height: 31px; line-height: 31px; padding-left: 0.25rem; } `; export default { title: 'Environment/Window', component: Window, subcomponents: { WindowHeader, WindowContent }, decorators: [story => {story()}] } as ComponentMeta; export function Default() { return ( <> react95.exe

When you set "resizable" prop, there will be drag handle in the bottom right corner (but resizing itself must be handled by you tho!)

Put some useful information here
not-active.exe I am not active ); } Default.story = { name: 'default' }; ================================================ FILE: src/Window/Window.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { createBorderStyles, createBoxStyles } from '../common'; import { CommonStyledProps } from '../types'; type WindowProps = { children?: React.ReactNode; resizable?: boolean; resizeRef?: React.Ref; shadow?: boolean; } & React.HTMLAttributes & CommonStyledProps; const StyledWindow = styled.div` position: relative; padding: 4px; font-size: 1rem; ${createBorderStyles({ style: 'window' })} ${createBoxStyles()} `; const ResizeHandle = styled.span` ${({ theme }) => css` display: inline-block; position: absolute; bottom: 10px; right: 10px; width: 25px; height: 25px; background-image: linear-gradient( 135deg, ${theme.borderLightest} 16.67%, ${theme.material} 16.67%, ${theme.material} 33.33%, ${theme.borderDark} 33.33%, ${theme.borderDark} 50%, ${theme.borderLightest} 50%, ${theme.borderLightest} 66.67%, ${theme.material} 66.67%, ${theme.material} 83.33%, ${theme.borderDark} 83.33%, ${theme.borderDark} 100% ); background-size: 8.49px 8.49px; clip-path: polygon(100% 0px, 0px 100%, 100% 100%); cursor: nwse-resize; `} `; const Window = forwardRef( ( { children, resizable = false, resizeRef, shadow = true, ...otherProps }, ref ) => { return ( {children} {resizable && ( )} ); } ); Window.displayName = 'Window'; export * from './WindowContent'; export * from './WindowHeader'; export { Window, WindowProps }; ================================================ FILE: src/Window/WindowContent.spec.tsx ================================================ import React from 'react'; import { renderWithTheme } from '../../test/utils'; import { WindowContent } from './WindowContent'; describe('', () => { it('renders WindowContent', () => { const { container } = renderWithTheme(); const windowContent = container.firstChild; expect(windowContent).toBeInTheDocument(); }); it('renders children', () => { const textContent = 'Hi there!'; const { getByText } = renderWithTheme( {textContent} ); expect(getByText(textContent)).toBeInTheDocument(); }); }); ================================================ FILE: src/Window/WindowContent.tsx ================================================ import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { CommonStyledProps } from '../types'; type WindowContentProps = { children?: React.ReactNode; } & React.HTMLAttributes & CommonStyledProps; const StyledWindowContent = styled.div` padding: 16px; `; const WindowContent = forwardRef( function WindowContent({ children, ...otherProps }, ref) { return ( {children} ); } ); WindowContent.displayName = 'WindowContent'; export { WindowContent, WindowContentProps }; ================================================ FILE: src/Window/WindowHeader.spec.tsx ================================================ import React from 'react'; import { renderWithTheme, theme } from '../../test/utils'; import { WindowHeader } from './WindowHeader'; describe('', () => { it('renders WindowHeader', () => { const { container } = renderWithTheme(); const windowHeader = container.firstChild; expect(windowHeader).toBeInTheDocument(); }); it('renders children', () => { const textContent = 'Hi there!'; const { getByText } = renderWithTheme( {textContent} ); expect(getByText(textContent)).toBeInTheDocument(); }); describe('prop: active', () => { it('displays active header by default', () => { const { container } = renderWithTheme(); const windowHeader = container.firstChild as HTMLDivElement; expect(windowHeader).toHaveStyleRule('color', theme.headerText); expect(windowHeader).toHaveStyleRule( 'background', theme.headerBackground ); }); it('displays active header when set to true', () => { const { container } = renderWithTheme(); const windowHeader = container.firstChild as HTMLDivElement; expect(windowHeader).toHaveStyleRule('color', theme.headerText); expect(windowHeader).toHaveStyleRule( 'background', theme.headerBackground ); }); it('renders non-active header when set to false', () => { const { container } = renderWithTheme(); const windowHeader = container.firstChild; expect(windowHeader).toHaveStyleRule('color', theme.headerNotActiveText); expect(windowHeader).toHaveStyleRule( 'background', theme.headerNotActiveBackground ); }); }); }); ================================================ FILE: src/Window/WindowHeader.tsx ================================================ import React, { forwardRef } from 'react'; import styled, { css } from 'styled-components'; import { StyledButton } from '../Button/Button'; import { CommonStyledProps } from '../types'; type WindowHeaderProps = { children?: React.ReactNode; active?: boolean; } & React.HTMLAttributes & CommonStyledProps; const StyledWindowHeader = styled.div>` height: 33px; line-height: 33px; padding-left: 0.25rem; padding-right: 3px; font-weight: bold; border: 2px solid ${({ theme }) => theme.material}; ${({ active }) => active === false ? css` background: ${({ theme }) => theme.headerNotActiveBackground}; color: ${({ theme }) => theme.headerNotActiveText}; ` : css` background: ${({ theme }) => theme.headerBackground}; color: ${({ theme }) => theme.headerText}; `} ${StyledButton} { padding-left: 0; padding-right: 0; height: 27px; width: 31px; } `; // TODO - should we add some aria label indicating if window is currently active? const WindowHeader = forwardRef( function WindowHeader({ active = true, children, ...otherProps }, ref) { return ( {children} ); } ); WindowHeader.displayName = 'WindowHeader'; export { WindowHeader, WindowHeaderProps }; ================================================ FILE: src/assets/fonts/src/ms-sans-serif/license.txt ================================================ The FontStruction “MS Sans Serif” (https://fontstruct.com/fontstructions/show/1384746) by “lou” is licensed under a Creative Commons Attribution Share Alike license (http://creativecommons.org/licenses/by-sa/3.0/). ================================================ FILE: src/assets/fonts/src/ms-sans-serif/readme.txt ================================================ The font file in this archive was created using Fontstruct the free, online font-building tool. This font was created by “lou”. This font has a homepage where this archive and other versions may be found: https://fontstruct.com/fontstructions/show/1384746 Try Fontstruct at http://fontstruct.com It’s easy and it’s fun. NOTE FOR FLASH USERS: Fontstruct fonts (fontstructions) are optimized for Flash. If the font in this archive is a pixel font, it is best displayed at a font-size of 11. Fontstruct is sponsored by FontShop. Visit them at https://fontshop.com FontShop is the original independent font retailer. We’ve been around since the dawn of digital type. Whether you need the right font or need to create the right font from scratch, let our 26 years of experience work for you. Fontstruct is copyright ©2017 Rob Meek LEGAL NOTICE: In using this font you must comply with the licensing terms described in the file “license.txt” included with this archive. If you redistribute the font file in this archive, it must be accompanied by all the other files from this archive, including this one. ================================================ FILE: src/assets/fonts/src/ms-sans-serif-bold/license.txt ================================================ The FontStruction “MS Sans Serif Bold” (https://fontstruct.com/fontstructions/show/1384862) by “lou” is licensed under a Creative Commons Attribution Share Alike license (http://creativecommons.org/licenses/by-sa/3.0/). ================================================ FILE: src/assets/fonts/src/ms-sans-serif-bold/readme.txt ================================================ The font file in this archive was created using Fontstruct the free, online font-building tool. This font was created by “lou”. This font has a homepage where this archive and other versions may be found: https://fontstruct.com/fontstructions/show/1384862 Try Fontstruct at http://fontstruct.com It’s easy and it’s fun. NOTE FOR FLASH USERS: Fontstruct fonts (fontstructions) are optimized for Flash. If the font in this archive is a pixel font, it is best displayed at a font-size of 11. Fontstruct is sponsored by FontShop. Visit them at https://fontshop.com FontShop is the original independent font retailer. We’ve been around since the dawn of digital type. Whether you need the right font or need to create the right font from scratch, let our 26 years of experience work for you. Fontstruct is copyright ©2017 Rob Meek LEGAL NOTICE: In using this font you must comply with the licensing terms described in the file “license.txt” included with this archive. If you redistribute the font file in this archive, it must be accompanied by all the other files from this archive, including this one. ================================================ FILE: src/common/SwitchBase.ts ================================================ import styled, { css } from 'styled-components'; import { createDisabledTextStyles, focusOutline } from '.'; import { StyledMenuListItem } from '../MenuList/MenuList'; export const size = 20; export const StyledInput = styled.input` position: absolute; left: 0; margin: 0; width: ${size}px; height: ${size}px; opacity: 0; z-index: -1; `; export const StyledLabel = styled.label<{ $disabled: boolean }>` display: inline-flex; align-items: center; position: relative; margin: 8px 0; cursor: ${({ $disabled }) => (!$disabled ? 'pointer' : 'auto')}; user-select: none; font-size: 1rem; color: ${({ theme }) => theme.materialText}; ${props => props.$disabled && createDisabledTextStyles()} ${StyledMenuListItem} & { margin: 0; height: 100%; } ${StyledMenuListItem}:hover & { ${({ $disabled, theme }) => !$disabled && css` color: ${theme.materialTextInvert}; `}; } `; // TODO how to handle focus styles in 'menu' variant of Checkbox/Radio? export const LabelText = styled.span` display: inline-block; line-height: 1; padding: 2px; ${StyledInput}:focus ~ & { ${focusOutline} } ${StyledInput}:not(:disabled) ~ &:active { ${focusOutline} } `; ================================================ FILE: src/common/constants.ts ================================================ export const KEYBOARD_KEY_CODES = { ARROW_DOWN: 'ArrowDown', ARROW_LEFT: 'ArrowLeft', ARROW_RIGHT: 'ArrowRight', ARROW_UP: 'ArrowUp', END: 'End', ENTER: 'Enter', ESC: 'Escape', HOME: 'Home', SPACE: 'Space', TAB: 'Tab' }; ================================================ FILE: src/common/hooks/useControlledOrUncontrolled.ts ================================================ import React, { useState, useCallback } from 'react'; export default function useControlledOrUncontrolled({ defaultValue, onChange, onChangePropName = 'onChange', readOnly, value, valuePropName = 'value' }: { defaultValue: T; // eslint-disable-next-line @typescript-eslint/no-explicit-any onChange?: (...args: any[]) => void; onChangePropName?: string; readOnly?: boolean; value: T | undefined; valuePropName?: string; }): [T, (newValue: React.SetStateAction) => void] { const isControlled = value !== undefined; const [controlledValue, setControlledValue] = useState(defaultValue); const handleChangeIfUncontrolled = useCallback( (newValue: React.SetStateAction) => { if (!isControlled) { setControlledValue(newValue); } }, [isControlled] ); // Because we provide `onChange` even to uncontrolled components, React's // default uncontrolled warning must be reimplemented. This also deals with // props that are different from `value`. if (isControlled && typeof onChange !== 'function' && !readOnly) { const message = `Warning: You provided a \`${valuePropName}\` prop to a component without an \`${onChangePropName}\` handler.${ valuePropName === 'value' ? `This will render a read-only field. If the field should be mutable use \`defaultValue\`. Otherwise, set either \`${onChangePropName}\` or \`readOnly\`.` : `This breaks the component state. You must provide an \`${onChangePropName}\` function that updates \`${valuePropName}\`.` }`; // eslint-disable-next-line no-console console.warn(message); } return [isControlled ? value : controlledValue, handleChangeIfUncontrolled]; } ================================================ FILE: src/common/hooks/useEventCallback.ts ================================================ import * as React from 'react'; const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect; /** * https://github.com/facebook/react/issues/14099#issuecomment-440013892 */ export default function useEventCallback( fn: (...args: Args) => Return ): (...args: Args) => Return { const ref = React.useRef(fn); useEnhancedEffect(() => { ref.current = fn; }); return React.useCallback( (...args: Args) => // @ts-expect-error hide `this` // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (0, ref.current!)(...args), [] ); } ================================================ FILE: src/common/hooks/useForkRef.spec.tsx ================================================ import { render } from '@testing-library/react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import useForkRef from './useForkRef'; const consoleError = console.error; beforeEach(() => { console.error = jest.fn(); }); afterEach(() => { console.error = consoleError; }); describe('useForkRef', () => { it('returns a single ref-setter function that forks the ref to its inputs', () => { function Component(props: { innerRef: React.RefObject }) { const { innerRef } = props; const ownRef = useRef(); const [, forceUpdate] = useState(true); useEffect(() => forceUpdate(n => !n), []); const handleRef = useForkRef(innerRef, ownRef); return (
{ownRef.current ? 'has a ref' : 'has no ref'}
); } const outerRef = React.createRef(); render(); expect(outerRef.current?.textContent).toBe('has a ref'); expect(console.error).not.toHaveBeenCalled(); }); it('forks if only one of the branches requires a ref', () => { const Component = React.forwardRef((_, ref) => { const [hasRef, setHasRef] = useState(false); const handleOwnRef = useCallback(() => setHasRef(true), []); const handleRef = useForkRef(handleOwnRef, ref); return
{String(hasRef)}
; }); const { getByText } = render(); expect(getByText('true')).toBeInTheDocument(); expect(console.error).not.toHaveBeenCalled(); }); it('does nothing if none of the forked branches requires a ref', () => { const setRef = jest.fn(); type OuterProps = { children: React.ReactElement; }; const Outer = React.forwardRef((props, ref) => { const { children } = props; // TODO: Fix this test as reading ref from children is not allowed so not available on React types // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const handleRef = useForkRef(children?.ref, ref); setRef(handleRef); return children ? React.cloneElement(children, { ref: handleRef }) : null; }); function Inner() { return
; } render( ); expect(console.error).not.toHaveBeenCalled(); expect(setRef).toHaveBeenCalledWith(null); }); describe('changing refs', () => { function Div( props: { leftRef?: React.Ref; rightRef?: React.Ref; } & React.HTMLAttributes ) { const { leftRef = null, rightRef = null, ...other } = props; const handleRef = useForkRef(leftRef, rightRef); return
; } it('handles changing from no ref to some ref', () => { const { rerender } = render(
); expect(console.error).not.toHaveBeenCalled(); const ref = React.createRef(); rerender(
); expect(ref.current?.id).toBe('test'); expect(console.error).not.toHaveBeenCalled(); }); it('cleans up detached refs', () => { const firstLeftRef = React.createRef(); const firstRightRef = React.createRef(); const secondRightRef = React.createRef(); const { rerender } = render(
); expect(console.error).not.toHaveBeenCalled(); expect(firstLeftRef.current?.id).toBe('test'); expect(firstRightRef.current?.id).toBe('test'); expect(secondRightRef.current).toBe(null); rerender(
); expect(firstLeftRef.current?.id).toBe('test'); expect(firstRightRef.current).toBe(null); expect(secondRightRef.current?.id).toBe('test'); }); }); }); ================================================ FILE: src/common/hooks/useForkRef.ts ================================================ // Straight out copied from https://github.com/mui-org/material-ui 😂 import { useMemo } from 'react'; function setRef( ref: React.RefCallback | React.MutableRefObject | null, value: T ) { if (typeof ref === 'function') { ref(value); } else if (ref) { // eslint-disable-next-line no-param-reassign ref.current = value; } } export default function useForkRef( refA: React.RefCallback | React.MutableRefObject | null, refB: React.RefCallback | React.MutableRefObject | null ): React.RefCallback | null { /** * This will create a new function if the ref props change and are defined. * This means react will call the old forkRef with `null` and the new forkRef * with the ref. Cleanup naturally emerges from this behavior */ return useMemo(() => { if (refA == null && refB == null) { return null; } return refValue => { setRef(refA, refValue); setRef(refB, refValue); }; }, [refA, refB]); } ================================================ FILE: src/common/hooks/useId.spec.ts ================================================ import { renderHook } from '@testing-library/react-hooks'; import { useId } from './useId'; describe(useId, () => { it('returns a random string of 10 digits', () => { const { result } = renderHook(() => useId()); expect(result.current).toMatch(/^[0-9a-z]{10}$/i); }); it('returns the passed ID', () => { const { result } = renderHook(() => useId('test')); expect(result.current).toEqual('test'); }); }); ================================================ FILE: src/common/hooks/useId.ts ================================================ import { useMemo } from 'react'; function makeId() { const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; let id = ''; for (let i = 0; i < 10; i += 1) { id += chars[Math.floor(Math.random() * chars.length)]; } return id; } export const useId = (id?: string) => { return useMemo(() => id ?? makeId(), [id]); }; ================================================ FILE: src/common/hooks/useIsFocusVisible.ts ================================================ // Straight out copied from https://github.com/mui-org/material-ui 😂 // based on https://github.com/WICG/focus-visible/blob/v4.1.5/src/focus-visible.js import { useCallback } from 'react'; import { findDOMNode } from 'react-dom'; let hadKeyboardEvent = true; let hadFocusVisibleRecently = false; let hadFocusVisibleRecentlyTimeout: number; const inputTypesWhitelist: Record = { text: true, search: true, url: true, tel: true, email: true, password: true, number: true, date: true, month: true, week: true, time: true, datetime: true, 'datetime-local': true }; /** * Computes whether the given element should automatically trigger the * `focus-visible` class being added, i.e. whether it should always match * `:focus-visible` when focused. * @param {Element} node * @return {boolean} */ function focusTriggersKeyboardModality( node: Element | HTMLElement | HTMLInputElement ) { if ('type' in node) { const { type, tagName } = node; if (tagName === 'INPUT' && inputTypesWhitelist[type] && !node.readOnly) { return true; } if (tagName === 'TEXTAREA' && !node.readOnly) { return true; } } if ('isContentEditable' in node && node.isContentEditable) { return true; } return false; } /** * Keep track of our keyboard modality state with `hadKeyboardEvent`. * If the most recent user interaction was via the keyboard; * and the key press did not include a meta, alt/option, or control key; * then the modality is keyboard. Otherwise, the modality is not keyboard. * @param {KeyboardEvent} event */ function handleKeyDown(event: KeyboardEvent) { if (event.metaKey || event.altKey || event.ctrlKey) { return; } hadKeyboardEvent = true; } /** * If at any point a user clicks with a pointing device, ensure that we change * the modality away from keyboard. * This avoids the situation where a user presses a key on an already focused * element, and then clicks on a different element, focusing it with a * pointing device, while we still think we're in keyboard modality. */ function handlePointerDown() { hadKeyboardEvent = false; } function handleVisibilityChange(this: Document) { if (this.visibilityState === 'hidden') { // If the tab becomes active again, the browser will handle calling focus // on the element (Safari actually calls it twice). // If this tab change caused a blur on an element with focus-visible, // re-apply the class when the user switches back to the tab. if (hadFocusVisibleRecently) { hadKeyboardEvent = true; } } } function prepare(doc: Document) { doc.addEventListener('keydown', handleKeyDown, true); doc.addEventListener('mousedown', handlePointerDown, true); doc.addEventListener('pointerdown', handlePointerDown, true); doc.addEventListener('touchstart', handlePointerDown, true); doc.addEventListener('visibilitychange', handleVisibilityChange, true); } export function teardown(doc: Document) { doc.removeEventListener('keydown', handleKeyDown, true); doc.removeEventListener('mousedown', handlePointerDown, true); doc.removeEventListener('pointerdown', handlePointerDown, true); doc.removeEventListener('touchstart', handlePointerDown, true); doc.removeEventListener('visibilitychange', handleVisibilityChange, true); } function isFocusVisible(event: React.FocusEvent) { const { target } = event; try { return target.matches(':focus-visible'); } catch (error) { // browsers not implementing :focus-visible will throw a SyntaxError // we use our own heuristic for those browsers // rethrow might be better if it's not the expected error but do we really // want to crash if focus-visible malfunctioned? } // no need for validFocusTarget check. the user does that by attaching it to // focusable events only return hadKeyboardEvent || focusTriggersKeyboardModality(target); } /** * Should be called if a blur event is fired on a focus-visible element */ function handleBlurVisible() { // To detect a tab/window switch, we look for a blur event followed // rapidly by a visibility change. // If we don't see a visibility change within 100ms, it's probably a // regular focus change. hadFocusVisibleRecently = true; window.clearTimeout(hadFocusVisibleRecentlyTimeout); hadFocusVisibleRecentlyTimeout = window.setTimeout(() => { hadFocusVisibleRecently = false; }, 100); } export function useIsFocusVisible() { const ref = useCallback((instance: T) => { // eslint-disable-next-line react/no-find-dom-node const node = findDOMNode(instance); if (node != null) { prepare(node.ownerDocument); } }, []); return { isFocusVisible, onBlurVisible: handleBlurVisible, ref }; } ================================================ FILE: src/common/index.ts ================================================ import { css } from 'styled-components'; import { Color, CommonThemeProps, Theme } from '../types'; export const shadow = '4px 4px 10px 0 rgba(0, 0, 0, 0.35)'; export const insetShadow = 'inset 2px 2px 3px rgba(0,0,0,0.2)'; export const createDisabledTextStyles = () => css` -webkit-text-fill-color: ${({ theme }) => theme.materialTextDisabled}; color: ${({ theme }) => theme.materialTextDisabled}; text-shadow: 1px 1px ${({ theme }) => theme.materialTextDisabledShadow}; /* filter: grayscale(100%); */ `; export const createBoxStyles = ({ background = 'material', color = 'materialText' }: { background?: keyof Theme; color?: keyof Theme; } = {}) => css` box-sizing: border-box; display: inline-block; background: ${({ theme }) => theme[background]}; color: ${({ theme }) => theme[color]}; `; // TODO for flat box styles add checkered background when disabled (not solid color) export const createHatchedBackground = ({ mainColor = 'black', secondaryColor = 'transparent', pixelSize = 2 }) => css` background-image: ${[ `linear-gradient( 45deg, ${mainColor} 25%, transparent 25%, transparent 75%, ${mainColor} 75% )`, `linear-gradient( 45deg, ${mainColor} 25%, transparent 25%, transparent 75%, ${mainColor} 75% )` ].join(',')}; background-color: ${secondaryColor}; background-size: ${`${pixelSize * 2}px ${pixelSize * 2}px`}; background-position: 0 0, ${`${pixelSize}px ${pixelSize}px`}; `; export const createFlatBoxStyles = () => css` position: relative; box-sizing: border-box; display: inline-block; color: ${({ theme }) => theme.materialText}; background: ${({ $disabled, theme }) => $disabled ? theme.flatLight : theme.canvas}; border: 2px solid ${({ theme }) => theme.canvas}; outline: 2px solid ${({ theme }) => theme.flatDark}; outline-offset: -4px; `; export type BorderStyles = | 'button' | 'buttonPressed' | 'buttonThin' | 'buttonThinPressed' | 'field' | 'grouping' | 'status' | 'window'; type BorderStyle = { topLeftOuter: keyof Theme; topLeftInner: keyof Theme | null; bottomRightInner: keyof Theme | null; bottomRightOuter: keyof Theme; }; const borderStyles: Record = { button: { topLeftOuter: 'borderLightest', topLeftInner: 'borderLight', bottomRightInner: 'borderDark', bottomRightOuter: 'borderDarkest' }, buttonPressed: { topLeftOuter: 'borderDarkest', topLeftInner: 'borderDark', bottomRightInner: 'borderLight', bottomRightOuter: 'borderLightest' }, buttonThin: { topLeftOuter: 'borderLightest', topLeftInner: null, bottomRightInner: null, bottomRightOuter: 'borderDark' }, buttonThinPressed: { topLeftOuter: 'borderDark', topLeftInner: null, bottomRightInner: null, bottomRightOuter: 'borderLightest' }, field: { topLeftOuter: 'borderDark', topLeftInner: 'borderDarkest', bottomRightInner: 'borderLight', bottomRightOuter: 'borderLightest' }, grouping: { topLeftOuter: 'borderDark', topLeftInner: 'borderLightest', bottomRightInner: 'borderDark', bottomRightOuter: 'borderLightest' }, status: { topLeftOuter: 'borderDark', topLeftInner: null, bottomRightInner: null, bottomRightOuter: 'borderLightest' }, window: { topLeftOuter: 'borderLight', topLeftInner: 'borderLightest', bottomRightInner: 'borderDark', bottomRightOuter: 'borderDarkest' } }; export const createInnerBorderWithShadow = ({ theme, topLeftInner, bottomRightInner, hasShadow = false, hasInsetShadow = false }: { theme: Theme; topLeftInner: keyof Theme | null; bottomRightInner: keyof Theme | null; hasShadow?: boolean; hasInsetShadow?: boolean; }) => [ hasShadow ? shadow : false, hasInsetShadow ? insetShadow : false, topLeftInner !== null ? `inset 1px 1px 0px 1px ${theme[topLeftInner]}` : false, bottomRightInner !== null ? `inset -1px -1px 0 1px ${theme[bottomRightInner]}` : false ] .filter(Boolean) .join(', '); export const createBorderStyles = ({ invert = false, style = 'button' }: { invert?: boolean; style?: BorderStyles } = {}) => { const borders = { topLeftOuter: invert ? 'bottomRightOuter' : 'topLeftOuter', topLeftInner: invert ? 'bottomRightInner' : 'topLeftInner', bottomRightInner: invert ? 'topLeftInner' : 'bottomRightInner', bottomRightOuter: invert ? 'topLeftOuter' : 'bottomRightOuter' } as const; return css` border-style: solid; border-width: 2px; border-left-color: ${({ theme }) => theme[borderStyles[style][borders.topLeftOuter]]}; border-top-color: ${({ theme }) => theme[borderStyles[style][borders.topLeftOuter]]}; border-right-color: ${({ theme }) => theme[borderStyles[style][borders.bottomRightOuter]]}; border-bottom-color: ${({ theme }) => theme[borderStyles[style][borders.bottomRightOuter]]}; box-shadow: ${({ theme, shadow: hasShadow }) => createInnerBorderWithShadow({ theme, topLeftInner: borderStyles[style][borders.topLeftInner], bottomRightInner: borderStyles[style][borders.bottomRightInner], hasShadow })}; `; }; /** @deprecated Use `createBorderStyles` instead */ export const createWellBorderStyles = (invert = false) => createBorderStyles({ invert: !invert, style: 'status' }); export const focusOutline = () => css` outline: 2px dotted ${({ theme }) => theme.materialText}; `; const nodeBtoa = (string: string) => Buffer.from(string).toString('base64'); const base64encode = typeof btoa !== 'undefined' ? btoa : nodeBtoa; const createTriangleSVG = (color: Color, angle = 0) => { const svg = ` `; const encoded = base64encode(svg); return `url(data:image/svg+xml;base64,${encoded})`; }; export const createScrollbars = (variant = 'default') => css` ::-webkit-scrollbar { width: 26px; height: 26px; } ::-webkit-scrollbar-track { ${({ theme }) => createHatchedBackground({ mainColor: variant === 'flat' ? theme.flatLight : theme.material, secondaryColor: variant === 'flat' ? theme.canvas : theme.borderLightest })} } ::-webkit-scrollbar-thumb { ${createBoxStyles()} ${variant === 'flat' ? createFlatBoxStyles() : createBorderStyles({ style: 'window' })} outline-offset: -2px; } ::-webkit-scrollbar-corner { background-color: ${({ theme }) => theme.material}; } ::-webkit-scrollbar-button { ${createBoxStyles()} ${variant === 'flat' ? createFlatBoxStyles() : createBorderStyles({ style: 'window' })} display: block; outline-offset: -2px; height: 26px; width: 26px; background-repeat: no-repeat; background-size: 100%; background-position: 0 0; } ::-webkit-scrollbar-button:active, ::-webkit-scrollbar-button:active { background-position: 0 1px; ${variant === 'default' ? createBorderStyles({ style: 'window', invert: true }) : ''} } ::-webkit-scrollbar-button:horizontal:increment:start, ::-webkit-scrollbar-button:horizontal:decrement:end, ::-webkit-scrollbar-button:vertical:increment:start, ::-webkit-scrollbar-button:vertical:decrement:end { display: none; } ::-webkit-scrollbar-button:horizontal:decrement { background-image: ${({ theme }) => createTriangleSVG(theme.materialText, 90)}; } ::-webkit-scrollbar-button:horizontal:increment { background-image: ${({ theme }) => createTriangleSVG(theme.materialText, 270)}; } ::-webkit-scrollbar-button:vertical:decrement { background-image: ${({ theme }) => createTriangleSVG(theme.materialText, 180)}; } ::-webkit-scrollbar-button:vertical:increment { background-image: ${({ theme }) => createTriangleSVG(theme.materialText, 0)}; } `; ================================================ FILE: src/common/styleReset.ts ================================================ export default ` html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1.5; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ""; content: none; } table { border-collapse: collapse; border-spacing: 0; } a { color: inherit; text-decoration: none; } ul, li { list-style-type: none; } button { outline: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { margin: 0; padding: 0; font-family: sans-serif; color: black; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } input[type="number"] { -moz-appearance: textfield; } `; ================================================ FILE: src/common/system.ts ================================================ // TODO - implement styled-system import { Sizes } from '../types'; export const blockSizes: Record = { sm: '28px', md: '36px', lg: '44px' }; ================================================ FILE: src/common/themes/aiee.ts ================================================ /* "AIEE" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Aiee-668092636 */ import { Theme } from './types'; export default { name: 'aiee', anchor: 'rgb(0,0,128)', anchorVisited: 'rgb(0,0,128)', borderDark: 'rgb(211,214,217)', borderDarkest: 'rgb(65,187,241)', borderLight: 'rgb(238,244,252)', borderLightest: 'rgb(250,254,255)', canvas: 'rgb(255,255,255)', canvasText: 'rgb(0,62,1090)', canvasTextDisabled: 'rgb(211,214,217)', canvasTextDisabledShadow: 'rgb(250,254,255)', canvasTextInvert: 'rgb(0,62,109)', checkmark: 'rgb(0,62,1090)', checkmarkDisabled: 'rgb(88,152,231)', desktopBackground: 'rgb(68,125,183)', flatDark: 'rgb(211,214,217)', flatLight: 'rgb(238,244,252)', focusSecondary: 'rgb(250,254,255)', headerBackground: 'linear-gradient(to right, rgb(4,118,180), rgb(251,211,61))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0,159,223), rgb(0,207,247))', headerNotActiveText: 'rgb(0,62,109)', headerText: 'rgb(255,255,255)', hoverBackground: 'rgb(251,211,61)', material: 'rgb(227,238,251)', materialDark: 'rgb(0,159,223)', materialText: 'rgb(0,62,109)', materialTextDisabled: 'rgb(211,214,217)', materialTextDisabledShadow: 'rgb(250,254,255)', materialTextInvert: 'rgb(0,62,109)', progress: 'rgb(251,211,61)', tooltip: 'rgb(255,243,185)' } as Theme; ================================================ FILE: src/common/themes/ash.ts ================================================ /* "Ash" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Ash-575566643 */ import { Theme } from './types'; export default { name: 'ash', anchor: 'rgb(192, 192, 192)', anchorVisited: 'rgb(192, 192, 192)', borderDark: 'rgb(63, 63, 63)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(115, 115, 115)', borderLightest: 'rgb(175, 175, 175)', canvas: 'rgb(64, 64, 64)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(63, 63, 63)', canvasTextDisabledShadow: 'rgb(175, 175, 175)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(128, 128, 128)', desktopBackground: 'rgb(0, 0, 0)', flatDark: 'rgb(63, 63, 63)', flatLight: 'rgb(96, 96, 96)', focusSecondary: 'rgb(175, 175, 175)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(0, 0, 0))', headerNotActiveBackground: 'linear-gradient(to right, rgb(63, 63, 63), rgb(0, 0, 0))', headerNotActiveText: 'rgb(128, 128, 128)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 0, 0)', material: 'rgb(96, 96, 96)', materialDark: 'rgb(63, 63, 63)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(63, 63, 63)', materialTextDisabledShadow: 'rgb(175, 175, 175)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 0, 0)', tooltip: 'rgb(0, 0, 0)' } as Theme; ================================================ FILE: src/common/themes/azureOrange.ts ================================================ import { Theme } from './types'; export default { name: 'azureOrange', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#05427f', borderDarkest: '#000000', borderLight: '#2b8fff', borderLightest: '#7ebfff', canvas: '#ffffff', canvasText: '#000000', canvasTextDisabled: '#05427f', canvasTextDisabledShadow: '#7ebfff', canvasTextInvert: '#000000', checkmark: '#000000', checkmarkDisabled: '#05427f', desktopBackground: '#ff7d01', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#171123', headerBackground: '#171123', headerNotActiveBackground: '#4E6766', headerNotActiveText: '#0180ff', headerText: '#ffffff', hoverBackground: '#F46036', material: '#0180ff', materialDark: '#9a9e9c', materialText: '#000000', materialTextDisabled: '#05427f', materialTextDisabledShadow: '#7ebfff', materialTextInvert: '#000000', progress: '#F46036', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/bee.ts ================================================ import { Theme } from './types'; export default { name: 'bee', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#846d06', borderDarkest: '#0C1618', borderLight: '#e7c221', borderLightest: '#f8df6e', canvas: '#ffffff', canvasText: '#0C1618', canvasTextDisabled: '#846d06', canvasTextDisabledShadow: '#f8df6e', canvasTextInvert: '#ffffff', checkmark: '#0C1618', checkmarkDisabled: '#846d06', desktopBackground: '#977800', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#0C1618', headerNotActiveBackground: '#7F7B82', headerNotActiveText: '#e5bd03', headerText: '#f8df6e', hoverBackground: '#0C1618', material: '#e5bd03', materialDark: '#7F7B82', materialText: '#0C1618', materialTextDisabled: '#846d06', materialTextDisabledShadow: '#f8df6e', materialTextInvert: '#ffffff', progress: '#0C1618', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/blackAndWhite.ts ================================================ import { Theme } from './types'; export default { name: 'blackAndWhite', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#888c8f', borderDarkest: '#000000', borderLight: '#dfe0e3', borderLightest: '#888c8f', canvas: '#ffffff', canvasText: '#000000', canvasTextDisabled: '#888c8f', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#000000', checkmarkDisabled: '#888c8f', desktopBackground: '#ffffff', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#000000', headerNotActiveBackground: '#ffffff', headerNotActiveText: '#000000', headerText: '#ffffff', hoverBackground: '#000000', material: '#ffffff', materialDark: '#9a9e9c', materialText: '#000000', materialTextDisabled: '#888c8f', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#000000', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/blue.ts ================================================ /* "Blue" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Blue-525167751 */ import { Theme } from './types'; export default { name: 'blue', anchor: 'rgb(0, 0, 128)', anchorVisited: 'rgb(0, 0, 128)', borderDark: 'rgb(49, 131, 221)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(166, 202, 240)', borderLightest: 'rgb(211, 228, 248)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(49, 131, 221)', canvasTextDisabledShadow: 'rgb(211, 228, 248)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(49, 131, 221)', desktopBackground: 'rgb(58, 110, 165)', flatDark: 'rgb(49, 131, 221)', flatLight: 'rgb(166, 202, 240)', focusSecondary: 'rgb(211, 228, 248)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 128), rgb(16, 132, 208))', headerNotActiveBackground: 'linear-gradient(to right, rgb(49, 131, 221), rgb(49, 131, 221))', headerNotActiveText: 'rgb(0, 0, 128)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(51, 153, 255)', material: 'rgb(166, 202, 240)', materialDark: 'rgb(49, 131, 221)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(49, 131, 221)', materialTextDisabledShadow: 'rgb(211, 228, 248)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(51, 153, 255)', tooltip: 'rgb(225, 225, 255)' } as Theme; ================================================ FILE: src/common/themes/brick.ts ================================================ import { Theme } from './types'; export default { name: 'brick', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#6c684b', borderDarkest: '#020000', borderLight: '#e2ddc9', borderLightest: '#ffffff', canvas: '#ffffff', canvasText: '#020000', canvasTextDisabled: '#6c684b', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#020000', checkmarkDisabled: '#6c684b', desktopBackground: '#420000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#8e0101', headerNotActiveBackground: '#90885c', headerNotActiveText: '#c2bfa3', headerText: '#ffffff', hoverBackground: '#8e0101', material: '#c2bfa3', materialDark: '#9a9e9c', materialText: '#020000', materialTextDisabled: '#6c684b', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#8e0101', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/candy.ts ================================================ import { Theme } from './types'; export default { name: 'candy', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#d1579e', borderDarkest: '#44132f', borderLight: '#f1acd5', borderLightest: '#EFF1F3', canvas: '#EFF1F3', canvasText: '#000000', canvasTextDisabled: '#d1579e', canvasTextDisabledShadow: '#EFF1F3', canvasTextInvert: '#EFF1F3', checkmark: '#000000', checkmarkDisabled: '#d1579e', desktopBackground: '#b477bd', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#87255B', headerNotActiveBackground: '#a08796', headerNotActiveText: '#EBD2BE', headerText: '#EFF1F3', hoverBackground: '#256EFF', material: '#E5A4CB', materialDark: '#9a9e9c', materialText: '#000000', materialTextDisabled: '#d1579e', materialTextDisabledShadow: '#EFF1F3', materialTextInvert: '#EFF1F3', progress: '#256EFF', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/cherry.ts ================================================ /* "Cherry" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Cherry-747961418 */ import { Theme } from './types'; export default { name: 'cherry', anchor: 'rgb(128, 0, 1)', anchorVisited: 'rgb(128, 0, 1)', borderDark: 'rgb(128, 128, 128)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(200, 212, 208)', borderLightest: 'rgb(255, 255, 255)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(128, 128, 128)', canvasTextDisabledShadow: 'rgb(255, 255, 255)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(128, 128, 128)', desktopBackground: 'rgb(165, 58, 109)', flatDark: 'rgb(128, 128, 128)', flatLight: 'rgb(200, 212, 208)', focusSecondary: 'rgb(255, 255, 255)', headerBackground: 'linear-gradient(to right, rgb(106, 10, 36), rgb(240, 166, 202))', headerNotActiveBackground: 'linear-gradient(to right, rgb(128, 128, 128), rgb(192, 192, 192))', headerNotActiveText: 'rgb(212, 208, 200)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(106, 10, 36)', material: 'rgb(200, 212, 208)', materialDark: 'rgb(128, 128, 128)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(128, 128, 128)', materialTextDisabledShadow: 'rgb(255, 255, 255)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(106, 10, 36)', tooltip: 'rgb(225, 254, 255)' } as Theme; ================================================ FILE: src/common/themes/coldGray.ts ================================================ import { Theme } from './types'; export default { name: 'coldGray', anchor: '#8d88c2', anchorVisited: '#440381', background: '#4C6663', borderDark: '#5b57a1', borderDarkest: '#010601', borderLight: '#a4a7c8', borderLightest: '#c7c7df', canvas: '#c7c7df', canvasText: '#050608', canvasTextDisabled: '#888c8f', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#010601', checkmarkDisabled: '#5b57a1', desktopBackground: '#606286', flatDark: '#5b57a1', flatLight: '#a4a7c8', focusSecondary: '#fefe03', headerBackground: '#3B3D64', headerNotActiveBackground: '#6063a5', headerNotActiveText: '#a1a3ca', headerText: '#010601', hoverBackground: '#8d88c2', material: '#a1a3ca', materialDark: '#6063a5', materialText: '#010601', materialTextDisabled: '#5b57a1', materialTextDisabledShadow: '#c7c7df', materialTextInvert: '#c7c7df', progress: '#8d88c2', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/counterStrike.ts ================================================ import { Theme } from './types'; export default { name: 'counterStrike', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#2c3125', borderDarkest: '#0a0a0a', borderLight: '#5d6d54', borderLightest: '#788475', canvas: '#3e4639', canvasText: '#f6fbf5', canvasTextDisabled: '#2c3125', canvasTextDisabledShadow: '#788475', canvasTextInvert: '#f6fbf5', checkmark: '#f6fbf5', checkmarkDisabled: '#2c3125', desktopBackground: '#bcbd52', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#4b5844', headerNotActiveBackground: '#4b5844', headerNotActiveText: '#74806e', headerText: '#fefefe', hoverBackground: '#978830', material: '#4b5844', materialDark: '#2f3428', materialText: '#f6fbf5', materialTextDisabled: '#2c3125', materialTextDisabledShadow: '#788475', materialTextInvert: '#fefefe', progress: '#978830', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/darkTeal.ts ================================================ /* "Teal for Shelbi - Dark" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Teal-for-Shelbi-Dark-631177772 */ import { Theme } from './types'; export default { name: 'darkTeal', anchor: 'rgb(0, 132, 132)', anchorVisited: 'rgb(0, 132, 132)', borderDark: 'rgb(21, 43, 43)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(32, 64, 64)', borderLightest: 'rgb(88, 139, 139)', canvas: 'rgb(0, 48, 48)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(21, 43, 43)', canvasTextDisabledShadow: 'rgb(88, 139, 139)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(64, 128, 128)', desktopBackground: 'rgb(0, 64, 64)', flatDark: 'rgb(21, 43, 43)', flatLight: 'rgb(32, 64, 64)', focusSecondary: 'rgb(88, 139, 139)', headerBackground: 'linear-gradient(to right, rgb(0, 96, 96), rgb(0, 132, 132))', headerNotActiveBackground: 'linear-gradient(to right, rgb(24, 50, 50), rgb(92, 112, 112))', headerNotActiveText: 'rgb(192, 204, 204)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 128, 128)', material: 'rgb(32, 64, 64)', materialDark: 'rgb(24, 50, 50)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(21, 43, 43)', materialTextDisabledShadow: 'rgb(88, 139, 139)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 128, 128)', tooltip: 'rgb(0, 32, 32)' } as Theme; ================================================ FILE: src/common/themes/denim.ts ================================================ /* "Denim" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Denim-870494744 */ import { Theme } from './types'; export default { name: 'denim', anchor: 'rgb(0, 0, 128)', anchorVisited: 'rgb(0, 0, 128)', borderDark: 'rgb(0, 0, 255)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(128, 128, 255)', borderLightest: 'rgb(191, 191, 255)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(0, 0, 255)', canvasTextDisabledShadow: 'rgb(191, 191, 255)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(0, 0, 255)', desktopBackground: 'rgb(0, 64, 128)', flatDark: 'rgb(0, 0, 255)', flatLight: 'rgb(128, 128, 255)', focusSecondary: 'rgb(191, 191, 255)', headerBackground: 'linear-gradient(to right, rgb(10, 36, 106), rgb(148, 190, 237))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 0, 255), rgb(180, 180, 180))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(10, 36, 106)', material: 'rgb(128, 128, 255)', materialDark: 'rgb(0, 0, 255)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(0, 0, 255)', materialTextDisabledShadow: 'rgb(191, 191, 255)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(10, 36, 106)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/themes/eggplant.ts ================================================ import { Theme } from './types'; export default { name: 'eggplant', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#526d67', borderDarkest: '#050608', borderLight: '#a2c7c0', borderLightest: '#cee8e3', canvas: '#ffffff', canvasText: '#050608', canvasTextDisabled: '#526d67', canvasTextDisabledShadow: '#cee8e3', canvasTextInvert: '#cee8e3', checkmark: '#050608', checkmarkDisabled: '#526d67', desktopBackground: '#400040', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#4b8178', headerNotActiveBackground: '#89b0a8', headerNotActiveText: '#4b8178', headerText: '#ffffff', hoverBackground: '#4b8178', material: '#89b0a8', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#526d67', materialTextDisabledShadow: '#cee8e3', materialTextInvert: '#ffffff', progress: '#4b8178', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/fxDev.ts ================================================ /* "FxDev" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/FxDev-701274128 */ import { Theme } from './types'; export default { name: 'fxDev', anchor: 'rgb(47, 138, 202)', anchorVisited: 'rgb(47, 138, 202)', borderDark: 'rgb(27, 33, 39)', borderDarkest: 'rgb(7, 9, 10)', borderLight: 'rgb(69, 82, 94)', borderLightest: 'rgb(107, 113, 122)', canvas: 'rgb(23, 27, 31)', canvasText: 'rgb(245, 247, 250)', canvasTextDisabled: 'rgb(27, 33, 39)', canvasTextDisabledShadow: 'rgb(107, 113, 122)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(245, 247, 250)', checkmarkDisabled: 'rgb(133, 136, 140)', desktopBackground: 'rgb(26, 58, 99)', flatDark: 'rgb(27, 33, 39)', flatLight: 'rgb(57, 66, 77)', focusSecondary: 'rgb(107, 113, 122)', headerBackground: 'linear-gradient(to right, rgb(26, 70, 102), rgb(26, 70, 102))', headerNotActiveBackground: 'linear-gradient(to right, rgb(28, 33, 38), rgb(28, 33, 38))', headerNotActiveText: 'rgb(245, 247, 250)', headerText: 'rgb(245, 247, 250)', hoverBackground: 'rgb(7, 77, 117)', material: 'rgb(57, 66, 77)', materialDark: 'rgb(28, 33, 38)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(27, 33, 39)', materialTextDisabledShadow: 'rgb(107, 113, 122)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(7, 77, 117)', tooltip: 'rgb(243, 242, 219)' } as Theme; ================================================ FILE: src/common/themes/highContrast.ts ================================================ import { Theme } from './types'; export default { name: 'highContrast', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#888c8f', borderDarkest: '#ffffff', borderLight: '#dfe0e3', borderLightest: '#ffffff', canvas: '#353535', canvasText: '#ffffff', canvasTextDisabled: '#888c8f', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#ffffff', checkmarkDisabled: '#888c8f', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#8e0284', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#ced0cf', headerText: '#ffffff', hoverBackground: '#8e0284', material: '#000000', materialDark: '#9a9e9c', materialText: '#ffffff', materialTextDisabled: '#888c8f', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#8e0284', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/honey.ts ================================================ /* "Honey" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Honey-632126512 */ import { Theme } from './types'; export default { name: 'honey', anchor: 'rgb(128, 128, 0)', anchorVisited: 'rgb(128, 128, 0)', borderDark: 'rgb(170, 123, 0)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(255, 186, 0)', borderLightest: 'rgb(255, 220, 128)', canvas: 'rgb(255, 220, 128)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(170, 123, 0)', canvasTextDisabledShadow: 'rgb(255, 220, 128)', canvasTextInvert: 'rgb(255, 255, 0)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(170, 123, 0)', desktopBackground: 'rgb(170, 123, 0)', flatDark: 'rgb(170, 123, 0)', flatLight: 'rgb(255, 186, 0)', focusSecondary: 'rgb(255, 220, 128)', headerBackground: 'linear-gradient(to right, rgb(255, 255, 0), rgb(255, 186, 0))', headerNotActiveBackground: 'linear-gradient(to right, rgb(255, 186, 0), rgb(255, 220, 128))', headerNotActiveText: 'rgb(170, 123, 0)', headerText: 'rgb(0, 0, 0)', hoverBackground: 'rgb(170, 123, 0)', material: 'rgb(255, 186, 0)', materialDark: 'rgb(255, 186, 0)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(170, 123, 0)', materialTextDisabledShadow: 'rgb(255, 220, 128)', materialTextInvert: 'rgb(255, 255, 0)', progress: 'rgb(170, 123, 0)', tooltip: 'rgb(255, 220, 128)' } as Theme; ================================================ FILE: src/common/themes/hotChocolate.ts ================================================ /* "Hot Chocolate" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Hot-Chocolate-654380979 */ import { Theme } from './types'; export default { name: 'hotChocolate', anchor: 'rgb(128, 96, 64)', anchorVisited: 'rgb(128, 96, 64)', borderDark: 'rgb(128, 96, 64)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(181, 143, 106)', borderLightest: 'rgb(219, 200, 181)', canvas: 'rgb(219, 200, 181)', canvasText: 'rgb(57, 43, 28)', canvasTextDisabled: 'rgb(128, 96, 64)', canvasTextDisabledShadow: 'rgb(219, 200, 181)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(57, 43, 28)', checkmarkDisabled: 'rgb(128, 96, 64)', desktopBackground: 'rgb(109, 82, 54)', flatDark: 'rgb(128, 96, 64)', flatLight: 'rgb(181, 143, 106)', focusSecondary: 'rgb(219, 200, 181)', headerBackground: 'linear-gradient(to right, rgb(128, 96, 64), rgb(208, 183, 157))', headerNotActiveBackground: 'linear-gradient(to right, rgb(128, 96, 64), rgb(128, 96, 64))', headerNotActiveText: 'rgb(219, 200, 181)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(128, 96, 64)', material: 'rgb(181, 143, 106)', materialDark: 'rgb(128, 96, 64)', materialText: 'rgb(57, 43, 28)', materialTextDisabled: 'rgb(128, 96, 64)', materialTextDisabledShadow: 'rgb(219, 200, 181)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(128, 96, 64)', tooltip: 'rgb(219, 200, 181)' } as Theme; ================================================ FILE: src/common/themes/hotdogStand.ts ================================================ /* "Hotdog Stand" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Hotdog-Stand-525606846 */ import { Theme } from './types'; export default { name: 'hotdogStand', anchor: 'rgb(255, 255, 0)', anchorVisited: 'rgb(255, 255, 0)', borderDark: 'rgb(0, 0, 0)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(0, 0, 0)', borderLightest: 'rgb(0, 0, 0)', canvas: 'rgb(255, 0, 0)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(0, 0, 0)', canvasTextDisabledShadow: 'rgb(0, 0, 0)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(128, 128, 128)', desktopBackground: 'rgb(255, 255, 0)', flatDark: 'rgb(0, 0, 0)', flatLight: 'rgb(0, 0, 0)', focusSecondary: 'rgb(0, 0, 0)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(0, 0, 0))', headerNotActiveBackground: 'linear-gradient(to right, rgb(255, 0, 0), rgb(255, 0, 0))', headerNotActiveText: 'rgb(255, 255, 255)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 0, 0)', material: 'rgb(255, 0, 0)', materialDark: 'rgb(255, 0, 0)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(0, 0, 0)', materialTextDisabledShadow: 'rgb(0, 0, 0)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 0, 0)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/themes/index.ts ================================================ import aiee from './aiee'; import ash from './ash'; import azureOrange from './azureOrange'; import bee from './bee'; import blackAndWhite from './blackAndWhite'; import blue from './blue'; import brick from './brick'; import candy from './candy'; import cherry from './cherry'; import coldGray from './coldGray'; import counterStrike from './counterStrike'; import darkTeal from './darkTeal'; import denim from './denim'; import eggplant from './eggplant'; import fxDev from './fxDev'; import highContrast from './highContrast'; import honey from './honey'; import hotChocolate from './hotChocolate'; import hotdogStand from './hotdogStand'; import lilac from './lilac'; import lilacRoseDark from './lilacRoseDark'; import maple from './maple'; import marine from './marine'; import matrix from './matrix'; import millenium from './millenium'; import modernDark from './modernDark'; import molecule from './molecule'; import ninjaTurtles from './ninjaTurtles'; import olive from './olive'; import original from './original'; import pamelaAnderson from './pamelaAnderson'; import peggysPastels from './peggysPastels'; import plum from './plum'; import polarized from './polarized'; import powerShell from './powerShell'; import rainyDay from './rainyDay'; import raspberry from './raspberry'; import redWine from './redWine'; import rose from './rose'; import seawater from './seawater'; import shelbiTeal from './shelbiTeal'; import slate from './slate'; import solarizedDark from './solarizedDark'; import solarizedLight from './solarizedLight'; import spruce from './spruce'; import stormClouds from './stormClouds'; import theSixtiesUSA from './theSixtiesUSA'; import tokyoDark from './tokyoDark'; import toner from './toner'; import tooSexy from './tooSexy'; import travel from './travel'; import vaporTeal from './vaporTeal'; import vermillion from './vermillion'; import violetDark from './violetDark'; import vistaesqueMidnight from './vistaesqueMidnight'; import water from './water'; import white from './white'; import windows1 from './windows1'; import wmii from './wmii'; export default { aiee, ash, azureOrange, bee, blackAndWhite, blue, brick, candy, cherry, coldGray, counterStrike, darkTeal, denim, eggplant, fxDev, highContrast, honey, hotChocolate, hotdogStand, lilac, lilacRoseDark, maple, marine, matrix, millenium, modernDark, molecule, ninjaTurtles, olive, original, pamelaAnderson, peggysPastels, plum, polarized, powerShell, rainyDay, raspberry, redWine, rose, seawater, shelbiTeal, slate, solarizedDark, solarizedLight, spruce, stormClouds, theSixtiesUSA, tokyoDark, toner, tooSexy, travel, vaporTeal, vermillion, violetDark, vistaesqueMidnight, water, white, windows1, wmii }; ================================================ FILE: src/common/themes/lilac.ts ================================================ import { Theme } from './types'; export default { name: 'lilac', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#5f549b', borderDarkest: '#1c1449', borderLight: '#bcb4e9', borderLightest: '#d3ccf4', canvas: '#ffffff', canvasText: '#050608', canvasTextDisabled: '#5f549b', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#050608', checkmarkDisabled: '#5f549b', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#5e4dba', headerNotActiveBackground: '#7f7f81', headerNotActiveText: '#ced0cf', headerText: '#ffffff', hoverBackground: '#5e4dba', material: '#b1a7df', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#5f549b', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#5e4dba', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/lilacRoseDark.ts ================================================ import { Theme } from './types'; export default { name: 'lilacRoseDark', anchor: '#a65387', anchorVisited: '#440381', background: '#3B3B58', borderDark: '#7F3163', borderDarkest: '#190000', borderLight: '#E597C9', borderLightest: '#FFCAFC', canvas: '#dab1c7', canvasText: '#000000', canvasTextDisabled: '#000000', canvasTextDisabledShadow: '#000000', canvasTextInvert: '#ecbfe3', checkmark: '#010601', checkmarkDisabled: '#7F3163', desktopBackground: '#663956', flatDark: '#7F3163', flatLight: '#E597C9', focusSecondary: '#fefe03', headerBackground: '#4C0030', headerNotActiveBackground: '#763a60', headerNotActiveText: '#b26496', headerText: '#010601', hoverBackground: '#713259', material: '#b26496', materialDark: '#763a60', materialText: '#000000', materialTextDisabled: '#82416d', materialTextDisabledShadow: '#ecbfe3', materialTextInvert: '#ecbfe3', progress: '#713259', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/maple.ts ================================================ import { Theme } from './types'; export default { name: 'maple', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#ab9042', borderDarkest: '#2a1801', borderLight: '#f5e2bb', borderLightest: '#ffffff', canvas: '#ffffff', canvasText: '#2a1801', canvasTextDisabled: '#ab9042', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#2a1801', checkmarkDisabled: '#ab9042', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#8e0101', headerNotActiveBackground: '#a1a0a5', headerNotActiveText: '#f5e2bb', headerText: '#ffffff', hoverBackground: '#8e0101', material: '#e5cc90', materialDark: '#9a9e9c', materialText: '#2a1801', materialTextDisabled: '#ab9042', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#8e0101', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/marine.ts ================================================ import { Theme } from './types'; export default { name: 'marine', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#3c8d88', borderDarkest: '#050608', borderLight: '#98d2cb', borderLightest: '#b1dfdf', canvas: '#c3e2da', canvasText: '#050608', canvasTextDisabled: '#3c8d88', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#050608', checkmarkDisabled: '#3c8d88', desktopBackground: '#2c4e47', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#000080', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#ced0cf', headerText: '#ffffff', hoverBackground: '#000080', material: '#75c1ba', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#3c8d88', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#000080', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/matrix.ts ================================================ import { Theme } from './types'; export default { name: 'matrix', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#282828', borderDarkest: '#000000', borderLight: '#656565', borderLightest: '#a7a7a7', canvas: '#c0c0c0', canvasText: '#000000', canvasTextDisabled: '#282828', canvasTextDisabledShadow: '#ff0000', canvasTextInvert: '#ffffff', checkmark: '#000000', checkmarkDisabled: '#282828', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#35FF69', headerBackground: '#000000', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#535353', headerText: '#a7a7a7', hoverBackground: '#000000', material: '#535353', materialDark: '#282828', materialText: '#35FF69', materialTextDisabled: '#282828', materialTextDisabledShadow: '#a7a7a7', materialTextInvert: '#ffffff', progress: '#000000', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/millenium.ts ================================================ import { Theme } from './types'; export default { name: 'millenium', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#828282', borderDarkest: 'black', borderLight: '#e5e5e5', borderLightest: '#ffffff', canvas: '#ffffff', canvasText: 'black', canvasTextDisabled: '#828282', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: 'black', checkmarkDisabled: '#828282', desktopBackground: '#3a6ea5', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: 'linear-gradient(to right, #012470, #a5c7e7)', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#d6cfc7', headerText: '#ffffff', hoverBackground: '#00256e', material: '#d6cfc7', materialDark: '#9a9e9c', materialText: 'black', materialTextDisabled: '#828282', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#00256e', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/modernDark.ts ================================================ import { Theme } from './types'; export default { name: 'modernDark', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#121317', borderDarkest: '#000000', borderLight: '#31323c', borderLightest: '#4b4d58', canvas: '#4b4d58', canvasText: '#000000', canvasTextDisabled: '#4b4d58', canvasTextDisabledShadow: '#4b4d58', canvasTextInvert: '#202127', checkmark: '#000000', checkmarkDisabled: '#121317', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#4b4d58', headerNotActiveBackground: 'transparent', headerNotActiveText: '#4b4d58', headerText: '#202127', hoverBackground: '#f88702', material: '#202127', materialDark: '#9a9e9c', materialText: '#f88702', materialTextDisabled: '#4b4d58', materialTextDisabledShadow: '#121317', materialTextInvert: '#202127', progress: '#f88702', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/molecule.ts ================================================ import { Theme } from './types'; export default { name: 'molecule', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#993845', borderDarkest: '#4b4d4e', borderLight: '#dfe0e3', borderLightest: '#d79099', canvas: '#f1f5f6', canvasText: '#020102', canvasTextDisabled: '#993845', canvasTextDisabledShadow: '#d79099', canvasTextInvert: '#f1f5f6', checkmark: '#020102', checkmarkDisabled: '#993845', desktopBackground: '#3a6ea5', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#a03d49', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#c2c1c2', headerText: '#f1f5f6', hoverBackground: '#70a3ce', material: '#c2c1c2', materialDark: '#9a9e9c', materialText: '#020102', materialTextDisabled: '#993845', materialTextDisabledShadow: '#d79099', materialTextInvert: '#f1f5f6', progress: '#a03d49', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/monochrome.ts ================================================ ================================================ FILE: src/common/themes/ninjaTurtles.ts ================================================ import { Theme } from './types'; export default { name: 'ninjaTurtles', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#017401', borderDarkest: '#000000', borderLight: '#1dbc1b', borderLightest: '#55fd55', canvas: '#ffffff', canvasText: '#000000', canvasTextDisabled: '#017401', canvasTextDisabledShadow: '#55fd55', canvasTextInvert: '#000000', checkmark: '#000000', checkmarkDisabled: '#017401', desktopBackground: '#045424', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#FF1D15', headerNotActiveBackground: '#7f7f7f', headerNotActiveText: '#000000', headerText: '#ffffff', hoverBackground: '#FABC3C', material: '#00a800', materialDark: '#9a9e9c', materialText: '#000000', materialTextDisabled: '#017401', materialTextDisabledShadow: '#55fd55', materialTextInvert: '#000000', progress: '#FF1D15', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/olive.ts ================================================ import { Theme } from './types'; export default { name: 'olive', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#4f4c02', borderDarkest: '#000000', borderLight: '#9d9d11', borderLightest: '#fcfd3e', canvas: '#ffffff', canvasText: '#000000', canvasTextDisabled: '#4f4c02', canvasTextDisabledShadow: '#fcfd3e', canvasTextInvert: '#000000', checkmark: '#000000', checkmarkDisabled: '#4f4c02', desktopBackground: '#666633', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#000000', headerBackground: '#F3DE2C', headerNotActiveBackground: '#4f4c02', headerNotActiveText: '#807f00', headerText: '#000000', hoverBackground: '#F3DE2C', material: '#807f00', materialDark: '#4f4c02', materialText: '#000000', materialTextDisabled: '#4f4c02', materialTextDisabledShadow: '#fcfd3e', materialTextInvert: '#000000', progress: '#F3DE2C', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/original.ts ================================================ import { Theme } from './types'; export default { name: 'original', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#848584', borderDarkest: '#0a0a0a', borderLight: '#dfdfdf', borderLightest: '#fefefe', canvas: '#ffffff', canvasText: '#0a0a0a', canvasTextDisabled: '#848584', canvasTextDisabledShadow: '#fefefe', canvasTextInvert: '#fefefe', checkmark: '#0a0a0a', checkmarkDisabled: '#848584', desktopBackground: '#008080', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#060084', headerNotActiveBackground: '#7f787f', headerNotActiveText: '#c6c6c6', headerText: '#fefefe', hoverBackground: '#060084', material: '#c6c6c6', materialDark: '#9a9e9c', materialText: '#0a0a0a', materialTextDisabled: '#848584', materialTextDisabledShadow: '#fefefe', materialTextInvert: '#fefefe', progress: '#060084', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/pamelaAnderson.ts ================================================ import { Theme } from './types'; export default { name: 'pamelaAnderson', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#7e0541', borderDarkest: '#000000', borderLight: '#ff308f', borderLightest: '#ff7ebf', canvas: '#F5CCE8', canvasText: '#000000', canvasTextDisabled: '#7e0541', canvasTextDisabledShadow: '#ff7ebf', canvasTextInvert: '#F1E4E8', checkmark: '#000000', checkmarkDisabled: '#7e0541', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#FF8CC6', headerNotActiveBackground: '#95818D', headerNotActiveText: '#ff0080', headerText: '#000000', hoverBackground: '#004FFF', material: '#ff0080', materialDark: '#95818D', materialText: '#000000', materialTextDisabled: '#7e0541', materialTextDisabledShadow: '#ff7ebf', materialTextInvert: '#F1E4E8', progress: '#004FFF', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/peggysPastels.ts ================================================ /* "Peggy's Pastels" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Peggy-s-Pastels-505540096 */ import { Theme } from './types'; export default { name: 'peggysPastels', anchor: 'rgb(0, 128, 128)', anchorVisited: 'rgb(0, 128, 128)', borderDark: 'rgb(222, 69, 96)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(247, 219, 215)', borderLightest: 'rgb(250, 224, 228)', canvas: 'rgb(244, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(222, 69, 96)', canvasTextDisabledShadow: 'rgb(250, 224, 228)', canvasTextInvert: 'rgb(0, 0, 0)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(222, 69, 96)', desktopBackground: 'rgb(162, 219, 210)', flatDark: 'rgb(222, 69, 96)', flatLight: 'rgb(247, 219, 215)', focusSecondary: 'rgb(250, 224, 228)', headerBackground: 'linear-gradient(to right, rgb(0, 191, 188), rgb(202, 156, 185))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 187, 169), rgb(236, 145, 162))', headerNotActiveText: 'rgb(0, 85, 77)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(162, 219, 210)', material: 'rgb(244, 193, 202)', materialDark: 'rgb(0, 187, 169)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(222, 69, 96)', materialTextDisabledShadow: 'rgb(250, 224, 228)', materialTextInvert: 'rgb(0, 0, 0)', progress: 'rgb(162, 219, 210)', tooltip: 'rgb(204, 255, 255)' } as Theme; ================================================ FILE: src/common/themes/plum.ts ================================================ import { Theme } from './types'; export default { name: 'plum', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#7b5f5b', borderDarkest: '#050608', borderLight: '#c3b1aa', borderLightest: '#e8dad6', canvas: '#dad0c7', canvasText: '#050608', canvasTextDisabled: '#7b5f5b', canvasTextDisabledShadow: '#e8dad6', canvasTextInvert: '#e8dad6', checkmark: '#050608', checkmarkDisabled: '#7b5f5b', desktopBackground: '#402840', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#483f63', headerNotActiveBackground: '#7d5e58', headerNotActiveText: '#e8dad6', headerText: '#ffffff', hoverBackground: '#483f63', material: '#ac978f', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#7b5f5b', materialTextDisabledShadow: '#e8dad6', materialTextInvert: '#ffffff', progress: '#483f63', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/polarized.ts ================================================ /* "Polarized" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Polarized-557712217 */ import { Theme } from './types'; export default { name: 'polarized', anchor: 'rgb(160, 160, 128)', anchorVisited: 'rgb(160, 160, 128)', borderDark: 'rgb(60, 60, 194)', borderDarkest: 'rgb(60, 60, 194)', borderLight: 'rgb(60, 60, 194)', borderLightest: 'rgb(60, 60, 194)', canvas: 'rgb(52, 52, 204)', canvasText: 'rgb(255, 255, 0)', canvasTextDisabled: 'rgb(60, 60, 194)', canvasTextDisabledShadow: 'rgb(60, 60, 194)', canvasTextInvert: 'rgb(0, 0, 255)', checkmark: 'rgb(255, 255, 0)', checkmarkDisabled: 'rgb(101, 101, 154)', desktopBackground: 'rgb(180, 180, 76)', flatDark: 'rgb(60, 60, 194)', flatLight: 'rgb(60, 60, 194)', focusSecondary: 'rgb(60, 60, 194)', headerBackground: 'linear-gradient(to right, rgb(192, 192, 64), rgb(192, 192, 64))', headerNotActiveBackground: 'linear-gradient(to right, rgb(64, 64, 192), rgb(170, 170, 84))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(0, 0, 255)', hoverBackground: 'rgb(192, 192, 64)', material: 'rgb(120, 120, 136)', materialDark: 'rgb(64, 64, 192)', materialText: 'rgb(255, 255, 0)', materialTextDisabled: 'rgb(60, 60, 194)', materialTextDisabledShadow: 'rgb(60, 60, 194)', materialTextInvert: 'rgb(0, 0, 255)', progress: 'rgb(192, 192, 64)', tooltip: 'rgb(16, 16, 240)' } as Theme; ================================================ FILE: src/common/themes/powerShell.ts ================================================ /* "PowerShell" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/PowerShell-646065752 */ import { Theme } from './types'; export default { name: 'powerShell', anchor: 'rgb(0, 192, 0)', anchorVisited: 'rgb(0, 192, 0)', borderDark: 'rgb(128, 128, 128)', borderDarkest: 'rgb(128, 128, 128)', borderLight: 'rgb(128, 128, 128)', borderLightest: 'rgb(128, 128, 128)', canvas: 'rgb(1, 36, 86)', canvasText: 'rgb(238, 237, 240)', canvasTextDisabled: 'rgb(128, 128, 128)', canvasTextDisabledShadow: 'rgb(128, 128, 128)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(238, 237, 240)', checkmarkDisabled: 'rgb(180, 180, 180)', desktopBackground: 'rgb(1, 36, 86)', flatDark: 'rgb(128, 128, 128)', flatLight: 'rgb(128, 128, 128)', focusSecondary: 'rgb(128, 128, 128)', headerBackground: 'linear-gradient(to right, rgb(1, 36, 86), rgb(1, 36, 86))', headerNotActiveBackground: 'linear-gradient(to right, rgb(1, 36, 86), rgb(1, 36, 86))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 128, 128)', material: 'rgb(1, 36, 86)', materialDark: 'rgb(1, 36, 86)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(128, 128, 128)', materialTextDisabledShadow: 'rgb(128, 128, 128)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 128, 128)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/themes/rainyDay.ts ================================================ import { Theme } from './types'; export default { name: 'rainyDay', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#3d5367', borderDarkest: '#16233b', borderLight: '#91abc2', borderLightest: '#b7cee5', canvas: '#ffffff', canvasText: '#050608', canvasTextDisabled: '#3d5367', canvasTextDisabledShadow: '#b7cee5', canvasTextInvert: '#ffffff', checkmark: '#050608', checkmarkDisabled: '#3d5367', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#4b6480', headerNotActiveBackground: '#7f7f81', headerNotActiveText: '#ced0d9', headerText: '#ffffff', hoverBackground: '#4b6480', material: '#7a99b3', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#3d5367', materialTextDisabledShadow: '#b7cee5', materialTextInvert: '#ffffff', progress: '#4b6480', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/raspberry.ts ================================================ /* "Raspberry" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Raspberry-539289720 */ import { Theme } from './types'; export default { name: 'raspberry', anchor: 'rgb(10, 36, 106)', anchorVisited: 'rgb(10, 36, 106)', borderDark: 'rgb(92, 101, 107)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(142, 151, 157)', borderLightest: 'rgb(200, 204, 206)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(92, 101, 107)', canvasTextDisabledShadow: 'rgb(200, 204, 206)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(92, 101, 107)', desktopBackground: 'rgb(0, 64, 128)', flatDark: 'rgb(92, 101, 107)', flatLight: 'rgb(142, 151, 157)', focusSecondary: 'rgb(200, 204, 206)', headerBackground: 'linear-gradient(to right, rgb(10, 36, 106), rgb(153, 0, 51))', headerNotActiveBackground: 'linear-gradient(to right, rgb(92, 101, 107), rgb(92, 101, 107))', headerNotActiveText: 'rgb(212, 208, 200)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(10, 36, 106)', material: 'rgb(142, 151, 157)', materialDark: 'rgb(92, 101, 107)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(92, 101, 107)', materialTextDisabledShadow: 'rgb(200, 204, 206)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(10, 36, 106)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/themes/redWine.ts ================================================ /* "Red Wine" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Red-Wine-545729607 */ import { Theme } from './types'; export default { name: 'redWine', anchor: 'rgb(255, 0, 0)', anchorVisited: 'rgb(255, 0, 0)', borderDark: 'rgb(67, 9, 5)', borderDarkest: 'rgb(64, 32, 32)', borderLight: 'rgb(102, 12, 8)', borderLightest: 'rgb(192, 64, 56)', canvas: 'rgb(64, 0, 0)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(67, 9, 5)', canvasTextDisabledShadow: 'rgb(192, 64, 56)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(192, 64, 64)', desktopBackground: 'rgb(147, 9, 0)', flatDark: 'rgb(67, 9, 5)', flatLight: 'rgb(102, 12, 8)', focusSecondary: 'rgb(192, 64, 56)', headerBackground: 'linear-gradient(to right, rgb(64, 0, 12), rgb(217, 11, 0))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(134, 4, 2))', headerNotActiveText: 'rgb(239, 44, 33)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(192, 0, 0)', material: 'rgb(102, 12, 8)', materialDark: 'rgb(0, 0, 0)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(67, 9, 5)', materialTextDisabledShadow: 'rgb(192, 64, 56)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(192, 0, 0)', tooltip: 'rgb(64, 0, 0)' } as Theme; ================================================ FILE: src/common/themes/rose.ts ================================================ import { Theme } from './types'; export default { name: 'rose', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#8a5b68', borderDarkest: '#26030b', borderLight: '#e5bec8', borderLightest: '#f1d4dc', canvas: '#ffffff', canvasText: '#050608', canvasTextDisabled: '#8a5b68', canvasTextDisabledShadow: '#f1d4dc', canvasTextInvert: '#ffffff', checkmark: '#050608', checkmarkDisabled: '#8a5b68', desktopBackground: '#808080', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#ab5a71', headerNotActiveBackground: '#a19fa5', headerNotActiveText: '#615f68', headerText: '#ffffff', hoverBackground: '#ab5a71', material: '#d6adb8', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#8a5b68', materialTextDisabledShadow: '#f1d4dc', materialTextInvert: '#ffffff', progress: '#ab5a71', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/seawater.ts ================================================ /* "Seawater" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Seawater-736002425 */ import { Theme } from './types'; export default { name: 'seawater', anchor: 'rgb(0, 0, 128)', anchorVisited: 'rgb(0, 0, 128)', borderDark: 'rgb(167, 194, 224)', borderDarkest: 'rgb(167, 194, 224)', borderLight: 'rgb(167, 194, 224)', borderLightest: 'rgb(167, 194, 224)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(167, 194, 224)', canvasTextDisabledShadow: 'rgb(167, 194, 224)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(47, 88, 134)', desktopBackground: 'rgb(0, 128, 128)', flatDark: 'rgb(167, 194, 224)', flatLight: 'rgb(167, 194, 224)', focusSecondary: 'rgb(167, 194, 224)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 128), rgb(0, 0, 128))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 85, 170), rgb(0, 85, 170))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 128, 128)', material: 'rgb(79, 133, 193)', materialDark: 'rgb(0, 85, 170)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(167, 194, 224)', materialTextDisabledShadow: 'rgb(167, 194, 224)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 128, 128)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/themes/shelbiTeal.ts ================================================ /* "Teal for Shelbi" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Teal-for-Shelbi-506118460 */ import { Theme } from './types'; export default { name: 'shelbiTeal', anchor: 'rgb(0, 128, 128)', anchorVisited: 'rgb(0, 128, 128)', borderDark: 'rgb(96, 128, 128)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(192, 204, 204)', borderLightest: 'rgb(204, 224, 224)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(96, 128, 128)', canvasTextDisabledShadow: 'rgb(204, 224, 224)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(96, 128, 128)', desktopBackground: 'rgb(0, 128, 128)', flatDark: 'rgb(96, 128, 128)', flatLight: 'rgb(192, 204, 204)', focusSecondary: 'rgb(204, 224, 224)', headerBackground: 'linear-gradient(to right, rgb(0, 128, 128), rgb(0, 204, 204))', headerNotActiveBackground: 'linear-gradient(to right, rgb(96, 128, 128), rgb(160, 192, 192))', headerNotActiveText: 'rgb(192, 204, 204)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 128, 128)', material: 'rgb(168, 192, 192)', materialDark: 'rgb(96, 128, 128)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(96, 128, 128)', materialTextDisabledShadow: 'rgb(204, 224, 224)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 128, 128)', tooltip: 'rgb(224, 255, 255)' } as Theme; ================================================ FILE: src/common/themes/slate.ts ================================================ import { Theme } from './types'; export default { name: 'slate', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#446b7c', borderDarkest: '#000814', borderLight: '#adc8da', borderLightest: '#c3d9e9', canvas: '#f2ffff', canvasText: '#00010f', canvasTextDisabled: '#446b7c', canvasTextDisabledShadow: '#c3d9e9', canvasTextInvert: '#f2ffff', checkmark: '#00010f', checkmarkDisabled: '#446b7c', desktopBackground: '#414141', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#448199', headerNotActiveBackground: '#807f80', headerNotActiveText: '#c2c1c2', headerText: '#f2ffff', hoverBackground: '#448199', material: '#97b9cb', materialDark: '#9a9e9c', materialText: '#00010f', materialTextDisabled: '#446b7c', materialTextDisabledShadow: '#c3d9e9', materialTextInvert: '#f2ffff', progress: '#448199', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/solarizedDark.ts ================================================ /* "Solarized Dark" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Solarized-Dark-592122068 */ import { Theme } from './types'; export default { name: 'solarizedDark', anchor: 'rgb(38, 139, 210)', anchorVisited: 'rgb(38, 139, 210)', borderDark: 'rgb(0, 43, 54)', borderDarkest: 'rgb(0, 43, 54)', borderLight: 'rgb(0, 43, 54)', borderLightest: 'rgb(0, 43, 54)', canvas: 'rgb(0, 43, 54)', canvasText: 'rgb(131, 148, 150)', canvasTextDisabled: 'rgb(0, 43, 54)', canvasTextDisabledShadow: 'rgb(0, 43, 54)', canvasTextInvert: 'rgb(238, 232, 213)', checkmark: 'rgb(131, 148, 150)', checkmarkDisabled: 'rgb(88, 110, 117)', desktopBackground: 'rgb(0, 43, 54)', flatDark: 'rgb(0, 43, 54)', flatLight: 'rgb(0, 43, 54)', focusSecondary: 'rgb(0, 43, 54)', headerBackground: 'linear-gradient(to right, rgb(0, 43, 54), rgb(0, 43, 54))', headerNotActiveBackground: 'linear-gradient(to right, rgb(7, 54, 66), rgb(7, 54, 66))', headerNotActiveText: 'rgb(131, 148, 150)', headerText: 'rgb(108, 113, 196)', hoverBackground: 'rgb(211, 54, 130)', material: 'rgb(7, 54, 66)', materialDark: 'rgb(7, 54, 66)', materialText: 'rgb(131, 148, 150)', materialTextDisabled: 'rgb(0, 43, 54)', materialTextDisabledShadow: 'rgb(0, 43, 54)', materialTextInvert: 'rgb(238, 232, 213)', progress: 'rgb(211, 54, 130)', tooltip: 'rgb(253, 246, 227)' } as Theme; ================================================ FILE: src/common/themes/solarizedLight.ts ================================================ /* "Solarized Light" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Solarized-Light-592124935 */ import { Theme } from './types'; export default { name: 'solarizedLight', anchor: 'rgb(38, 139, 210)', anchorVisited: 'rgb(38, 139, 210)', borderDark: 'rgb(253, 246, 227)', borderDarkest: 'rgb(253, 246, 227)', borderLight: 'rgb(253, 246, 227)', borderLightest: 'rgb(253, 246, 227)', canvas: 'rgb(253, 246, 227)', canvasText: 'rgb(101, 123, 131)', canvasTextDisabled: 'rgb(253, 246, 227)', canvasTextDisabledShadow: 'rgb(253, 246, 227)', canvasTextInvert: 'rgb(238, 232, 213)', checkmark: 'rgb(101, 123, 131)', checkmarkDisabled: 'rgb(88, 110, 117)', desktopBackground: 'rgb(253, 246, 227)', flatDark: 'rgb(253, 246, 227)', flatLight: 'rgb(253, 246, 227)', focusSecondary: 'rgb(253, 246, 227)', headerBackground: 'linear-gradient(to right, rgb(253, 246, 227), rgb(253, 246, 227))', headerNotActiveBackground: 'linear-gradient(to right, rgb(238, 232, 213), rgb(238, 232, 213))', headerNotActiveText: 'rgb(101, 123, 131)', headerText: 'rgb(108, 113, 196)', hoverBackground: 'rgb(211, 54, 130)', material: 'rgb(238, 232, 213)', materialDark: 'rgb(238, 232, 213)', materialText: 'rgb(101, 123, 131)', materialTextDisabled: 'rgb(253, 246, 227)', materialTextDisabledShadow: 'rgb(253, 246, 227)', materialTextInvert: 'rgb(238, 232, 213)', progress: 'rgb(211, 54, 130)', tooltip: 'rgb(253, 246, 227)' } as Theme; ================================================ FILE: src/common/themes/spruce.ts ================================================ import { Theme } from './types'; export default { name: 'spruce', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#477b5e', borderDarkest: '#001004', borderLight: '#b0d2bb', borderLightest: '#cdead2', canvas: '#fcfff6', canvasText: '#050608', canvasTextDisabled: '#3d5367', canvasTextDisabledShadow: '#cdead2', canvasTextInvert: '#fcfff6', checkmark: '#050608', checkmarkDisabled: '#477b5e', desktopBackground: '#213f21', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#3d9961', headerNotActiveBackground: '#807f80', headerNotActiveText: '#d4deda', headerText: '#fcfff6', hoverBackground: '#3d9961', material: '#99c9a8', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#3d5367', materialTextDisabledShadow: '#cdead2', materialTextInvert: '#fcfff6', progress: '#3d9961', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/stormClouds.ts ================================================ /* "Storm Clouds" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Storm-Clouds-613198674 */ import { Theme } from './types'; export default { name: 'stormClouds', anchor: 'rgb(64, 64, 255)', anchorVisited: 'rgb(64, 64, 255)', borderDark: 'rgb(47, 52, 53)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(70, 80, 81)', borderLightest: 'rgb(159, 171, 172)', canvas: 'rgb(47, 52, 53)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(47, 52, 53)', canvasTextDisabledShadow: 'rgb(159, 171, 172)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(128, 128, 128)', desktopBackground: 'rgb(45, 87, 87)', flatDark: 'rgb(47, 52, 53)', flatLight: 'rgb(70, 80, 81)', focusSecondary: 'rgb(159, 171, 172)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 168), rgb(16, 132, 208))', headerNotActiveBackground: 'linear-gradient(to right, rgb(47, 52, 53), rgb(128, 128, 128))', headerNotActiveText: 'rgb(192, 199, 200)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(128, 128, 0)', material: 'rgb(70, 80, 81)', materialDark: 'rgb(47, 52, 53)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(47, 52, 53)', materialTextDisabledShadow: 'rgb(159, 171, 172)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(128, 128, 0)', tooltip: 'rgb(48, 64, 80)' } as Theme; ================================================ FILE: src/common/themes/theSixtiesUSA.ts ================================================ import { Theme } from './types'; export default { name: 'theSixtiesUSA', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#6c1f71', borderDarkest: '#010001', borderLight: '#d982de', borderLightest: '#df9be7', canvas: '#ffffff', canvasText: '#010001', canvasTextDisabled: '#6c1f71', canvasTextDisabledShadow: '#df9be7', canvasTextInvert: '#010001', checkmark: '#010001', checkmarkDisabled: '#6c1f71', desktopBackground: '#92458a', // original: #000000 flatDark: '#d067d7', flatLight: '#df9be7', focusSecondary: '#fefe03', headerBackground: '#050080', headerNotActiveBackground: '#a130a9', headerNotActiveText: '#df9be7', headerText: '#ffffff', hoverBackground: '#0f0', material: '#d067d7', materialDark: '#9a9e9c', materialText: '#010001', materialTextDisabled: '#6c1f71', materialTextDisabledShadow: '#df9be7', materialTextInvert: '#010001', progress: '#0f0', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/tokyoDark.ts ================================================ import { Theme } from './types'; export default { name: 'tokyoDark', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#1f2223', borderDarkest: '#070809', borderLight: '#5e696a', borderLightest: '#93a0a1', canvas: '#2f3435', canvasText: '#F4F4ED', canvasTextDisabled: '#1f2223', canvasTextDisabledShadow: '#93a0a1', canvasTextInvert: '#ffffff', checkmark: '#F4F4ED', checkmarkDisabled: '#1f2223', desktopBackground: '#181a1b', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#20FC8F', headerBackground: '#1f2223', headerNotActiveBackground: '#5e696a', headerNotActiveText: '#F4F4ED', headerText: '#F4F4ED', hoverBackground: '#F61067', material: '#465051', materialDark: '#1f2223', materialText: '#F4F4ED', materialTextDisabled: '#1f2223', materialTextDisabledShadow: '#93a0a1', materialTextInvert: '#ffffff', progress: '#F61067', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/toner.ts ================================================ /* "Toner" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Toner-871968986 */ import { Theme } from './types'; export default { name: 'toner', anchor: 'rgb(0, 0, 128)', anchorVisited: 'rgb(0, 0, 128)', borderDark: 'rgb(0, 0, 0)', borderDarkest: 'rgb(128, 128, 128)', borderLight: 'rgb(0, 0, 0)', borderLightest: 'rgb(127, 127, 127)', canvas: 'rgb(128, 128, 128)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(0, 0, 0)', canvasTextDisabledShadow: 'rgb(127, 127, 127)', canvasTextInvert: 'rgb(0, 0, 0)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(64, 64, 64)', desktopBackground: 'rgb(128, 128, 128)', flatDark: 'rgb(0, 0, 0)', flatLight: 'rgb(0, 0, 0)', focusSecondary: 'rgb(127, 127, 127)', headerBackground: 'linear-gradient(to right, rgb(128, 128, 128), rgb(128, 128, 128))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(128, 128, 128))', headerNotActiveText: 'rgb(128, 128, 128)', headerText: 'rgb(0, 0, 0)', hoverBackground: 'rgb(255, 255, 255)', material: 'rgb(0, 0, 0)', materialDark: 'rgb(0, 0, 0)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(0, 0, 0)', materialTextDisabledShadow: 'rgb(127, 127, 127)', materialTextInvert: 'rgb(0, 0, 0)', progress: 'rgb(255, 255, 255)', tooltip: 'rgb(128, 128, 128)' } as Theme; ================================================ FILE: src/common/themes/tooSexy.ts ================================================ import { Theme } from './types'; export default { name: 'tooSexy', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#5a0302', borderDarkest: '#000000', borderLight: '#c81d19', borderLightest: '#fe5757', canvas: '#FFF1D0', canvasText: '#000000', canvasTextDisabled: '#5a0302', canvasTextDisabledShadow: '#FFF1D0', canvasTextInvert: '#ffffff', checkmark: '#000000', checkmarkDisabled: '#5a0302', desktopBackground: '#000000', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#161B33', headerNotActiveBackground: '#5a0302', headerNotActiveText: '#B80100', headerText: '#FFF1D0', hoverBackground: '#474973', material: '#B80100', materialDark: '#9a9e9c', materialText: '#000000', materialTextDisabled: '#5a0302', materialTextDisabledShadow: '#fe5757', materialTextInvert: '#ffffff', progress: '#474973', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/travel.ts ================================================ import { Theme } from './types'; export default { name: 'travel', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#695f50', borderDarkest: '#28251e', borderLight: '#9d8f80', borderLightest: '#baae9f', canvas: '#d8d0c8', canvasText: '#28251e', canvasTextDisabled: '#695f50', canvasTextDisabledShadow: '#baae9f', canvasTextInvert: '#ffffff', checkmark: '#28251e', checkmarkDisabled: '#695f50', desktopBackground: '#7c654c', // original: #000000 flatDark: '#695f50', flatLight: '#9d8f80', focusSecondary: '#fefe03', headerBackground: '#404878', headerNotActiveBackground: '#605848', headerNotActiveText: '#908070', headerText: '#d8d0c8', hoverBackground: '#48604f', material: '#908070', materialDark: '#9a9e9c', materialText: '#28251e', materialTextDisabled: '#695f50', materialTextDisabledShadow: '#baae9f', materialTextInvert: '#ffffff', progress: '#48604f', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/types.ts ================================================ export type Color = string; export type Theme = { name: string; anchor: Color; anchorVisited: Color; borderDark: Color; borderDarkest: Color; borderLight: Color; borderLightest: Color; canvas: Color; canvasText: Color; canvasTextDisabled: Color; canvasTextDisabledShadow: Color; canvasTextInvert: Color; checkmark: Color; checkmarkDisabled: Color; desktopBackground: Color; flatDark: Color; flatLight: Color; focusSecondary: Color; headerBackground: Color; headerNotActiveBackground: Color; headerNotActiveText: Color; headerText: Color; hoverBackground: Color; material: Color; materialDark: Color; materialText: Color; materialTextDisabled: Color; materialTextDisabledShadow: Color; materialTextInvert: Color; progress: Color; tooltip: Color; }; export type WindowsTheme = { ActiveBorder: Color; ActiveTitle: Color; AppWorkspace: Color; Background: Color; ButtonAlternateFace: Color; ButtonDkShadow: Color; ButtonFace: Color; ButtonHilight: Color; ButtonLight: Color; ButtonShadow: Color; ButtonText: Color; GradientActiveTitle: Color; GradientInactiveTitle: Color; GrayText: Color; Hilight: Color; HilightText: Color; HotTrackingColor: Color; InactiveBorder: Color; InactiveTitle: Color; InactiveTitleText: Color; InfoText: Color; InfoWindow: Color; Menu: Color; MenuBar: Color; MenuHilight: Color; MenuText: Color; Scrollbar: Color; TitleText: Color; Window: Color; WindowFrame: Color; WindowText: Color; }; ================================================ FILE: src/common/themes/vaporTeal.ts ================================================ import { Theme } from './types'; export default { name: 'vaporTeal', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#00706f', borderDarkest: '#000000', borderLight: '#2fcecd', borderLightest: '#58ffff', canvas: '#98DFEA', canvasText: '#000000', canvasTextDisabled: '#00706f', canvasTextDisabledShadow: '#58ffff', canvasTextInvert: '#000000', checkmark: '#000000', checkmarkDisabled: '#00706f', desktopBackground: '#008080', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#FCF6BD', headerBackground: '#246A73', headerNotActiveBackground: '#2fcecd', headerNotActiveText: '#246A73', headerText: '#58ffff', hoverBackground: '#FF99C8', material: '#01a8a8', materialDark: '#246A73', materialText: '#000000', materialTextDisabled: '#00706f', materialTextDisabledShadow: '#58ffff', materialTextInvert: '#000000', progress: '#FF99C8', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/vermillion.ts ================================================ import { Theme } from './types'; export default { name: 'vermillion', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#7f2120', borderDarkest: '#130405', borderLight: '#d25051', borderLightest: '#e59697', canvas: '#EFE9F4', canvasText: '#130405', canvasTextDisabled: '#7f2120', canvasTextDisabledShadow: '#e59697', canvasTextInvert: '#EFE9F4', checkmark: '#130405', checkmarkDisabled: '#7f2120', desktopBackground: '#b22930', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#000103', headerNotActiveBackground: '#7f2120', headerNotActiveText: '#EFE9F4', headerText: '#EFE9F4', hoverBackground: '#000103', material: '#cf4545', materialDark: '#7f2120', materialText: '#130405', materialTextDisabled: '#7f2120', materialTextDisabledShadow: '#e59697', materialTextInvert: '#EFE9F4', progress: '#000103', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/violetDark.ts ================================================ import { Theme } from './types'; export default { name: 'violetDark', anchor: '#1034a6', anchorVisited: '#440381', borderDark: '#3c1f3e', borderDarkest: '#18051a', borderLight: '#945b9b', borderLightest: '#c47bcc', canvas: '#c47bcc', canvasText: '#18051a', canvasTextDisabled: '#000000', canvasTextDisabledShadow: '#000000', canvasTextInvert: '#c57ece', checkmark: '#000000', checkmarkDisabled: '#3c1f3e', desktopBackground: '#3b1940', flatDark: '#3c1f3e', flatLight: '#945b9b', focusSecondary: '#fefe03', headerBackground: '#1034a6', headerNotActiveBackground: '#210e23', headerNotActiveText: '#652a6d', headerText: '#010601', hoverBackground: '#512155', material: '#652a6d', materialDark: '#210e23', materialText: '#c57ece', materialTextDisabled: '#3c1f3e', materialTextDisabledShadow: '#c47bcc', materialTextInvert: '#c47bcc', progress: '#000080', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/vistaesqueMidnight.ts ================================================ /* "Vista-esque Midnight" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Vista-esque-Midnight-557498234 */ import { Theme } from './types'; export default { name: 'vistaesqueMidnight', anchor: 'rgb(64, 64, 192)', anchorVisited: 'rgb(64, 64, 192)', borderDark: 'rgb(21, 21, 21)', borderDarkest: 'rgb(0, 0, 0)', borderLight: 'rgb(32, 32, 32)', borderLightest: 'rgb(128, 128, 128)', canvas: 'rgb(0, 0, 0)', canvasText: 'rgb(255, 255, 255)', canvasTextDisabled: 'rgb(21, 21, 21)', canvasTextDisabledShadow: 'rgb(128, 128, 128)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(255, 255, 255)', checkmarkDisabled: 'rgb(32, 32, 32)', desktopBackground: 'rgb(31, 60, 89)', flatDark: 'rgb(21, 21, 21)', flatLight: 'rgb(32, 32, 32)', focusSecondary: 'rgb(128, 128, 128)', headerBackground: 'linear-gradient(to right, rgb(81, 132, 188), rgb(100, 168, 60))', headerNotActiveBackground: 'linear-gradient(to right, rgb(22, 46, 101), rgb(18, 91, 30))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(49, 106, 197)', material: 'rgb(32, 32, 32)', materialDark: 'rgb(22, 46, 101)', materialText: 'rgb(255, 255, 255)', materialTextDisabled: 'rgb(21, 21, 21)', materialTextDisabledShadow: 'rgb(128, 128, 128)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(49, 106, 197)', tooltip: 'rgb(0, 0, 30)' } as Theme; ================================================ FILE: src/common/themes/water.ts ================================================ import { Theme } from './types'; export default { name: 'water', anchor: '#72b3b4', anchorVisited: '#440381', borderDark: '#888c8f', borderDarkest: '#050608', borderLight: '#dfe0e3', borderLightest: '#ffffff', canvas: '#ffffff', canvasText: '#050608', canvasTextDisabled: '#888c8f', canvasTextDisabledShadow: '#ffffff', canvasTextInvert: '#ffffff', checkmark: '#050608', checkmarkDisabled: '#888c8f', desktopBackground: '#3a6ea5', flatDark: '#9e9e9e', flatLight: '#d8d8d8', focusSecondary: '#fefe03', headerBackground: '#72b3b4', headerNotActiveBackground: '#9a9e9c', headerNotActiveText: '#ced0cf', headerText: '#ffffff', hoverBackground: '#72b3b4', material: '#ced0cf', materialDark: '#9a9e9c', materialText: '#050608', materialTextDisabled: '#888c8f', materialTextDisabledShadow: '#ffffff', materialTextInvert: '#ffffff', progress: '#72b3b4', tooltip: '#fefbcc' } as Theme; ================================================ FILE: src/common/themes/white.ts ================================================ /* "White" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/White-870495714 */ import { Theme } from './types'; export default { name: 'white', anchor: 'rgb(0, 0, 128)', anchorVisited: 'rgb(0, 0, 128)', borderDark: 'rgb(0, 0, 0)', borderDarkest: 'rgb(255, 255, 255)', borderLight: 'rgb(0, 0, 0)', borderLightest: 'rgb(255, 255, 255)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(0, 0, 0)', canvasTextDisabledShadow: 'rgb(255, 255, 255)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(128, 128, 128)', desktopBackground: 'rgb(0, 128, 128)', flatDark: 'rgb(0, 0, 0)', flatLight: 'rgb(0, 0, 0)', focusSecondary: 'rgb(255, 255, 255)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 128), rgb(0, 0, 128))', headerNotActiveBackground: 'linear-gradient(to right, rgb(128, 128, 128), rgb(128, 128, 128))', headerNotActiveText: 'rgb(192, 192, 192)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 0, 128)', material: 'rgb(255, 255, 255)', materialDark: 'rgb(128, 128, 128)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(0, 0, 0)', materialTextDisabledShadow: 'rgb(255, 255, 255)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 0, 128)', tooltip: 'rgb(255, 255, 128)' } as Theme; ================================================ FILE: src/common/themes/windows1.ts ================================================ /* "Windows 1" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/Windows-1-573195578 */ import { Theme } from './types'; export default { name: 'windows1', anchor: 'rgb(0, 0, 85)', anchorVisited: 'rgb(0, 0, 85)', borderDark: 'rgb(0, 0, 0)', borderDarkest: 'rgb(255, 255, 255)', borderLight: 'rgb(0, 0, 0)', borderLightest: 'rgb(255, 255, 255)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(0, 0, 0)', canvasTextDisabledShadow: 'rgb(255, 255, 255)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(85, 85, 85)', desktopBackground: 'rgb(85, 254, 120)', flatDark: 'rgb(0, 0, 0)', flatLight: 'rgb(0, 0, 0)', focusSecondary: 'rgb(255, 255, 255)', headerBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(85, 85, 255))', headerNotActiveBackground: 'linear-gradient(to right, rgb(0, 0, 0), rgb(109, 109, 199))', headerNotActiveText: 'rgb(255, 255, 255)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 0, 0)', material: 'rgb(255, 255, 255)', materialDark: 'rgb(0, 0, 0)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(0, 0, 0)', materialTextDisabledShadow: 'rgb(255, 255, 255)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 0, 0)', tooltip: 'rgb(255, 255, 85)' } as Theme; ================================================ FILE: src/common/themes/wmii.ts ================================================ /* "WMII" by tPenguinLTG * https://www.deviantart.com/tpenguinltg/art/wmii-683233676 */ import { Theme } from './types'; export default { name: 'wmii', anchor: 'rgb(129, 101, 79)', anchorVisited: 'rgb(129, 101, 79)', borderDark: 'rgb(145, 148, 75)', borderDarkest: 'rgb(64, 64, 64)', borderLight: 'rgb(193, 196, 139)', borderLightest: 'rgb(224, 225, 198)', canvas: 'rgb(255, 255, 255)', canvasText: 'rgb(0, 0, 0)', canvasTextDisabled: 'rgb(145, 148, 75)', canvasTextDisabledShadow: 'rgb(224, 225, 198)', canvasTextInvert: 'rgb(255, 255, 255)', checkmark: 'rgb(0, 0, 0)', checkmarkDisabled: 'rgb(145, 148, 75)', desktopBackground: 'rgb(51, 51, 51)', flatDark: 'rgb(145, 148, 75)', flatLight: 'rgb(193, 196, 139)', focusSecondary: 'rgb(224, 225, 198)', headerBackground: 'linear-gradient(to right, rgb(129, 101, 79), rgb(129, 101, 79))', headerNotActiveBackground: 'linear-gradient(to right, rgb(145, 148, 75), rgb(145, 148, 75))', headerNotActiveText: 'rgb(193, 196, 139)', headerText: 'rgb(255, 255, 255)', hoverBackground: 'rgb(0, 0, 0)', material: 'rgb(193, 196, 139)', materialDark: 'rgb(145, 148, 75)', materialText: 'rgb(0, 0, 0)', materialTextDisabled: 'rgb(145, 148, 75)', materialTextDisabledShadow: 'rgb(224, 225, 198)', materialTextInvert: 'rgb(255, 255, 255)', progress: 'rgb(0, 0, 0)', tooltip: 'rgb(255, 255, 225)' } as Theme; ================================================ FILE: src/common/utils/events.spec.tsx ================================================ import { EventType, fireEvent, render, screen } from '@testing-library/react'; import React, { forwardRef } from 'react'; import { isReactFocusEvent, isReactKeyboardEvent, isReactMouseEvent } from './events'; type FireEventMap = Record; const focusEventTypesMap: FireEventMap = { blur: 'blur', focus: 'focus' }; const keyboardEventTypesMap: FireEventMap = { keydown: 'keyDown', keypress: 'keyPress', keyup: 'keyUp' }; const mouseEventTypesMap: FireEventMap = { click: 'click', contextmenu: 'contextMenu', doubleclick: 'doubleClick', drag: 'drag', dragend: 'dragEnd', dragenter: 'dragEnter', dragexit: 'dragExit', dragleave: 'dragLeave', dragover: 'dragOver', dragstart: 'dragStart', drop: 'drop', mousedown: 'mouseDown', mouseenter: 'mouseEnter', mouseleave: 'mouseLeave', mousemove: 'mouseMove', mouseout: 'mouseOut', mouseover: 'mouseOver', mouseup: 'mouseUp' }; const OnEverything = forwardRef< HTMLInputElement, { callback: (event: React.SyntheticEvent) => void; } >(({ callback }, ref) => { return ( ); }); const renderEventTester = async () => { const callback = jest.fn]>(); render(); const input = await screen.findByRole('textbox'); return { callback, emit: (fireEventFunctionName: keyof typeof fireEvent, init?: EventInit) => { fireEvent[fireEventFunctionName](input, init); const callbackCall = callback.mock.calls[callback.mock.calls.length - 1]?.[0]; return callbackCall; } }; }; describe(isReactFocusEvent, () => { let emit: ( fireEventFunctionName: keyof typeof fireEvent, init?: EventInit ) => React.SyntheticEvent; beforeAll(async () => { ({ emit } = await renderEventTester()); }); it.each(Object.keys(focusEventTypesMap))( 'returns correct results for %s', focusEventType => { expect(isReactFocusEvent(new Event(focusEventType))).toBeFalsy(); const event = emit(focusEventTypesMap[focusEventType]); expect(isReactFocusEvent(event)).toBeTruthy(); } ); }); describe(isReactKeyboardEvent, () => { let emit: ( fireEventFunctionName: keyof typeof fireEvent, init?: EventInit ) => React.SyntheticEvent; beforeAll(async () => { ({ emit } = await renderEventTester()); }); it.each(Object.keys(keyboardEventTypesMap))( 'returns correct results for %s', focusEventType => { expect(isReactKeyboardEvent(new Event(focusEventType))).toBeFalsy(); const event = emit(keyboardEventTypesMap[focusEventType]); expect(isReactKeyboardEvent(event)).toBeTruthy(); } ); }); describe(isReactMouseEvent, () => { let emit: ( fireEventFunctionName: keyof typeof fireEvent, init?: EventInit ) => React.SyntheticEvent; beforeAll(async () => { ({ emit } = await renderEventTester()); }); it.each(Object.keys(mouseEventTypesMap))( 'returns correct results for %s', focusEventType => { expect(isReactMouseEvent(new Event(focusEventType))).toBeFalsy(); const event = emit(mouseEventTypesMap[focusEventType]); expect(isReactMouseEvent(event)).toBeTruthy(); } ); }); ================================================ FILE: src/common/utils/events.ts ================================================ import React from 'react'; export const focusEventTypes = ['blur', 'focus']; export const keyboardEventTypes = ['keydown', 'keypress', 'keyup']; export const mouseEventTypes = [ 'click', 'contextmenu', 'doubleclick', 'drag', 'dragend', 'dragenter', 'dragexit', 'dragleave', 'dragover', 'dragstart', 'drop', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup' ]; export function isReactFocusEvent( event: React.SyntheticEvent | Event ): event is React.FocusEvent { return 'nativeEvent' in event && focusEventTypes.includes(event.type); } export function isReactKeyboardEvent( event: React.SyntheticEvent | Event ): event is React.KeyboardEvent { return 'nativeEvent' in event && keyboardEventTypes.includes(event.type); } export function isReactMouseEvent( event: React.SyntheticEvent | Event ): event is React.MouseEvent { return 'nativeEvent' in event && mouseEventTypes.includes(event.type); } ================================================ FILE: src/common/utils/index.spec.ts ================================================ import { clamp, mapFromWindowsTheme, getDecimalPrecision, roundValueToStep } from './index'; describe('clamp', () => { it('should return passed value if its between min and max', () => { expect(clamp(4, 3, 5)).toBe(4); }); it('should return min if value is smaller than min', () => { expect(clamp(2, 3, 5)).toBe(3); }); it('should return max if value is bigger than max', () => { expect(clamp(6, 3, 5)).toBe(5); }); it('should accept null as min', () => { expect(clamp(6, null, 5)).toBe(5); }); it('should accept null as max', () => { expect(clamp(1, 2, null)).toBe(2); }); }); describe('mapFromWindowsTheme', () => { it('should map corresponding properties directly if gradients are disabled', () => { const theme = { ButtonAlternateFace: '#000000', ButtonDkShadow: '#000001', ButtonFace: '#000002', ButtonHilight: '#000003', ButtonLight: '#000004', ButtonShadow: '#000005', ButtonText: '#000006', ActiveBorder: '#000007', AppWorkspace: '#000008', Background: '#000009', InactiveBorder: '#00000a', Scrollbar: '#00000b', Window: '#00000c', WindowFrame: '#00000d', WindowText: '#00000e', ActiveTitle: '#00000f', GradientActiveTitle: '#000010', GradientInactiveTitle: '#000011', InactiveTitle: '#000012', InactiveTitleText: '#000013', TitleText: '#000014', Menu: '#000015', MenuBar: '#000016', MenuHilight: '#000017', MenuText: '#000018', GrayText: '#000019', Hilight: '#00001a', HilightText: '#00001b', HotTrackingColor: '#00001c', InfoText: '#00001d', InfoWindow: '#00001e' }; const expectedTheme = { name: 'theme', anchor: '#00001c', anchorVisited: '#00001c', borderDark: '#000005', borderDarkest: '#000001', borderLight: '#000004', borderLightest: '#000003', canvas: '#00000c', canvasText: '#00000e', canvasTextDisabled: '#000005', canvasTextDisabledShadow: '#000003', canvasTextInvert: '#00001b', checkmark: '#00000e', checkmarkDisabled: '#000019', desktopBackground: '#000009', flatDark: '#000005', flatLight: '#000004', focusSecondary: '#000003', headerBackground: '#00000f', headerNotActiveBackground: '#000012', headerNotActiveText: '#000013', headerText: '#000014', hoverBackground: '#00001a', material: '#000002', materialDark: '#000012', materialText: '#000006', materialTextDisabled: '#000005', materialTextDisabledShadow: '#000003', materialTextInvert: '#00001b', progress: '#00001a', tooltip: '#00001e' }; expect(mapFromWindowsTheme('theme', theme, false)).toEqual(expectedTheme); }); it('should map corresponding properties with gradients if gradients are enabled', () => { const theme = { ButtonAlternateFace: '#000000', ButtonDkShadow: '#000001', ButtonFace: '#000002', ButtonHilight: '#000003', ButtonLight: '#000004', ButtonShadow: '#000005', ButtonText: '#000006', ActiveBorder: '#000007', AppWorkspace: '#000008', Background: '#000009', InactiveBorder: '#00000a', Scrollbar: '#00000b', Window: '#00000c', WindowFrame: '#00000d', WindowText: '#00000e', ActiveTitle: '#00000f', GradientActiveTitle: '#000010', GradientInactiveTitle: '#000011', InactiveTitle: '#000012', InactiveTitleText: '#000013', TitleText: '#000014', Menu: '#000015', MenuBar: '#000016', MenuHilight: '#000017', MenuText: '#000018', GrayText: '#000019', Hilight: '#00001a', HilightText: '#00001b', HotTrackingColor: '#00001c', InfoText: '#00001d', InfoWindow: '#00001e' }; const expectedTheme = { name: 'theme', anchor: '#00001c', anchorVisited: '#00001c', borderDark: '#000005', borderDarkest: '#000001', borderLight: '#000004', borderLightest: '#000003', canvas: '#00000c', canvasText: '#00000e', canvasTextDisabled: '#000005', canvasTextDisabledShadow: '#000003', canvasTextInvert: '#00001b', checkmark: '#00000e', checkmarkDisabled: '#000019', desktopBackground: '#000009', flatDark: '#000005', flatLight: '#000004', focusSecondary: '#000003', headerBackground: 'linear-gradient(to right, #00000f, #000010)', headerNotActiveBackground: 'linear-gradient(to right, #000012, #000011)', headerNotActiveText: '#000013', headerText: '#000014', hoverBackground: '#00001a', material: '#000002', materialDark: '#000012', materialText: '#000006', materialTextDisabled: '#000005', materialTextDisabledShadow: '#000003', materialTextInvert: '#00001b', progress: '#00001a', tooltip: '#00001e' }; expect(mapFromWindowsTheme('theme', theme, true)).toEqual(expectedTheme); }); }); describe('getDecimalPrecision', () => { it('should return 0 when passed a round number', () => { expect(getDecimalPrecision(4)).toBe(0); }); it('should return correct decimal precision', () => { expect(getDecimalPrecision(1.233)).toBe(3); }); }); describe('roundValueToStep', () => { it('should be able to round down', () => { expect(roundValueToStep(4, 3, 0)).toBe(3); }); it('should be able to round up', () => { expect(roundValueToStep(5, 3, 0)).toBe(6); }); }); ================================================ FILE: src/common/utils/index.ts ================================================ import { WindowsTheme } from '../../types'; export const noOp = () => {}; export function clamp(value: number, min: number | null, max: number | null) { if (max !== null && value > max) { return max; } if (min !== null && value < min) { return min; } return value; } function linearGradient(left: string, right: string) { return `linear-gradient(to right, ${left}, ${right})`; } export function mapFromWindowsTheme( name: string, windowsTheme: WindowsTheme, useGradients: boolean ) { const { ButtonDkShadow, ButtonFace, ButtonHilight, ButtonLight, ButtonShadow, ButtonText, Background, Window, WindowText, ActiveTitle, GradientActiveTitle, GradientInactiveTitle, InactiveTitle, InactiveTitleText, TitleText, GrayText, Hilight, HilightText, HotTrackingColor, InfoWindow } = windowsTheme; return { name, anchor: HotTrackingColor, anchorVisited: HotTrackingColor, borderDark: ButtonShadow, borderDarkest: ButtonDkShadow, borderLight: ButtonLight, borderLightest: ButtonHilight, canvas: Window, canvasText: WindowText, canvasTextDisabled: ButtonShadow, canvasTextDisabledShadow: ButtonHilight, canvasTextInvert: HilightText, checkmark: WindowText, checkmarkDisabled: GrayText, desktopBackground: Background, flatDark: ButtonShadow, flatLight: ButtonLight, focusSecondary: ButtonHilight, // should be Hilight inverted headerBackground: useGradients ? linearGradient(ActiveTitle, GradientActiveTitle) : ActiveTitle, headerNotActiveBackground: useGradients ? linearGradient(InactiveTitle, GradientInactiveTitle) : InactiveTitle, headerNotActiveText: InactiveTitleText, headerText: TitleText, hoverBackground: Hilight, material: ButtonFace, materialDark: InactiveTitle, materialText: ButtonText, materialTextDisabled: ButtonShadow, materialTextDisabledShadow: ButtonHilight, materialTextInvert: HilightText, progress: Hilight, tooltip: InfoWindow }; } // helper functions below are from Material UI (https://github.com/mui-org/material-ui) export function getDecimalPrecision(num: number) { if (Math.abs(num) < 1) { const parts = num.toExponential().split('e-'); const matissaDecimalPart = parts[0].split('.')[1]; return ( (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10) ); } const decimalPart = num.toString().split('.')[1]; return decimalPart ? decimalPart.length : 0; } export function roundValueToStep(value: number, step: number, min: number) { const nearest = Math.round((value - min) / step) * step + min; return Number(nearest.toFixed(getDecimalPrecision(step))); } export function getSize(value: string | number) { return typeof value === 'number' ? `${value}px` : value; } ================================================ FILE: src/index.ts ================================================ /* common */ export { default as styleReset } from './common/styleReset'; export { createScrollbars } from './common/index'; /* components */ export * from './Anchor/Anchor'; export * from './AppBar/AppBar'; export * from './Avatar/Avatar'; export * from './Button/Button'; export * from './Checkbox/Checkbox'; export * from './ColorInput/ColorInput'; export * from './Counter/Counter'; export * from './DatePicker/DatePicker'; export * from './Frame/Frame'; export * from './GroupBox/GroupBox'; export * from './Handle/Handle'; export * from './Hourglass/Hourglass'; export * from './MenuList/MenuList'; export * from './Monitor/Monitor'; export * from './NumberInput/NumberInput'; export * from './ProgressBar/ProgressBar'; export * from './Radio/Radio'; export * from './ScrollView/ScrollView'; export * from './Select/Select'; export * from './Separator/Separator'; export * from './Slider/Slider'; export * from './Table/Table'; export * from './Tabs/Tabs'; export * from './TextInput/TextInput'; export * from './Toolbar/Toolbar'; export * from './Tooltip/Tooltip'; export * from './TreeView/TreeView'; export * from './Window/Window'; /* deprecated components */ export * from './legacy/Bar'; export * from './legacy/Cutout'; export * from './legacy/Desktop'; export * from './legacy/Divider'; export * from './legacy/Fieldset'; export * from './legacy/List'; export * from './legacy/ListItem'; export * from './legacy/NumberField'; export * from './legacy/Panel'; export * from './legacy/Progress'; export * from './legacy/TextField'; export * from './legacy/Tree'; ================================================ FILE: src/legacy/Bar.tsx ================================================ import { Handle, HandleProps } from '../Handle/Handle'; /** @deprecated Use `HandleProps` */ export type BarProps = HandleProps; /** @deprecated Use `Handle` */ export const Bar = Handle; ================================================ FILE: src/legacy/Cutout.tsx ================================================ import { ScrollView, ScrollViewProps } from '../ScrollView/ScrollView'; /** @deprecated Use `ScrollViewProps` */ export type CutoutProps = ScrollViewProps; /** @deprecated Use `ScrollView` */ export const Cutout = ScrollView; ================================================ FILE: src/legacy/Desktop.tsx ================================================ import { Monitor, MonitorProps } from '../Monitor/Monitor'; /** @deprecated Use `MonitorProps` */ export type DesktopProps = MonitorProps; /** @deprecated Use `Monitor` */ export const Desktop = Monitor; ================================================ FILE: src/legacy/Divider.tsx ================================================ import { Separator, SeparatorProps } from '../Separator/Separator'; /** @deprecated Use `SeparatorProps` */ export type DividerProps = SeparatorProps; /** @deprecated Use `Separator` */ export const Divider = Separator; ================================================ FILE: src/legacy/Fieldset.tsx ================================================ import { GroupBox, GroupBoxProps } from '../GroupBox/GroupBox'; /** @deprecated Use `GroupBoxProps` */ export type FieldsetProps = GroupBoxProps; /** @deprecated Use `GroupBox` */ export const Fieldset = GroupBox; ================================================ FILE: src/legacy/List.tsx ================================================ import { MenuList, MenuListProps } from '../MenuList/MenuList'; /** @deprecated Use `MenuListProps` */ export type ListProps = MenuListProps; /** @deprecated Use `MenuList` */ export const List = MenuList; ================================================ FILE: src/legacy/ListItem.tsx ================================================ import { MenuListItem, MenuListItemProps, StyledMenuListItem } from '../MenuList/MenuList'; /** @deprecated Use `MenuListItemProps` */ export type ListItemProps = MenuListItemProps; /** @deprecated Use `MenuListItem` */ export const ListItem = MenuListItem; /** @deprecated Use `StyledMenuListItem` */ export const StyledListItem = StyledMenuListItem; ================================================ FILE: src/legacy/NumberField.tsx ================================================ import { NumberInput, NumberInputProps } from '../NumberInput/NumberInput'; /** @deprecated Use `NumberInputProps` */ export type NumberFieldProps = NumberInputProps; /** @deprecated Use `NumberInput` */ export const NumberField = NumberInput; ================================================ FILE: src/legacy/Panel.tsx ================================================ import { Frame, FrameProps } from '../Frame/Frame'; /** @deprecated Use `FrameProps` */ export type PanelProps = FrameProps; /** @deprecated Use `Frame` */ export const Panel = Frame; ================================================ FILE: src/legacy/Progress.tsx ================================================ import { ProgressBar, ProgressBarProps } from '../ProgressBar/ProgressBar'; /** @deprecated Use `ProgressBarProps` */ export type ProgressProps = ProgressBarProps; /** @deprecated Use `ProgressBar` */ export const Progress = ProgressBar; ================================================ FILE: src/legacy/TextField.tsx ================================================ import { TextInput, TextInputProps } from '../TextInput/TextInput'; /** @deprecated Use `TextInputProps` */ export type TextFieldProps = TextInputProps; /** @deprecated Use `TextInput` */ export const TextField = TextInput; ================================================ FILE: src/legacy/Tree.tsx ================================================ import { TreeView, TreeViewProps } from '../TreeView/TreeView'; /** @deprecated Use `TreeViewProps` */ export type TreeProps = TreeViewProps; /** @deprecated Use `TreeView` */ export const Tree = TreeView; ================================================ FILE: src/types.ts ================================================ import { ComponentType } from 'react'; import { Color, Theme, WindowsTheme } from './common/themes/types'; export type Sizes = 'sm' | 'md' | 'lg'; export type Orientation = 'horizontal' | 'vertical'; export type Direction = 'up' | 'down' | 'left' | 'right'; export type DimensionValue = undefined | number | string; export type CommonStyledProps = { /** * "as" polymorphic prop allows to render a different HTML element or React component * @see {@link https://styled-components.com/docs/api#as-polymorphic-prop} */ as?: string | ComponentType; // eslint-disable-line @typescript-eslint/no-explicit-any }; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type HTMLDataAttributes = Record<`data-${string}`, any>; export type CommonThemeProps = { 'data-testid'?: string; $disabled?: boolean; shadow?: boolean; }; export { Color, Theme, WindowsTheme }; ================================================ FILE: test/setup-test.ts ================================================ import '@testing-library/jest-dom'; import 'jest-styled-components'; ================================================ FILE: test/utils.tsx ================================================ import { render } from '@testing-library/react'; import React from 'react'; import { ThemeProvider } from 'styled-components'; import themes from '../src/common/themes'; export const theme = themes.original; export const renderWithTheme = (component: React.ReactNode) => render({component}); export class Touch { #identifier: number; #clientX = 0; #clientY = 0; #pageX = 0; #pageY = 0; constructor({ identifier, clientX = 0, clientY = 0, pageX = 0, pageY = 0 }: { identifier: number; clientX?: number; clientY?: number; pageX?: number; pageY?: number; }) { this.#identifier = identifier; this.#clientX = clientX; this.#clientY = clientY; this.#pageX = pageX; this.#pageY = pageY; } get identifier() { return this.#identifier; } get pageX() { return this.#pageX; } get pageY() { return this.#pageY; } get clientX() { return this.#clientX; } get clientY() { return this.#clientY; } } ================================================ FILE: tsconfig.build.index.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "emitDeclarationOnly": true, "outDir": "./dist", "rootDir": "./src", "sourceMap": false }, "include": [ "types/global.d.ts", "types/themes.d.ts", "src/**/*.ts", "src/*/*.tsx" ], "exclude": [ "**/*.spec.ts", "**/*.spec.tsx", "**/*.stories.ts", "**/*.stories.tsx", ] } ================================================ FILE: tsconfig.build.themes.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "emitDeclarationOnly": true, "outDir": "./dist/themes", "rootDir": "./src/common/themes", "sourceMap": false }, "include": [ "src/common/themes/*.ts", "src/common/themes/*.tsx" ] } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "allowJs": true, "alwaysStrict": true, "declaration": true, "declarationMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, "jsx": "react", "lib": [ "ESNext", "DOM" ], "module": "Node16", "moduleResolution": "Node", "noEmitOnError": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": true, "noImplicitReturns": true, "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "./lib", "paths": { "react95": [ "./src" ] }, "resolveJsonModule": true, "rootDir": "./", "skipLibCheck": true, "sourceMap": true, "strict": true, "strictFunctionTypes": true, "strictNullChecks": true, "strictPropertyInitialization": true, "target": "ES2018" }, "include": [ "**/*.ts", "**/*.tsx" ], "exclude": [ "coverage", "dist", "node_modules", "storybook" ] } ================================================ FILE: types/globals.d.ts ================================================ declare module '*.png' { const value: string; export = value; } declare module '*.woff2' { const value: string; export = value; } ================================================ FILE: types/themes.d.ts ================================================ // import original module declarations import { Theme } from '../src/types'; import 'styled-components'; // and extend them! declare module 'styled-components' { export interface DefaultTheme extends Theme {} }