├── .commitlintrc.json ├── .config └── eslint-config.json ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── labeler.yml ├── release-drafter.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── deploy-docs.yml │ ├── labeler.yml │ ├── npm.yml │ ├── percy.yml │ ├── pull-request.yml │ └── release-drafter.yml ├── .gitignore ├── .huskyrc ├── .lintstagedrc ├── .percy.yaml ├── .prettierrc ├── .stylelintrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build ├── babel.js ├── builder.js ├── index.js ├── library.js ├── minification.js ├── packages.js └── tasks.js ├── gatsby-config.js ├── gatsby-node.js ├── package.json ├── packages ├── calendar │ ├── README.md │ ├── index.ts │ └── package.json ├── upload │ ├── README.md │ ├── index.ts │ └── package.json └── woly │ ├── index.ts │ └── package.json ├── screenshot-testing ├── get-configs.js ├── index.js ├── make-screenshots.js ├── make-snapshots.js └── screenshot-page-template.pug ├── src ├── calendar │ ├── index.ts │ └── time-picker │ │ ├── index.tsx │ │ ├── types.ts │ │ ├── usage.mdx │ │ └── use-time-picker.tsx ├── dev │ ├── block.tsx │ ├── configurators │ │ ├── color │ │ │ ├── index.tsx │ │ │ └── scopes.ts │ │ ├── common │ │ │ ├── sections.tsx │ │ │ └── variable-field.tsx │ │ ├── configurators.tsx │ │ ├── context.ts │ │ ├── index.ts │ │ ├── inputs │ │ │ ├── color.tsx │ │ │ └── types.tsx │ │ ├── stylesheet.tsx │ │ └── types.ts │ ├── font-stacks.ts │ ├── global.ts │ ├── map-generation.tsx │ ├── playground.tsx │ └── state-map.tsx ├── images.d.ts ├── index.ts ├── lib │ ├── hooks │ │ ├── index.ts │ │ ├── use-force-update.ts │ │ ├── use-sync-height.tsx │ │ ├── use-unique-id.ts │ │ └── use-update-effect.ts │ ├── keyboard │ │ ├── index.ts │ │ ├── keyboard-event.ts │ │ ├── list-handlers.ts │ │ └── select-handlers.ts │ ├── level-downgrade.ts │ ├── palette.tsx │ ├── position-relative.tsx │ └── types.ts ├── static │ └── icons │ │ ├── arrow-down.svg │ │ ├── arrow-left.svg │ │ ├── check-filled-unchecked.svg │ │ ├── check-filled.svg │ │ ├── check.svg │ │ ├── close-small.svg │ │ ├── close.svg │ │ ├── color-palette.svg │ │ ├── dots-horizontaldd.svg │ │ ├── eye-closed.svg │ │ ├── eye-opened.svg │ │ ├── index.ts │ │ ├── info.svg │ │ ├── plus.svg │ │ ├── profile.svg │ │ ├── search.svg │ │ ├── spinner.svg │ │ └── threebars.svg ├── styleguide │ └── html │ │ └── data-attribute │ │ └── data-attribute.mdx ├── types │ └── woly.d.ts ├── upload │ ├── elements │ │ ├── index.ts │ │ └── upload-area │ │ │ └── index.tsx │ ├── index.ts │ └── molecules │ │ ├── button-uploader │ │ ├── index.tsx │ │ └── usage.mdx │ │ ├── drag-uploader │ │ ├── index.tsx │ │ └── usage.mdx │ │ └── index.ts └── woly │ ├── atoms │ ├── avatar │ │ ├── index.tsx │ │ ├── usage.mdx │ │ └── use-image-load.ts │ ├── backdrop │ │ ├── index.tsx │ │ └── usage.mdx │ ├── box │ │ ├── index.ts │ │ └── usage.mdx │ ├── button-icon │ │ ├── index.tsx │ │ ├── specification.mdx │ │ ├── state-props.tsx │ │ ├── state.mdx │ │ └── usage.mdx │ ├── button │ │ ├── __screenshot-test__ │ │ │ ├── config.js │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── specification.mdx │ │ ├── state-props.tsx │ │ ├── state.mdx │ │ └── usage.mdx │ ├── chip │ │ ├── __screenshot-test__ │ │ │ ├── config.js │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── specification.mdx │ │ ├── state-props.tsx │ │ ├── state.mdx │ │ └── usage.mdx │ ├── header-panel │ │ ├── index.tsx │ │ └── usage.mdx │ ├── heading │ │ ├── index.tsx │ │ └── usage.mdx │ ├── icon-box │ │ ├── index.tsx │ │ └── usage.mdx │ ├── index.ts │ ├── input │ │ ├── index.tsx │ │ ├── state-props.tsx │ │ ├── state.mdx │ │ └── usage.mdx │ ├── label │ │ ├── index.tsx │ │ └── usage.mdx │ ├── link │ │ └── specification.mdx │ ├── list │ │ ├── index.tsx │ │ └── usage.mdx │ ├── loader │ │ ├── example.tsx │ │ ├── index.tsx │ │ └── usage.mdx │ ├── separator │ │ ├── index.tsx │ │ └── usage.mdx │ ├── surface │ │ ├── index.tsx │ │ └── usage.mdx │ ├── table │ │ ├── index.tsx │ │ └── usage.mdx │ ├── text-area │ │ ├── index.tsx │ │ └── usage.mdx │ └── text │ │ ├── index.tsx │ │ └── usage.mdx │ ├── elements │ ├── box │ │ └── index.tsx │ ├── index.ts │ ├── input-container │ │ └── index.tsx │ ├── input-element │ │ └── index.tsx │ └── visually-hidden │ │ └── index.tsx │ ├── index.ts │ ├── molecules │ ├── accordion │ │ ├── index.tsx │ │ └── usage.mdx │ ├── checkbox │ │ ├── index.tsx │ │ └── usage.mdx │ ├── data-table │ │ ├── filter.tsx │ │ ├── index.tsx │ │ ├── range-cell.tsx │ │ ├── types.ts │ │ └── usage.mdx │ ├── field │ │ ├── index.tsx │ │ └── usage.mdx │ ├── index.ts │ ├── input-password │ │ ├── __screenshot-test__ │ │ │ ├── config.js │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── usage.mdx │ ├── modal │ │ ├── index.tsx │ │ ├── usage.mdx │ │ └── use-scroll-lock.ts │ ├── notification │ │ ├── index.tsx │ │ └── usage.mdx │ ├── popover │ │ ├── index.tsx │ │ └── usage.mdx │ ├── radio-button │ │ ├── index.tsx │ │ └── usage.mdx │ ├── select │ │ ├── index.tsx │ │ └── usage.mdx │ ├── switch │ │ ├── index.tsx │ │ └── usage.mdx │ ├── tabs │ │ ├── index.tsx │ │ └── usage.mdx │ ├── toast │ │ ├── index.tsx │ │ └── usage.mdx │ └── tooltip │ │ ├── index.tsx │ │ └── usage.mdx │ ├── templates │ ├── grid │ │ ├── index.tsx │ │ └── usage.mdx │ └── index.ts │ └── woly-global-styles.ts ├── static └── CNAME ├── tsconfig.json ├── versions.json └── yarn.lock /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.config/eslint-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/explicit-function-return-type": "off", 4 | "@typescript-eslint/no-use-before-define": "off", 5 | "no-use-before-define": "off", 6 | "unicorn/filename-case": ["error", { "case": "kebabCase" }], 7 | "unicorn/no-nested-ternary": "error", 8 | "unicorn/prevent-abbreviations": [ 9 | "off", 10 | { 11 | "extendDefaultWhitelist": true 12 | } 13 | ], 14 | "import/export": "off" 15 | }, 16 | "overrides": [ 17 | { 18 | "files": ["**/story.tsx", "**/stories.tsx"], 19 | "rules": { 20 | "import/no-anonymous-default-export": "off", 21 | "import/no-unresolved": "off" 22 | } 23 | }, 24 | { 25 | "files": ["build/**"], 26 | "rules": { 27 | "import/no-extraneous-dependencies": "off", 28 | "import/no-default-export": "off" 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | public 4 | static 5 | .temp 6 | .cache 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@eslint-kit/base", 4 | "@eslint-kit/typescript", 5 | "@eslint-kit/node", 6 | "@eslint-kit/react", 7 | "@eslint-kit/react-new-jsx-transform", 8 | "@eslint-kit/prettier", 9 | "./.config/eslint-config" 10 | ], 11 | "root": true, 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "project": "./tsconfig.json" 15 | }, 16 | "settings": { 17 | "import/parsers": { 18 | "@typescript-eslint/parser": [ 19 | ".ts", 20 | ".tsx" 21 | ] 22 | }, 23 | "import/resolver": { 24 | "typescript": { 25 | "project": "./tsconfig.json" 26 | }, 27 | "alias": [ 28 | [ 29 | "lib", 30 | "./src/lib" 31 | ], 32 | [ 33 | "icons", 34 | "./src/static/icons" 35 | ] 36 | ] 37 | } 38 | }, 39 | "rules": { 40 | "import/order": "off", 41 | "react/jsx-props-no-spreading": "off" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | '@atoms': 2 | - src/woly/atoms/**/* 3 | - src/atoms/**/* 4 | 5 | '@molecules': 6 | - src/woly/molecules/**/* 7 | - src/molecules/**/* 8 | 9 | '@organisms': 10 | - src/woly/organisms/**/* 11 | - src/organisms/**/* 12 | 13 | '@templates': 14 | - src/woly/templates/**/* 15 | - src/templates/**/* 16 | 17 | packages: 18 | - packages/**/* 19 | 20 | build: 21 | - build/**/* 22 | 23 | configuration: 24 | - ./* 25 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | categories: 2 | - title: 🧩 Atoms 3 | labels: 4 | - '@atoms' 5 | 6 | - title: 🧩 Molecules 7 | labels: 8 | - '@molecules' 9 | 10 | - title: 🧩 Organisms 11 | labels: 12 | - '@organisms' 13 | 14 | - title: 🧩 Templates 15 | labels: 16 | - '@templates' 17 | 18 | - title: 🪐 Internal 19 | labels: 20 | - lib 21 | - configuration 22 | - packages 23 | - build 24 | - dependencies 25 | 26 | - title: '⚠️ Breaking changes' 27 | label: 'BREAKING CHANGES' 28 | 29 | - title: '🧰 Maintenance' 30 | labels: 31 | - 'chore' 32 | - 'dependencies' 33 | 34 | - title: '📚 Documentation' 35 | label: 'documentation' 36 | 37 | - title: '🧪 Tests' 38 | label: 'tests' 39 | 40 | - title: '🏎 Optimizations' 41 | label: 'optimizations' 42 | 43 | version-resolver: 44 | major: 45 | labels: 46 | - 'BREAKING CHANGES' 47 | minor: 48 | labels: 49 | - 'feature' 50 | - 'enhancement' 51 | - 'dependencies' 52 | patch: 53 | labels: 54 | - 'fix' 55 | - 'bugfix' 56 | - 'bug' 57 | - 'optimizations' 58 | default: patch 59 | 60 | name-template: 'v$RESOLVED_VERSION' 61 | tag-template: 'v$RESOLVED_VERSION' 62 | 63 | change-template: '- $TITLE #$NUMBER (@$AUTHOR)' 64 | template: | 65 | $CHANGES 66 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Package CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Set Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '14' 21 | 22 | - name: Install Dependencies 23 | run: yarn install 24 | 25 | - name: Build 26 | run: yarn build 27 | 28 | - name: Test 29 | run: yarn test 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '20 1 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | - name: Perform CodeQL Analysis 55 | uses: github/codeql-action/analyze@v1 56 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-docs: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Set Node.js 18 | uses: actions/setup-node@master 19 | with: 20 | node-version: 12.x 21 | 22 | - name: Install Dependencies 23 | run: yarn install 24 | 25 | - name: Build for GitHub 26 | run: yarn docs:build 27 | 28 | - name: Build and Deploy 29 | uses: JamesIves/github-pages-deploy-action@releases/v3 30 | with: 31 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | BRANCH: gh-pages 34 | FOLDER: public 35 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: 'Pull Request Labeler' 2 | on: 3 | pull_request: 4 | types: ['opened'] 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@v2 11 | with: 12 | repo-token: '${{ secrets.GITHUB_TOKEN }}' 13 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package CI 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | woly: 7 | description: '[woly] Set "yes" to publish' 8 | required: true 9 | default: 'no' 10 | calendar: 11 | description: '[calendar] Set "yes" to run publish' 12 | required: true 13 | default: 'no' 14 | upload: 15 | description: '[upload] Set "yes" to run publish' 16 | required: true 17 | default: 'no' 18 | 19 | jobs: 20 | publish: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | with: 26 | persist-credentials: false 27 | 28 | - name: Set Node.js 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: '14' 32 | 33 | - name: Install Dependencies 34 | run: yarn install 35 | 36 | - name: Build 37 | run: yarn build 38 | 39 | - name: Test 40 | run: yarn test 41 | 42 | - name: Create NPM config 43 | run: npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN 44 | env: 45 | NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 46 | 47 | - name: Publish woly to NPM 48 | if: github.event.inputs.woly == 'yes' 49 | working-directory: 'dist/woly' 50 | run: npm publish 51 | 52 | - name: Publish calendar to NPM 53 | if: github.event.inputs.calendar == 'yes' 54 | working-directory: 'dist/calendar' 55 | run: npm publish 56 | 57 | - name: Publish upload to NPM 58 | if: github.event.inputs.upload == 'yes' 59 | working-directory: 'dist/upload' 60 | run: npm publish 61 | -------------------------------------------------------------------------------- /.github/workflows/percy.yml: -------------------------------------------------------------------------------- 1 | name: Percy CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | percy: 9 | runs-on: ubuntu-latest 10 | if: 'false' 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | persist-credentials: false 16 | 17 | - name: Set Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '14' 21 | 22 | - name: Install Dependencies 23 | run: yarn install --frozen-lockfile 24 | env: 25 | PERCY_TOKEN: '${{ secrets.PERCY_TOKEN }}' 26 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request CI 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - synchronize 7 | - opened 8 | - reopened 9 | - ready_for_review 10 | - unlocked 11 | - review_requested 12 | 13 | jobs: 14 | validate: 15 | runs-on: ubuntu-18.04 16 | 17 | strategy: 18 | matrix: 19 | node: [14] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | persist-credentials: false 25 | 26 | - name: Node.JS ${{matrix.node}} 27 | uses: actions/setup-node@v2 28 | with: 29 | node-version: ${{matrix.node}} 30 | 31 | - name: Install dependencies 32 | run: yarn install 33 | 34 | - name: Build Woly 35 | run: yarn build 36 | 37 | - name: Check for formatting 38 | run: yarn format 39 | 40 | - name: Check for code style 41 | run: yarn lint:code 42 | 43 | - name: Check for CSS issues 44 | run: yarn lint:style 45 | 46 | - name: Run tests 47 | run: yarn test 48 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Git ### 2 | # Created by git for backups. To disable backups in Git: 3 | # $ git config --global mergetool.keepBackup false 4 | *.orig 5 | 6 | # Created by git when using merge tools for conflicts 7 | *.BACKUP.* 8 | *.BASE.* 9 | *.LOCAL.* 10 | *.REMOTE.* 11 | *_BACKUP_*.txt 12 | *_BASE_*.txt 13 | *_LOCAL_*.txt 14 | *_REMOTE_*.txt 15 | 16 | ### macOS ### 17 | # General 18 | .DS_Store 19 | .AppleDouble 20 | .LSOverride 21 | 22 | # Icon must end with two \r 23 | Icon 24 | 25 | # Thumbnails 26 | ._* 27 | 28 | # Files that might appear in the root of a volume 29 | .DocumentRevisions-V100 30 | .fseventsd 31 | .Spotlight-V100 32 | .TemporaryItems 33 | .Trashes 34 | .VolumeIcon.icns 35 | .com.apple.timemachine.donotpresent 36 | 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | 44 | ### Node ### 45 | # Logs 46 | logs 47 | *.log 48 | npm-debug.log* 49 | yarn-debug.log* 50 | yarn-error.log* 51 | lerna-debug.log* 52 | 53 | # Diagnostic reports (https://nodejs.org/api/report.html) 54 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 55 | 56 | # Runtime data 57 | pids 58 | *.pid 59 | *.seed 60 | *.pid.lock 61 | 62 | # Directory for instrumented libs generated by jscoverage/JSCover 63 | lib-cov 64 | 65 | # Coverage directory used by tools like istanbul 66 | coverage 67 | *.lcov 68 | 69 | # nyc test coverage 70 | .nyc_output 71 | 72 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 73 | .grunt 74 | 75 | # Bower dependency directory (https://bower.io/) 76 | bower_components 77 | 78 | # node-waf configuration 79 | .lock-wscript 80 | 81 | # Compiled binary addons (https://nodejs.org/api/addons.html) 82 | build/Release 83 | 84 | # Dependency directories 85 | node_modules/ 86 | jspm_packages/ 87 | 88 | # TypeScript v1 declaration files 89 | typings/ 90 | 91 | # TypeScript cache 92 | *.tsbuildinfo 93 | 94 | # Optional npm cache directory 95 | .npm 96 | 97 | # Optional eslint cache 98 | .eslintcache 99 | 100 | # Optional REPL history 101 | .node_repl_history 102 | 103 | # Output of 'npm pack' 104 | *.tgz 105 | 106 | # Yarn Integrity file 107 | .yarn-integrity 108 | 109 | # dotenv environment variables file 110 | .env 111 | .env.test 112 | 113 | # parcel-bundler cache (https://parceljs.org/) 114 | .cache 115 | 116 | # next.js build output 117 | .next 118 | 119 | # nuxt.js build output 120 | .nuxt 121 | 122 | # react / gatsby 123 | public/ 124 | 125 | # vuepress build output 126 | .vuepress/dist 127 | 128 | # Serverless directories 129 | .serverless/ 130 | 131 | # FuseBox cache 132 | .fusebox/ 133 | 134 | # DynamoDB Local files 135 | .dynamodb/ 136 | 137 | ### react ### 138 | .DS_* 139 | **/*.backup.* 140 | **/*.back.* 141 | 142 | node_modules 143 | bower_componets 144 | 145 | *.sublime* 146 | 147 | psd 148 | thumb 149 | sketch 150 | 151 | ### VisualStudioCode ### 152 | .vscode/* 153 | !.vscode/settings.json 154 | !.vscode/tasks.json 155 | !.vscode/launch.json 156 | !.vscode/extensions.json 157 | 158 | ### VisualStudioCode Patch ### 159 | # Ignore all local history of files 160 | .history 161 | 162 | storybook-static 163 | dist 164 | 165 | # End of https://www.gitignore.io/api/git,node,react,macos,visualstudiocode 166 | 167 | stats 168 | .size-snapshot.json 169 | 170 | **/screenshots 171 | 172 | .idea 173 | .temp 174 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 4 | "pre-commit": "lint-staged" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,tsx,js,jsx,mjs}": [ 3 | "stylelint --fix", 4 | "eslint --fix", 5 | "prettier --write" 6 | ], 7 | "*.css": [ 8 | "stylelint --fix" 9 | ], 10 | "*.md": [ 11 | "prettier --write" 12 | ], 13 | "*.mdx": [ 14 | "prettier --write --parser mdx" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.percy.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | static: 3 | baseUrl: / 4 | snapshot: 5 | widths: 6 | - 1024 7 | minHeight: 1024 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all", 6 | "arrowParens": "always", 7 | "tabWidth": 2, 8 | "useTabs": false, 9 | "plugins": ["./node_modules/prettier-plugin-import-sort"] 10 | } 11 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-recommended", 4 | "stylelint-config-styled-components", 5 | "stylelint-config-rational-order" 6 | ], 7 | "rules": { 8 | "order/properties-order": [[], { "severity": "error" }], 9 | "plugin/rational-order": [ 10 | true, 11 | { 12 | "empty-line-between-groups": true, 13 | "severity": "error" 14 | } 15 | ], 16 | "no-descending-specificity": null 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sergey Sova 4 | Copyright (c) 2020 Dmitry Marison 5 | Copyright (c) 2020 Redmadrobot SPb 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /build/babel.js: -------------------------------------------------------------------------------- 1 | const common = { 2 | presets: [ 3 | [ 4 | '@babel/env', 5 | { 6 | loose: true, 7 | useBuiltIns: 'entry', 8 | corejs: 3, 9 | modules: false, 10 | shippedProposals: true, 11 | targets: { 12 | node: '10', 13 | browsers: [ 14 | 'last 2 Chrome versions', 15 | 'last 2 Firefox versions', 16 | 'last 2 Safari versions', 17 | 'last 1 Edge versions', 18 | ], 19 | }, 20 | }, 21 | ], 22 | '@babel/preset-react', 23 | '@babel/typescript', 24 | ], 25 | plugins: [ 26 | [ 27 | 'babel-plugin-styled-components', 28 | { 29 | ssr: true, 30 | displayName: true, 31 | fileName: true, 32 | minify: true, 33 | transpileTemplateLiterals: true, 34 | pure: true, 35 | }, 36 | ], 37 | ['@babel/proposal-class-properties', { loose: true }], 38 | '@babel/proposal-object-rest-spread', 39 | '@babel/plugin-proposal-optional-chaining', 40 | '@babel/plugin-proposal-nullish-coalescing-operator', 41 | '@babel/plugin-transform-react-constant-elements', 42 | ], 43 | overrides: [ 44 | { 45 | test(filename) { 46 | return filename && (filename.endsWith('.tsx') || filename.endsWith('.ts')); 47 | }, 48 | presets: [ 49 | [ 50 | '@babel/preset-typescript', 51 | { 52 | isTSX: true, 53 | allExtensions: true, 54 | }, 55 | ], 56 | ], 57 | }, 58 | ], 59 | sourceMaps: true, 60 | }; 61 | 62 | module.exports = (api) => { 63 | if (api && api.cache && api.cache.never) api.cache.never(); 64 | return common; 65 | }; 66 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/no-nested-ternary */ 2 | const { 3 | copyLicense, 4 | generatePackageJson, 5 | massCopy, 6 | // publishScript, 7 | } = require('./tasks'); 8 | const { buildWoly, buildCalendar, buildUpload } = require('./builder'); 9 | 10 | const woly = [ 11 | generatePackageJson('woly'), 12 | copyLicense('woly'), 13 | massCopy('.', 'dist/woly', ['README.md']), 14 | massCopy('packages/woly', 'dist/woly', ['package.json']), 15 | buildWoly, 16 | // publishScript('woly'), // TODO: fix to correctly publish 17 | ]; 18 | 19 | const calendar = [ 20 | generatePackageJson('calendar'), 21 | copyLicense('calendar'), 22 | massCopy('packages/calendar', 'dist/calendar', ['README.md']), 23 | massCopy('packages/calendar', 'dist/calendar', ['package.json']), 24 | buildCalendar, 25 | // publishScript('calendar', '@woly/calendar'), 26 | ]; 27 | 28 | const upload = [ 29 | generatePackageJson('upload'), 30 | copyLicense('upload'), 31 | massCopy('packages/upload', 'dist/upload', ['README.md']), 32 | massCopy('packages/upload', 'dist/upload', ['package.json']), 33 | buildUpload, 34 | // publishScript('upload', '@woly/upload'), 35 | ]; 36 | 37 | main(); 38 | 39 | function main() { 40 | const config = { 41 | next: Boolean(process.env.NEXT), 42 | dryRun: Boolean(process.env.DRY_RUN), 43 | packages: process.env.PACKAGE 44 | ? [process.env.PACKAGE.trim()] 45 | : process.env.PACKAGES 46 | ? process.env.PACKAGES.split(',').map((p) => p.trim()) 47 | : undefined, 48 | }; 49 | 50 | /* eslint-disable no-console */ 51 | console.info('running tasks with config'); 52 | console.dir(config); 53 | 54 | Promise.all([ 55 | run({ tasks: woly, name: 'woly', config }), 56 | run({ tasks: calendar, name: 'calendar', config }), 57 | run({ tasks: upload, name: 'upload', config }), 58 | ]); 59 | } 60 | 61 | function run({ tasks, name, config }) { 62 | if (config.packages && !config.packages.includes(name)) { 63 | return; 64 | } 65 | 66 | /* eslint-disable no-console */ 67 | return tasks 68 | .reduce((p, task) => p.then(() => task(config)), Promise.resolve()) 69 | .then(() => { 70 | console.log('DONE', name, 'task complete'); 71 | }) 72 | .catch((error) => { 73 | console.log('FAIL', name, 'task failed'); 74 | throw error; 75 | }); 76 | /* eslint-enable no-console */ 77 | } 78 | -------------------------------------------------------------------------------- /build/library.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const fs = require('fs-extra'); 3 | 4 | async function writePackageJson(path, config) { 5 | let fullPath; 6 | if (path.endsWith('package.json')) fullPath = directory(path); 7 | else fullPath = directory(path, 'package.json'); 8 | await fs.outputJSON(fullPath, config, { spaces: 2 }); 9 | } 10 | 11 | const root = process.cwd(); 12 | 13 | function directory(...paths) { 14 | return resolve(root, ...paths); 15 | } 16 | 17 | module.exports = { 18 | writePackageJson, 19 | directory, 20 | }; 21 | -------------------------------------------------------------------------------- /build/minification.js: -------------------------------------------------------------------------------- 1 | const nameCache = {}; 2 | 3 | const minifyConfig = ({ beautify, inline = true }) => ({ 4 | parse: { 5 | bare_returns: false, 6 | ecma: 8, 7 | html5_comments: false, 8 | shebang: true, 9 | }, 10 | compress: { 11 | global_defs: { 12 | // __DEV__: false, 13 | // 'process.env.NODE_ENV': 'production', 14 | }, 15 | arrows: true, 16 | arguments: true, 17 | booleans: true, 18 | booleans_as_integers: true, 19 | collapse_vars: true, 20 | comparisons: true, 21 | computed_props: true, 22 | conditionals: true, 23 | dead_code: true, 24 | directives: true, 25 | drop_console: false, 26 | drop_debugger: true, 27 | ecma: 8, 28 | evaluate: true, 29 | expression: true, // ? 30 | hoist_funs: true, // ? 31 | hoist_props: true, 32 | hoist_vars: false, 33 | if_return: true, 34 | inline, 35 | join_vars: true, // ? 36 | 37 | defaults: false, 38 | keep_classnames: false, 39 | keep_fargs: false, 40 | keep_fnames: false, 41 | loops: true, 42 | module: true, 43 | properties: true, 44 | pure_getters: true, 45 | reduce_funcs: true, 46 | reduce_vars: true, 47 | sequences: true, 48 | side_effects: true, 49 | switches: true, 50 | toplevel: true, 51 | 52 | typeofs: true, 53 | unused: true, 54 | passes: 3, 55 | 56 | unsafe_arrows: true, 57 | unsafe_Function: true, 58 | unsafe_math: true, 59 | unsafe_proto: true, 60 | }, 61 | mangle: { 62 | reserved: ['effector', 'effectorVue', 'effectorReact', 'it', 'test'], 63 | eval: true, 64 | keep_classnames: false, 65 | keep_fnames: false, 66 | module: true, 67 | toplevel: true, // ? 68 | safari10: false, 69 | // properties: { 70 | // builtins: false, 71 | // debug: false, 72 | // keep_quoted: false, //? 73 | // reserved: ['test', 'it'], 74 | // }, 75 | }, 76 | output: { 77 | ascii_only: false, 78 | braces: false, // ? 79 | // comments: /#/i, 80 | comments: false, 81 | ecma: 8, 82 | beautify, 83 | indent_level: 2, 84 | inline_script: false, // ? 85 | keep_quoted_props: false, // ? 86 | quote_keys: false, 87 | quote_style: 3, // ? 88 | safari10: false, 89 | semicolons: true, // ? 90 | shebang: true, 91 | webkit: false, 92 | wrap_iife: false, 93 | }, 94 | // sourceMap: { 95 | // // source map options 96 | // }, 97 | ecma: 8, 98 | keep_classnames: false, 99 | keep_fnames: false, 100 | ie8: false, 101 | module: true, 102 | nameCache, 103 | safari10: false, 104 | toplevel: true, 105 | warnings: true, 106 | }); 107 | 108 | module.exports = { minifyConfig }; 109 | -------------------------------------------------------------------------------- /build/packages.js: -------------------------------------------------------------------------------- 1 | const versions = require('../versions.json'); 2 | 3 | const common = { 4 | scripts: {}, 5 | author: { 6 | name: 'Sergey Sova', 7 | email: 'mail@sergeysova.com', 8 | url: 'https://sova.dev', 9 | }, 10 | engines: { 11 | node: '>=8.0.0', 12 | }, 13 | sideEffects: false, 14 | contributors: [], 15 | homepage: 'https://github.com/woly-ui/woly', 16 | license: 'MIT', 17 | bugs: { 18 | url: 'https://github.com/woly-ui/woly/issues', 19 | email: 'woly@sergeysova.com', 20 | }, 21 | publishConfig: { 22 | access: 'public', 23 | }, 24 | }; 25 | 26 | const keywords = [ 27 | 'component', 28 | 'components', 29 | 'design', 30 | 'library', 31 | 'react-component', 32 | 'react', 33 | 'system', 34 | 'ui', 35 | 'woly', 36 | ]; 37 | 38 | const cjs = (name) => [`${name}.js`, `${name}.js.map`]; 39 | const esm = (name) => [`${name}.mjs`, `${name}.mjs.map`]; 40 | const types = (name) => [`${name}.d.ts`]; 41 | 42 | const getFiles = (name) => ['README.md', 'LICENSE', ...cjs(name), ...esm(name), ...types(name)]; 43 | 44 | const getRepo = (name) => ({ 45 | type: 'git', 46 | url: 'https://github.com/woly-ui/woly', 47 | directory: `packages/${name}`, 48 | }); 49 | 50 | const dependsPeer = { 51 | react: '^16.14.0', 52 | 'styled-components': '^5.2.1', 53 | }; 54 | 55 | const dependsOnWoly = { 56 | woly: `^${versions.woly}`, 57 | }; 58 | 59 | const getModules = (name) => ({ 60 | main: `${name}.js`, 61 | // module: `${name}.mjs`, 62 | // 'jsnext:main': `${name}.mjs`, 63 | types: `${name}.d.ts`, 64 | typings: `${name}.d.ts`, 65 | // exports: { 66 | // '.': { 67 | // require: `./${name}.js`, 68 | // default: `./${name}.mjs`, 69 | // }, 70 | // [`./${name}.mjs`]: `./${name}.mjs`, 71 | // './package.json': './package.json', 72 | // }, 73 | }); 74 | 75 | const woly = { 76 | ...common, 77 | name: 'woly', 78 | version: versions.woly, 79 | description: 'Exhaustive component library for React', 80 | ...getModules('woly'), 81 | files: getFiles('woly'), 82 | keywords, 83 | dependencies: { 84 | 'react-dropzone': '^11.3.4', 85 | 'react-resize-detector': '^6.7.4', 86 | '@tippyjs/react': '^4.2.5', 87 | }, 88 | peerDependencies: { 89 | ...dependsPeer, 90 | }, 91 | repository: getRepo('woly'), 92 | }; 93 | 94 | const calendar = { 95 | ...common, 96 | name: '@woly/calendar', 97 | version: versions.calendar, 98 | description: 'Date and time components for Woly React library', 99 | ...getModules('calendar'), 100 | files: getFiles('calendar'), 101 | keywords: [...keywords, 'calendar', 'date', 'time'], 102 | dependencies: {}, 103 | peerDependencies: { 104 | ...dependsPeer, 105 | ...dependsOnWoly, 106 | }, 107 | repository: getRepo('calendar'), 108 | }; 109 | 110 | const upload = { 111 | ...common, 112 | name: '@woly/upload', 113 | version: versions.upload, 114 | description: 'Upload components for Woly React library', 115 | ...getModules('upload'), 116 | files: getFiles('upload'), 117 | keywords: [...keywords, 'upload'], 118 | dependencies: {}, 119 | peerDependencies: { 120 | ...dependsPeer, 121 | ...dependsOnWoly, 122 | }, 123 | repository: getRepo('upload'), 124 | }; 125 | 126 | module.exports = { woly, calendar, upload }; 127 | -------------------------------------------------------------------------------- /build/tasks.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('woly:tasks'); 2 | const fs = require('fs-extra'); 3 | const execa = require('execa'); 4 | 5 | const packages = require('./packages'); 6 | const { writePackageJson, directory } = require('./library'); 7 | 8 | const copyLicense = (libraryName) => massCopy('.', `dist/${libraryName}`, ['LICENSE']); 9 | 10 | const generatePackageJson = (libraryName) => () => { 11 | debug('running generatePackageJson', libraryName); 12 | return writePackageJson(`packages/${libraryName}/package.json`, packages[libraryName]); 13 | }; 14 | 15 | function massCopy(from, to, targets) { 16 | return () => { 17 | debug('running massCopy', targets, 'from', from, '->', to); 18 | const jobs = []; 19 | for (const target of targets) { 20 | jobs.push([directory(from, target), directory(to, target)]); 21 | } 22 | 23 | return Promise.all(jobs.map(([f, t]) => fs.copy(f, t))); 24 | }; 25 | } 26 | 27 | /* eslint-disable no-console */ 28 | function publishScript(libraryName, _npmPackage = libraryName) { 29 | const onCatch = (error) => { 30 | debug('failed publishScript', libraryName); 31 | console.error(error); 32 | }; 33 | 34 | return async (config) => { 35 | const tag = config.next ? 'next' : 'latest'; 36 | const dry = config.dryRun; 37 | 38 | debug('running publishScript', libraryName, { tag, dry }); 39 | try { 40 | const command = ['publish', '--tag', tag, dry ? '--dry-run' : undefined].filter(Boolean); 41 | const cwd = `${process.cwd()}/dist/${libraryName}`; 42 | 43 | debug('run > npm ', command.join(' '), { cwd }); 44 | 45 | const { stdout, stderr } = await execa('npm', command, { cwd }); 46 | 47 | console.log(stdout); 48 | console.error(stderr); 49 | } catch (error) { 50 | onCatch(error); 51 | } 52 | }; 53 | } 54 | 55 | /* eslint-enable no-console */ 56 | 57 | module.exports = { 58 | copyLicense, 59 | generatePackageJson, 60 | massCopy, 61 | publishScript, 62 | }; 63 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | siteUrl: `https://woly.sova.dev/`, 4 | }, 5 | plugins: [ 6 | { 7 | resolve: `gatsby-theme-woly`, 8 | options: { 9 | components: `src`, 10 | examplesGlobalImports: { 11 | 'dev/playground': { 12 | namedImports: [ 13 | { name: 'block', value: 'block' }, 14 | { name: 'Playground', value: 'Playground' }, 15 | ], 16 | }, 17 | }, 18 | }, 19 | }, 20 | { 21 | resolve: 'gatsby-plugin-react-svg', 22 | options: { 23 | rule: { 24 | include: /icons/, 25 | }, 26 | }, 27 | }, 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | exports.onCreateWebpackConfig = ({ actions }) => { 4 | actions.setWebpackConfig({ 5 | resolve: { 6 | alias: { 7 | calendar: path.resolve(__dirname, './src/calendar/'), 8 | dev: path.resolve(__dirname, './src/dev/'), 9 | lib: path.resolve(__dirname, './src/lib/'), 10 | static: path.resolve(__dirname, './src/static/'), 11 | ui: path.resolve(__dirname, './src/woly/'), 12 | woly: path.resolve(__dirname, './src/woly/'), 13 | }, 14 | }, 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /packages/calendar/README.md: -------------------------------------------------------------------------------- 1 | # @woly/calendar 2 | 3 | Just say hello 4 | -------------------------------------------------------------------------------- /packages/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export const Demo = 1; 2 | -------------------------------------------------------------------------------- /packages/calendar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": {}, 3 | "author": { 4 | "name": "Sergey Sova", 5 | "email": "mail@sergeysova.com", 6 | "url": "https://sova.dev" 7 | }, 8 | "engines": { 9 | "node": ">=8.0.0" 10 | }, 11 | "sideEffects": false, 12 | "contributors": [], 13 | "homepage": "https://github.com/woly-ui/woly", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/woly-ui/woly/issues", 17 | "email": "woly@sergeysova.com" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "name": "@woly/calendar", 23 | "version": "0.3.3", 24 | "description": "Date and time components for Woly React library", 25 | "main": "calendar.js", 26 | "types": "calendar.d.ts", 27 | "typings": "calendar.d.ts", 28 | "files": [ 29 | "README.md", 30 | "LICENSE", 31 | "calendar.js", 32 | "calendar.js.map", 33 | "calendar.mjs", 34 | "calendar.mjs.map", 35 | "calendar.d.ts" 36 | ], 37 | "keywords": [ 38 | "component", 39 | "components", 40 | "design", 41 | "library", 42 | "react-component", 43 | "react", 44 | "system", 45 | "ui", 46 | "woly", 47 | "calendar", 48 | "date", 49 | "time" 50 | ], 51 | "dependencies": {}, 52 | "peerDependencies": { 53 | "react": "^16.14.0", 54 | "styled-components": "^5.2.1", 55 | "woly": "^0.11.6" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "https://github.com/woly-ui/woly", 60 | "directory": "packages/calendar" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/upload/README.md: -------------------------------------------------------------------------------- 1 | # @woly/upload 2 | 3 | @woly/upload 4 | -------------------------------------------------------------------------------- /packages/upload/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../src/upload'; 2 | -------------------------------------------------------------------------------- /packages/upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": {}, 3 | "author": { 4 | "name": "Sergey Sova", 5 | "email": "mail@sergeysova.com", 6 | "url": "https://sova.dev" 7 | }, 8 | "engines": { 9 | "node": ">=8.0.0" 10 | }, 11 | "sideEffects": false, 12 | "contributors": [], 13 | "homepage": "https://github.com/woly-ui/woly", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/woly-ui/woly/issues", 17 | "email": "woly@sergeysova.com" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "name": "@woly/upload", 23 | "version": "0.1.1", 24 | "description": "Upload components for Woly React library", 25 | "main": "upload.js", 26 | "types": "upload.d.ts", 27 | "typings": "upload.d.ts", 28 | "files": [ 29 | "README.md", 30 | "LICENSE", 31 | "upload.js", 32 | "upload.js.map", 33 | "upload.mjs", 34 | "upload.mjs.map", 35 | "upload.d.ts" 36 | ], 37 | "keywords": [ 38 | "component", 39 | "components", 40 | "design", 41 | "library", 42 | "react-component", 43 | "react", 44 | "system", 45 | "ui", 46 | "woly", 47 | "upload" 48 | ], 49 | "dependencies": {}, 50 | "peerDependencies": { 51 | "react": "^16.14.0", 52 | "styled-components": "^5.2.1", 53 | "woly": "^0.11.6" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "https://github.com/woly-ui/woly", 58 | "directory": "packages/upload" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/woly/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../src/woly'; 2 | -------------------------------------------------------------------------------- /packages/woly/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": {}, 3 | "author": { 4 | "name": "Sergey Sova", 5 | "email": "mail@sergeysova.com", 6 | "url": "https://sova.dev" 7 | }, 8 | "engines": { 9 | "node": ">=8.0.0" 10 | }, 11 | "sideEffects": false, 12 | "contributors": [], 13 | "homepage": "https://github.com/woly-ui/woly", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/woly-ui/woly/issues", 17 | "email": "woly@sergeysova.com" 18 | }, 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "name": "woly", 23 | "version": "0.11.6", 24 | "description": "Exhaustive component library for React", 25 | "main": "woly.js", 26 | "types": "woly.d.ts", 27 | "typings": "woly.d.ts", 28 | "files": [ 29 | "README.md", 30 | "LICENSE", 31 | "woly.js", 32 | "woly.js.map", 33 | "woly.mjs", 34 | "woly.mjs.map", 35 | "woly.d.ts" 36 | ], 37 | "keywords": [ 38 | "component", 39 | "components", 40 | "design", 41 | "library", 42 | "react-component", 43 | "react", 44 | "system", 45 | "ui", 46 | "woly" 47 | ], 48 | "dependencies": { 49 | "react-dropzone": "^11.3.4", 50 | "react-resize-detector": "^6.7.4", 51 | "@tippyjs/react": "^4.2.5" 52 | }, 53 | "peerDependencies": { 54 | "react": "^16.14.0", 55 | "styled-components": "^5.2.1" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "https://github.com/woly-ui/woly", 60 | "directory": "packages/woly" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /screenshot-testing/get-configs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** 4 | * 1. gastby-theme-woly plugin finds all screenshot-test configs and 5 | * generate a page (returns JSON with configs meta) on build stage. 6 | * Configs searcher heavly rely on file structure, so it should have 7 | * this format – ///. 8 | * is set in gatsby-config.js via plugin`s option `components`. 9 | * 2. Test pages url repeat same format mentioned above 10 | */ 11 | 12 | async function getConfigs({ browser, configsUrl, reporter }) { 13 | const excludedComponents = process.env.EXCLUDE 14 | ? process.env.EXCLUDE.split(',').map((component) => component.trim()) 15 | : []; 16 | 17 | const includedComponents = process.env.INCLUDE 18 | ? process.env.INCLUDE.split(',').map((component) => component.trim()) 19 | : []; 20 | 21 | const notDismissed = (name) => { 22 | if (!process.env.INCLUDE && !process.env.EXCLUDE) { 23 | return true; 24 | } 25 | return ( 26 | (includedComponents.length > 0 && includedComponents.includes(name)) || 27 | (excludedComponents.length > 0 && !excludedComponents.includes(name)) 28 | ); 29 | }; 30 | 31 | const page = await browser.newPage(); 32 | let response; 33 | 34 | try { 35 | const result = await page.goto(configsUrl); /** 1 */ 36 | if (result) { 37 | response = result; 38 | } 39 | } catch (error) { 40 | reporter('could not fetch configs, url is broken\n', error); 41 | } 42 | 43 | const configs = []; 44 | 45 | for await (const { path: configPath, ...meta } of await response.json()) { 46 | try { 47 | const config = require(path.resolve(__dirname, '../src', configPath)); 48 | 49 | if (config && notDismissed(meta.name)) { 50 | configs.push({ 51 | config, 52 | /** 2 */ 53 | url: `${meta.package}/${meta.category}/${meta.name}/__screenshot-test__`, 54 | ...meta, 55 | }); 56 | } 57 | } catch (error) { 58 | reporter('could not get config file\n', error); 59 | } 60 | } 61 | 62 | await page.close(); 63 | 64 | return configs; 65 | } 66 | 67 | module.exports = { 68 | getConfigs, 69 | }; 70 | -------------------------------------------------------------------------------- /screenshot-testing/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const playwright = require('playwright'); 3 | const fs = require('fs-extra'); 4 | const reporter = require('debug')('screenshoter'); 5 | 6 | const { getConfigs } = require('./get-configs'); 7 | const { makeScreenshots } = require('./make-screenshots'); 8 | const { makeSnapshots } = require('./make-snapshots'); 9 | 10 | const express = require('express'); 11 | 12 | /** 13 | * TODO: test in all major browsers 14 | */ 15 | 16 | const defaultPageOptions = { 17 | deviceScaleFactor: 2, 18 | viewport: { width: 800, height: 400 }, 19 | defaultTimout: 3000, 20 | }; 21 | 22 | const defaultScreenshotSize = { 23 | width: 250, 24 | height: 200, 25 | }; 26 | 27 | const startScreenshotTesting = async ({ rootUrl, mapSelector }) => { 28 | await fs.remove(`${__dirname}/screenshots`); 29 | 30 | reporter('booting playwright'); 31 | const { browser } = await bootPlaywright(); 32 | 33 | reporter('booting screenshot server'); 34 | 35 | const { app, stopServer, port } = bootScreenshotServer(); 36 | 37 | reporter("loading component's config"); 38 | const configsMeta = await getConfigs({ 39 | browser, 40 | configsUrl: `${rootUrl}/screenshot-test-configs.json`, 41 | reporter, 42 | }); 43 | 44 | if (!configsMeta || configsMeta.length === 0) { 45 | reporter('components not found'); 46 | process.exit(); 47 | } 48 | 49 | reporter('got configs for –', configsMeta.map(({ name }) => name).join(', ')); 50 | 51 | const testComponent = async (configMeta) => { 52 | const context = await browser.newContext(defaultPageOptions); 53 | 54 | const { 55 | config: { screenshotSize }, 56 | } = configMeta; 57 | 58 | const wrapperSize = { 59 | ...defaultScreenshotSize, 60 | ...screenshotSize, 61 | }; 62 | 63 | const { groupsMeta } = await makeScreenshots({ 64 | configMeta, 65 | wrapperSize, 66 | context, 67 | mapSelector, 68 | reporter, 69 | rootUrl, 70 | }); 71 | 72 | if (!groupsMeta || groupsMeta.length === 0) { 73 | reporter( 74 | `zero screenshots were taken for ${configMeta.name || 'component'}, abort making snapshots`, 75 | ); 76 | return; 77 | } 78 | 79 | await makeSnapshots({ app, context, groupsMeta, mapSelector, port, reporter, wrapperSize }); 80 | }; 81 | 82 | await Promise.all(configsMeta.map(testComponent)); 83 | 84 | reporter('closed screenshot server'); 85 | 86 | stopServer.close(); 87 | 88 | reporter('closed playwright'); 89 | await browser.close(); 90 | }; 91 | 92 | function bootScreenshotServer() { 93 | const port = 3000; 94 | 95 | const http = require('http'); 96 | const app = express(); 97 | const server = http.createServer(app); 98 | 99 | app.set('view engine', 'pug'); 100 | app.set('views', __dirname); 101 | app.use('/screenshots', express.static(`${__dirname}/screenshots`)); 102 | 103 | server.on('error', (error) => { 104 | reporter('somehting went wrong with screenshot server'); 105 | throw error; 106 | }); 107 | 108 | const stopServer = server.listen(port, () => { 109 | reporter(`booted screenshot server at localhost:${port}`); 110 | }); 111 | 112 | return { app, stopServer, port }; 113 | } 114 | 115 | async function bootPlaywright() { 116 | const browser = await playwright.chromium.launch(); 117 | 118 | return { browser }; 119 | } 120 | 121 | async function main() { 122 | try { 123 | await startScreenshotTesting({ 124 | rootUrl: 'http://localhost:8000', 125 | mapSelector: '.state-map', 126 | }); 127 | } catch (error) { 128 | reporter(error); 129 | process.exit(1); 130 | } 131 | } 132 | 133 | main(); 134 | -------------------------------------------------------------------------------- /screenshot-testing/make-snapshots.js: -------------------------------------------------------------------------------- 1 | const utils = require('@percy/sdk-utils'); 2 | 3 | async function makeSnapshots({ 4 | app, 5 | context, 6 | groupsMeta, 7 | mapSelector, 8 | port, 9 | reporter, 10 | wrapperSize, 11 | }) { 12 | const makeGroupSnapshot = async ({ name, groupName, stats, states }) => { 13 | const page = await context.newPage(); 14 | 15 | const variantGroup = `${name}-${groupName}`; 16 | 17 | app.get(`/${variantGroup}`, (_req, res) => { 18 | res.render('screenshot-page-template', { 19 | stats, 20 | wrapperSize, 21 | }); 22 | }); 23 | 24 | await page.goto(`http://localhost:${port}/${variantGroup}`); 25 | 26 | await page.waitForSelector(mapSelector); 27 | 28 | if (process.env.PERCY_TOKEN) { 29 | await sendPercy({ name: variantGroup, page, width: states * wrapperSize.width }); 30 | } else { 31 | const stateMap = await page.$(mapSelector); 32 | await stateMap.screenshot({ 33 | path: `${__dirname}/screenshots/${name}/${groupName}.png`, 34 | }); 35 | await page.close(); 36 | } 37 | }; 38 | 39 | await Promise.all(groupsMeta.map(makeGroupSnapshot)); 40 | } 41 | 42 | async function sendPercy({ name, page, width }) { 43 | const versions = require('../versions.json'); 44 | 45 | await page.evaluate(await utils.fetchPercyDOM()); 46 | // eslint-disable-next-line no-loop-func 47 | const domSnapshot = await page.evaluate(() => PercyDOM.serialize()); 48 | 49 | await utils.postSnapshot({ 50 | // required 51 | name, 52 | url: page.url(), 53 | domSnapshot, 54 | // optional 55 | environmentInfo: 'localhost/playwright' /** meta info */, 56 | clientInfo: `woly/${versions.woly}` /** meta info */, 57 | widths: [width], 58 | minHeight: 1024, 59 | }); 60 | } 61 | 62 | module.exports = { makeSnapshots }; 63 | -------------------------------------------------------------------------------- /screenshot-testing/screenshot-page-template.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | meta(charset="UTF-8") 5 | meta(http-equiv="X-UA-Compatible", content="IE=edge") 6 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 7 | title Component 8 | style(type="text/css"). 9 | body { 10 | margin: 0; 11 | } 12 | 13 | .state-map { 14 | display: flex; 15 | flex-wrap: wrap; 16 | } 17 | .state-map__cell { 18 | outline: 1px solid #ccc; 19 | position: relative; 20 | } 21 | 22 | .state-map__cell img { max-width: 100%; display: block;} 23 | 24 | .state-map__cell::before { 25 | position: absolute; 26 | top: 2px; 27 | left: 2px; 28 | display: block; 29 | content: attr(data-variation); 30 | font-size: 0.8rem; 31 | min-height: 20px; 32 | box-sizing: border-box; 33 | padding: 2px; 34 | } 35 | body 36 | .state-map 37 | for stat in stats 38 | .state-map__cell(data-variation=`${stat.variation}` style=`width: ${wrapperSize.width}px;height: ${wrapperSize.height}px`) 39 | img(src=`${stat.path}`) 40 | -------------------------------------------------------------------------------- /src/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export { TimePicker } from './time-picker'; 2 | -------------------------------------------------------------------------------- /src/calendar/time-picker/types.ts: -------------------------------------------------------------------------------- 1 | import { InputProps } from 'ui/atoms/input'; 2 | import { Priority } from 'lib/types'; 3 | 4 | type ReservedProps = 'type' | 'autoComplete' | 'onChange' | 'value'; 5 | 6 | export type Format = 'HH:mm:ss' | 'hh:mm:ss' | 'HH:mm' | 'hh:mm'; 7 | 8 | export type TimePickerProps = Omit & 9 | Priority & { 10 | format?: Format; 11 | value: string; 12 | hourStep?: number; 13 | minStep?: number; 14 | secStep?: number; 15 | onChange: (value: string) => void; 16 | }; 17 | -------------------------------------------------------------------------------- /src/calendar/time-picker/usage.mdx: -------------------------------------------------------------------------------- 1 | ### Example 2 | 3 | ```tsx playground 4 | import { TimePicker } from 'calendar'; 5 | 6 | export function Example() { 7 | const [value, onChange] = React.useState('') 8 | return ( 9 | 10 |
11 | 12 |
13 |
14 | ) 15 | } 16 | ``` 17 | 18 | ### Custom steps 19 | 20 | ```tsx playground 21 | import { TimePicker } from 'calendar'; 22 | 23 | export function Example() { 24 | const [value, onChange] = React.useState('') 25 | return ( 26 | 27 |
28 | 38 |
39 |
40 | ) 41 | } 42 | ``` 43 | 44 | ### Disabled 45 | 46 | ```tsx playground 47 | import { TimePicker } from 'calendar'; 48 | 49 | export function Example() { 50 | const [value, onChange] = React.useState('') 51 | return ( 52 | 53 |
54 | 55 |
56 |
57 | ) 58 | } 59 | ``` 60 | 61 | ### Props 62 | 63 | | Name | Type | Default | Description | 64 | | ---------- | --------------------------------------------- | --------- | ------------------------------------------------------------------------------------- | 65 | | `format` | `'HH:mm:ss' l 'hh:mm:ss' l 'HH:mm' l 'hh:mm'` | `'HH:mm'` | time format | 66 | | `hourStep` | `number` | `1` | define set of available values for hour (eg. hourStep = 3, options = [0, 3, 6, 9...]) | 67 | | `minStep` | `number` | `1` | define set of available values for min | 68 | | `secStep` | `number` | `1` | define set of available values for sec | 69 | | `onChange` | `(newValue: string) => void` | | change callback | 70 | | `...` | `InputProps` | `{}` | Props from Input component | 71 | -------------------------------------------------------------------------------- /src/dev/block.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Block = styled.div` 4 | --woly-font-size: 15px; 5 | `; 6 | 7 | const N = styled(Block)` 8 | --woly-component-level: 0; 9 | --woly-line-height: 21px; 10 | `; 11 | 12 | const XS = styled(Block)` 13 | --woly-component-level: 1; 14 | `; 15 | 16 | const S = styled(Block)` 17 | --woly-component-level: 2; 18 | `; 19 | 20 | const M = styled(Block)` 21 | --woly-component-level: 3; 22 | `; 23 | 24 | const L = styled(Block)` 25 | --woly-component-level: 4; 26 | --woly-font-size: 18px; 27 | `; 28 | 29 | const XL = styled(Block)` 30 | --woly-component-level: 5; 31 | --woly-font-size: 21px; 32 | `; 33 | 34 | const H = styled(Block)` 35 | --woly-component-level: 6; 36 | --woly-font-size: 21px; 37 | `; 38 | 39 | export const block = { N, XS, S, M, L, XL, H }; 40 | -------------------------------------------------------------------------------- /src/dev/configurators/color/index.tsx: -------------------------------------------------------------------------------- 1 | import { SCOPES } from './scopes'; 2 | import { useSectionsJSX } from '../common/sections'; 3 | 4 | export const ColorConfigurator: React.FC = () => { 5 | return useSectionsJSX('color', SCOPES); 6 | }; 7 | -------------------------------------------------------------------------------- /src/dev/configurators/color/scopes.ts: -------------------------------------------------------------------------------- 1 | import { Scope, Variable } from '../types'; 2 | 3 | const CONFIGURABLE_COLORS: Variable[] = [ 4 | { 5 | displayName: 'Shape (default)', 6 | name: '--woly-shape-default', 7 | type: 'color', 8 | }, 9 | { 10 | displayName: 'Shape text (default)', 11 | name: '--woly-shape-text-default', 12 | type: 'color', 13 | }, 14 | { 15 | displayName: 'Shape (disabled)', 16 | name: '--woly-shape-disabled', 17 | type: 'color', 18 | }, 19 | { 20 | displayName: 'Shape text (disabled)', 21 | name: '--woly-shape-text-disabled', 22 | type: 'color', 23 | }, 24 | { 25 | displayName: 'Shape (hover)', 26 | name: '--woly-shape-hover', 27 | type: 'color', 28 | }, 29 | { 30 | displayName: 'Shape text (hover)', 31 | name: '--woly-shape-text-hover', 32 | type: 'color', 33 | }, 34 | { 35 | displayName: 'Shape (active)', 36 | name: '--woly-shape-active', 37 | type: 'color', 38 | }, 39 | { 40 | displayName: 'Shape text (active)', 41 | name: '--woly-shape-text-active', 42 | type: 'color', 43 | }, 44 | { 45 | displayName: 'Canvas (default)', 46 | name: '--woly-canvas-default', 47 | type: 'color', 48 | }, 49 | { 50 | displayName: 'Canvas text (default)', 51 | name: '--woly-canvas-text-default', 52 | type: 'color', 53 | }, 54 | { 55 | displayName: 'Canvas (disabled)', 56 | name: '--woly-canvas-disabled', 57 | type: 'color', 58 | }, 59 | { 60 | displayName: 'Canvas text (disabled)', 61 | name: '--woly-canvas-text-disabled', 62 | type: 'color', 63 | }, 64 | { 65 | displayName: 'Canvas (hover)', 66 | name: '--woly-canvas-hover', 67 | type: 'color', 68 | }, 69 | { 70 | displayName: 'Canvas text (hover)', 71 | name: '--woly-canvas-text-hover', 72 | type: 'color', 73 | }, 74 | { 75 | displayName: 'Canvas (active)', 76 | name: '--woly-canvas-active', 77 | type: 'color', 78 | }, 79 | { 80 | displayName: 'Canvas text (active)', 81 | name: '--woly-canvas-text-active', 82 | type: 'color', 83 | }, 84 | ]; 85 | 86 | export const SCOPES: Scope[] = [ 87 | { 88 | displayName: 'Priorities: Default', 89 | selector: `[data-priority='default']`, 90 | variables: CONFIGURABLE_COLORS, 91 | }, 92 | { 93 | displayName: 'Priorities: Primary', 94 | selector: `[data-priority='primary']`, 95 | variables: CONFIGURABLE_COLORS, 96 | }, 97 | { 98 | displayName: 'Priorities: Secondary', 99 | selector: `[data-priority='secondary']`, 100 | variables: CONFIGURABLE_COLORS, 101 | }, 102 | { 103 | displayName: 'Priorities: White', 104 | selector: `[data-priority='white']`, 105 | variables: CONFIGURABLE_COLORS, 106 | }, 107 | { 108 | displayName: 'Priorities: Success', 109 | selector: `[data-priority='success']`, 110 | variables: CONFIGURABLE_COLORS, 111 | }, 112 | { 113 | displayName: 'Priorities: Danger', 114 | selector: `[data-priority='danger']`, 115 | variables: CONFIGURABLE_COLORS, 116 | }, 117 | ]; 118 | -------------------------------------------------------------------------------- /src/dev/configurators/common/sections.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React, { useReducer, useState } from 'react'; 3 | import { IconArrowDown } from 'static/icons'; 4 | 5 | import { ConfiguratorName, Scope } from '../types'; 6 | import { VariableField } from './variable-field'; 7 | import { getInitialVariableValue } from '../stylesheet'; 8 | import { useLocalConfiguratorsState } from '../context'; 9 | 10 | type SectionProps = React.HTMLAttributes & { name: string }; 11 | 12 | const SectionView: React.FC = ({ name, children, ...rest }) => { 13 | const [isOpen, toggleOpen] = useReducer((is) => !is, false); 14 | 15 | return ( 16 |
17 |
18 | 19 | {name} 20 |
21 | {isOpen &&
{children}
} 22 |
23 | ); 24 | }; 25 | 26 | export const Section = styled(SectionView)` 27 | &:last-of-type { 28 | margin-bottom: 0; 29 | } 30 | 31 | & [data-name-wrapper] { 32 | display: flex; 33 | flex-direction: row; 34 | align-items: center; 35 | width: 100%; 36 | margin: 6px 0; 37 | padding: 6px 0; 38 | 39 | cursor: pointer; 40 | 41 | user-select: none; 42 | } 43 | 44 | & [data-arrow-icon] { 45 | & path { 46 | fill: currentColor; 47 | } 48 | 49 | &[data-reversed='true'] { 50 | transform: rotate(180deg); 51 | } 52 | } 53 | 54 | & [data-section-name] { 55 | margin-left: 12px; 56 | } 57 | 58 | & [data-section-content] { 59 | margin: 12px 0; 60 | } 61 | `; 62 | 63 | export function useSectionsJSX(configurator: ConfiguratorName, scopes: Scope[]) { 64 | const { id } = useLocalConfiguratorsState(); 65 | 66 | const sections = scopes.map((scope) => { 67 | const inputs = scope.variables 68 | .map((variable) => { 69 | const initialValue = getInitialVariableValue(id, { 70 | selector: scope.selector, 71 | variable: variable.name, 72 | }); 73 | 74 | if (!initialValue) { 75 | // there is no element using this variable inside 76 | return null; 77 | } 78 | 79 | return ( 80 | 86 | ); 87 | }) 88 | .filter(Boolean); 89 | 90 | if (inputs.length === 0) { 91 | return null; 92 | } 93 | 94 | return ( 95 |
96 | {inputs} 97 |
98 | ); 99 | }); 100 | 101 | return <>{sections}; 102 | } 103 | -------------------------------------------------------------------------------- /src/dev/configurators/common/variable-field.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { ColorInput } from '../inputs/color'; 5 | import { ConfiguratorName, Scope, Variable, VariableType } from '../types'; 6 | import { InputProps } from '../inputs/types'; 7 | import { useCssVariable } from '../stylesheet'; 8 | 9 | interface Props { 10 | configurator: ConfiguratorName; 11 | scope: Scope; 12 | variable: Variable; 13 | } 14 | 15 | export const VariableField: React.FC = ({ configurator, scope, variable }) => { 16 | const [value, setValue] = useCssVariable({ 17 | configurator, 18 | selector: scope.selector, 19 | variable: variable.name, 20 | }); 21 | 22 | if (!value) return null; 23 | const Input = mapTypeToInput[variable.type]; 24 | 25 | return ( 26 | 27 | 28 | {variable.displayName} 29 | 30 | ); 31 | }; 32 | 33 | const mapTypeToInput: Record> = { 34 | color: ColorInput, 35 | }; 36 | 37 | const VariableWrapper = styled.div` 38 | display: flex; 39 | flex-direction: row; 40 | align-items: center; 41 | margin-bottom: 12px; 42 | 43 | &:last-of-type { 44 | margin-bottom: 0; 45 | } 46 | `; 47 | 48 | const VariableName = styled.span` 49 | display: inline-block; 50 | margin-left: 16px; 51 | 52 | font-size: 14px; 53 | `; 54 | -------------------------------------------------------------------------------- /src/dev/configurators/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | import { ConfiguratorName, Stylesheets } from './types'; 4 | 5 | export interface LocalConfiguratorsState { 6 | id: string; 7 | configurators: ConfiguratorName[]; 8 | active: ConfiguratorName | null; 9 | stylesheets: Stylesheets; 10 | } 11 | 12 | const Context = createContext({} as LocalConfiguratorsState); 13 | 14 | export function useLocalConfiguratorsState() { 15 | return useContext(Context); 16 | } 17 | 18 | export const LocalConfiguratorsStateProvider = Context.Provider; 19 | -------------------------------------------------------------------------------- /src/dev/configurators/index.ts: -------------------------------------------------------------------------------- 1 | export { Configurators } from './configurators'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/dev/configurators/inputs/color.tsx: -------------------------------------------------------------------------------- 1 | import Tippy from '@tippyjs/react'; 2 | import rgba from 'color-rgba'; 3 | import React, { forwardRef } from 'react'; 4 | import styled, { css } from 'styled-components'; 5 | import { RgbaColorPicker } from 'react-colorful'; 6 | 7 | import { InputProps } from './types'; 8 | 9 | interface RGBA { 10 | r: number; 11 | g: number; 12 | b: number; 13 | a: number; 14 | } 15 | 16 | function fromString(anyColor: string): RGBA { 17 | const parsed = rgba(anyColor); 18 | if (!parsed) return { r: 0, g: 0, b: 0, a: 0 }; 19 | const [r, g, b, a] = parsed; 20 | return { r, g, b, a }; 21 | } 22 | 23 | function toString(rgba: RGBA): string { 24 | const { r, g, b, a } = rgba; 25 | return `rgba(${r}, ${g}, ${b}, ${a})`; 26 | } 27 | 28 | export const ColorInput: React.FC = ({ value, onChange }) => { 29 | // wrap in div for better a11y 30 | return ( 31 |
32 | onChange(toString(rgba))} 41 | /> 42 | } 43 | > 44 | 45 | 46 |
47 | ); 48 | }; 49 | 50 | const ColorIndicator = forwardRef(({ value }, ref) => { 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ); 62 | }); 63 | 64 | const Wrapper = styled.button` 65 | position: relative; 66 | 67 | width: 24px; 68 | height: 24px; 69 | overflow: hidden; 70 | 71 | border: 1px solid rgba(0, 0, 0, 0.35); 72 | border-radius: 4px; 73 | outline: none; 74 | cursor: pointer; 75 | `; 76 | 77 | const background = css` 78 | position: absolute; 79 | top: 0; 80 | left: 0; 81 | 82 | width: 100%; 83 | height: 100%; 84 | `; 85 | 86 | const TransparencyWrapper = styled.svg.attrs({ 87 | viewBox: '0 0 24 24', 88 | })` 89 | ${background} 90 | 91 | & > rect { 92 | width: 50%; 93 | height: 50%; 94 | stroke-width: 0; 95 | } 96 | `; 97 | 98 | const Fill = styled.div` 99 | ${background} 100 | `; 101 | -------------------------------------------------------------------------------- /src/dev/configurators/inputs/types.tsx: -------------------------------------------------------------------------------- 1 | export interface InputProps { 2 | value: string; 3 | onChange: (value: string) => void; 4 | } 5 | -------------------------------------------------------------------------------- /src/dev/configurators/types.ts: -------------------------------------------------------------------------------- 1 | export type ConfiguratorName = 'color'; 2 | export type VariableType = 'color'; 3 | 4 | export interface Variable { 5 | displayName: string; 6 | name: string; 7 | type: VariableType; 8 | } 9 | 10 | export interface Scope { 11 | displayName: string; 12 | selector: string; 13 | variables: Variable[]; 14 | } 15 | 16 | export interface Path { 17 | configurator: ConfiguratorName; 18 | selector: string; 19 | variable: string; 20 | } 21 | 22 | export type ElementRef = React.MutableRefObject; 23 | export type ValueRef = React.MutableRefObject; 24 | 25 | export interface Style { 26 | element: JSX.Element; 27 | elementRef: ElementRef; 28 | initialValue: string; 29 | valueRef: ValueRef; 30 | } 31 | 32 | export type StylesheetSubscriber = () => void; 33 | 34 | export interface Stylesheets { 35 | subscribe(path: Path, callback: StylesheetSubscriber): () => void; 36 | initialize(path: Path): void; 37 | get(path: Path): Style | null; 38 | set(path: Path, value: string): void; 39 | reset(params: { configurator: ConfiguratorName }): void; 40 | render(): JSX.Element[]; 41 | } 42 | -------------------------------------------------------------------------------- /src/dev/font-stacks.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | export const systemUi = css` 4 | @font-face { 5 | font-family: system-stack-emoji; 6 | unicode-range: U+1F300-1F5FF, U+1F600-1F64F, U+1F680-1F6FF, U+2600-26FF; 7 | /* Edge fix */ 8 | src: local('Segoe UI Emoji'); /* Windows 8.1+ */ 9 | /* prettier-ignore */ 10 | src: 11 | /* MacOS, iOS */ 12 | local("Apple Color Emoji"), 13 | /* Windows 8.1+ */ 14 | local("Segoe UI Emoji"), 15 | /* Android */ 16 | local("Noto Color Emoji"), 17 | local("Android Emoji"), 18 | /* Linux */ 19 | local("Emoji One Color"), 20 | local("Twitter Color Emoji"), 21 | local("EmojiSymbols"), 22 | local("Symbola"); 23 | } 24 | 25 | @font-face { 26 | font-weight: 300; 27 | font-family: system-stack-sans-serif; 28 | font-style: normal; 29 | /* prettier-ignore */ 30 | src: 31 | /* MacOS, iOS */ 32 | local(".SFNSText-Light"), /* El Capitan */ 33 | local(".HelveticaNeueDeskInterface-Light"), /* Yosemite */ 34 | local(".LucidaGrandeUI"), /* Mavericks */ 35 | /* Windows */ 36 | local("Segoe UI Light"), /* Since Vista */ 37 | local("Tahoma"), /* Until XP */ 38 | /* Android */ 39 | local("Roboto-Light"), /* Since 4.0 */ 40 | local("Droid Sans"), /* Until 3.2 */ 41 | /* Linux */ 42 | local("Ubuntu Light"), /* Ubuntu */ 43 | local("Oxygen"), /* KDE */ 44 | local("Cantarell"); /* Gnome */ 45 | } 46 | 47 | /* prettier-ignore */ 48 | --font-system-ui: 49 | /* Always render emoji first */ 50 | system-stack-emoji, 51 | /* CSS Fonts Level 4 generic */ 52 | system-ui, 53 | /* Safari (MacOS 10.11+, iOS 9+) */ 54 | -apple-system-body, 55 | /* Chrome until 55, Opera until 42 (MacOS) */ 56 | BlinkMacSystemFont, 57 | /* Fallback to local fonts */ 58 | system-stack-sans-serif, 59 | /* In case all else fails */ 60 | sans-serif; 61 | `; 62 | -------------------------------------------------------------------------------- /src/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | import React from 'react'; 3 | 4 | const ReactComponent: React.FunctionComponent & { title?: string }>; 5 | 6 | export default ReactComponent; 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './woly'; 2 | -------------------------------------------------------------------------------- /src/lib/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useForceUpdate } from './use-force-update'; 2 | export { useSyncHeight } from './use-sync-height'; 3 | export { useUniqueID } from './use-unique-id'; 4 | export { useUpdateEffect } from './use-update-effect'; 5 | -------------------------------------------------------------------------------- /src/lib/hooks/use-force-update.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export function useForceUpdate() { 4 | const [, setCount] = useState(0); 5 | return () => setCount((count) => count + 1); 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/hooks/use-sync-height.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ResizeDetector from 'react-resize-detector'; 3 | 4 | interface Params { 5 | of: React.MutableRefObject; 6 | with: React.MutableRefObject; 7 | } 8 | 9 | export function useSyncHeight({ of: fromRef, with: toRef }: Params) { 10 | const handleResize = () => { 11 | if (!fromRef.current) return; 12 | if (!toRef.current) return; 13 | const { height } = fromRef.current.getBoundingClientRect(); 14 | toRef.current.style.height = height + 'px'; 15 | }; 16 | 17 | const detectorJSX = ; 18 | 19 | return { detectorJSX }; 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/hooks/use-unique-id.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid'; 2 | import { useState } from 'react'; 3 | 4 | export function useUniqueID() { 5 | const [uniqueId] = useState(() => nanoid()); 6 | return uniqueId; 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/hooks/use-update-effect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export function useUpdateEffect(fn: () => void, deps: unknown[]) { 4 | const mounted = useRef(false); 5 | useEffect(() => { 6 | if (!mounted.current) { 7 | mounted.current = true; 8 | } else { 9 | fn(); 10 | } 11 | }, deps); 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/keyboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keyboard-event'; 2 | export * from './list-handlers'; 3 | export * from './select-handlers'; 4 | -------------------------------------------------------------------------------- /src/lib/keyboard/keyboard-event.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface KeyboardEventProps { 4 | event: React.KeyboardEvent; 5 | keyHandler?: HandlerType; 6 | shiftKeyHandler?: HandlerType; 7 | } 8 | 9 | interface HandlerType { 10 | [key: string]: (event: React.SyntheticEvent) => void; 11 | } 12 | 13 | const camelCase = (string: string) => string.charAt(0).toLowerCase() + string.slice(1); 14 | 15 | export const keyboardEventHandle = ({ event, keyHandler, shiftKeyHandler }: KeyboardEventProps) => { 16 | const { shiftKey } = event; 17 | const key = camelCase(event.key); 18 | 19 | if (key && shiftKey && shiftKeyHandler && shiftKeyHandler.hasOwnProperty(key)) { 20 | shiftKeyHandler[key](event); 21 | } 22 | 23 | if (key && keyHandler && keyHandler.hasOwnProperty(key)) { 24 | keyHandler[key](event); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/lib/keyboard/list-handlers.ts: -------------------------------------------------------------------------------- 1 | const LI_TAG = 'LI'; 2 | 3 | export const onKeyDown = (listNode: HTMLElement) => () => { 4 | if (document.activeElement?.tagName === LI_TAG) { 5 | (document.activeElement.nextElementSibling as HTMLElement).focus(); 6 | } else { 7 | (listNode.firstChild as HTMLElement).focus(); 8 | } 9 | }; 10 | 11 | export const onKeyUp = (listNode: HTMLElement) => () => { 12 | if (document.activeElement?.tagName === LI_TAG) { 13 | (document.activeElement.previousElementSibling as HTMLElement).focus(); 14 | } else { 15 | (listNode.lastChild as HTMLElement).focus(); 16 | } 17 | }; 18 | 19 | const onItemEnter = (listNode: HTMLElement) => () => { 20 | if (document.activeElement?.tagName === LI_TAG) { 21 | (document.activeElement as HTMLElement).click(); 22 | } else { 23 | (listNode.lastChild as HTMLElement).focus(); 24 | } 25 | }; 26 | 27 | export const keyHandlerList = ({ listNode }: any) => ({ 28 | arrowDown: onKeyDown(listNode), 29 | arrowUp: onKeyUp(listNode), 30 | enter: onItemEnter(listNode), 31 | }); 32 | -------------------------------------------------------------------------------- /src/lib/keyboard/select-handlers.ts: -------------------------------------------------------------------------------- 1 | interface GetSelectHandlersProps extends EnterProps { 2 | dropdownNode: HTMLElement; 3 | } 4 | 5 | interface EnterProps { 6 | isOpen: boolean; 7 | onChange: React.EventHandler; 8 | selectNode: HTMLElement; 9 | setIsOpen: () => void; 10 | } 11 | 12 | const LI_TAG = 'LI'; 13 | 14 | const onArrowDown = (dropdownNode: HTMLElement) => () => { 15 | if (document.activeElement?.tagName === LI_TAG) { 16 | (document.activeElement.nextElementSibling as HTMLElement).focus(); 17 | } else { 18 | (dropdownNode.firstChild as HTMLElement).focus(); 19 | } 20 | }; 21 | 22 | const onArrowUp = (dropdownNode: HTMLElement) => () => { 23 | if (document.activeElement?.tagName === LI_TAG) { 24 | (document.activeElement.previousElementSibling as HTMLElement).focus(); 25 | } else { 26 | (dropdownNode.lastChild as HTMLElement).focus(); 27 | } 28 | }; 29 | 30 | const onEnter = ({ isOpen, onChange, selectNode, setIsOpen }: EnterProps) => ( 31 | event: React.SyntheticEvent, 32 | ) => { 33 | if (isOpen && document.activeElement?.tagName === LI_TAG) { 34 | onChange(event); 35 | } 36 | setIsOpen(); 37 | selectNode.focus(); 38 | }; 39 | 40 | export const keyHandlerGet = ({ 41 | dropdownNode, 42 | isOpen, 43 | onChange, 44 | selectNode, 45 | setIsOpen, 46 | }: GetSelectHandlersProps) => ({ 47 | arrowDown: onArrowDown(dropdownNode), 48 | arrowUp: onArrowUp(dropdownNode), 49 | enter: onEnter({ selectNode, setIsOpen, isOpen, onChange }), 50 | }); 51 | -------------------------------------------------------------------------------- /src/lib/level-downgrade.ts: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | const MAX_DOWNGRADE = 6; 4 | 5 | export const levelDowngradeCss = css` 6 | [data-woly-component-level-downgrade-wrapper] { 7 | ${range(1, MAX_DOWNGRADE + 1) 8 | .map(tempVarSet) 9 | .join('\n')} 10 | } 11 | 12 | ${range(1, MAX_DOWNGRADE + 1) 13 | .map(tempVarUse) 14 | .join('\n')} 15 | 16 | [data-woly-component-level-downgrade-wrapper] > [data-woly-component-level-downgrade-wrapper] { 17 | background-color: red; 18 | } 19 | 20 | [data-woly-component-level-downgrade-wrapper] 21 | > [data-woly-component-level-downgrade-wrapper]::after { 22 | content: "don't pass data-woly-component-level-downgrade-wrapper directly in another data-woly-component-level-downgrade-wrapper"; 23 | } 24 | `; 25 | 26 | export const levelDowngrade = { 27 | setup: () => ({ 28 | 'data-woly-component-level-downgrade-wrapper': true, 29 | }), 30 | use: ({ diff } = { diff: 1 }) => ({ 31 | 'data-woly-component-level-downgrade': diff, 32 | }), 33 | }; 34 | 35 | function tempVarSet(num: number) { 36 | return `--woly-component-level-temp-${num}: max(calc(var(--woly-component-level) - ${num}), 0);`; 37 | } 38 | 39 | function tempVarUse(num: number) { 40 | return ` 41 | [data-woly-component-level-downgrade-wrapper] > [data-woly-component-level-downgrade='${num}'] { 42 | --woly-component-level: var(--woly-component-level-temp-${num}); 43 | } 44 | `; 45 | } 46 | 47 | function range(start: number, end: number) { 48 | return [...new Array(end).keys()].slice(start); 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/palette.tsx: -------------------------------------------------------------------------------- 1 | import { css } from 'styled-components'; 2 | 3 | /** 4 | * 5 | * @param color : hsl => "255, 85%, 58%""; 6 | */ 7 | 8 | export const getHsl = (color: string) => { 9 | const colors = color.replace(' ', '').split(','); 10 | const hue = Number.parseInt(colors[0], 10); 11 | const saturation = Number.parseFloat(colors[1]); 12 | const lightness = Number.parseFloat(colors[2]); 13 | return { hue, saturation, lightness }; 14 | }; 15 | 16 | const lightnessFn = (number: number, lightness: number) => lightness + 50 - 0.1 * number; 17 | 18 | const saturationFn = (number: number, saturation: number) => { 19 | /** for black and white palette => saturation doesn't change */ 20 | if (saturation < 30 || number === 500) return saturation; 21 | if (number > 500) return saturation - 30; 22 | return saturation - 10; 23 | }; 24 | 25 | const COLOR_NUMBERS = [100, 200, 300, 400, 500, 600, 700, 800]; 26 | 27 | export const createPalette = (color: string, name: string) => { 28 | const hsl = getHsl(color); 29 | 30 | const palette = COLOR_NUMBERS.map((number) => { 31 | const saturation = saturationFn(number, hsl.saturation); 32 | const lightness = lightnessFn(number, hsl.lightness); 33 | 34 | return `--${name}-${number}: ${hsl.hue}, ${saturation}%, ${lightness}%;`; 35 | }); 36 | 37 | return css` 38 | ${palette} 39 | `; 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/position-relative.tsx: -------------------------------------------------------------------------------- 1 | type PositionProps = 'bottom' | 'top' | 'left' | 'right'; 2 | 3 | const oppositePosition: { [key: string]: PositionProps } = { 4 | bottom: 'top', 5 | left: 'right', 6 | right: 'left', 7 | top: 'bottom', 8 | }; 9 | 10 | export function positionRelativeGet(node: Element, position: PositionProps): PositionProps { 11 | const child = node.lastChild as Element | null; 12 | 13 | if (child === null) { 14 | return position; 15 | } 16 | 17 | const { top, left, bottom, right } = node.getBoundingClientRect(); 18 | const { width, height } = child.getBoundingClientRect(); 19 | 20 | const noSpaceTop = top < height; 21 | const noSpaceBottom = window.innerHeight - bottom < height; 22 | const noSpaceLeft = left < width; 23 | const noSpaceRight = window.innerWidth - right < width; 24 | 25 | if ( 26 | (noSpaceTop && position === 'top') || 27 | (noSpaceBottom && position === 'bottom') || 28 | (noSpaceLeft && position === 'left') || 29 | (noSpaceRight && position === 'right') 30 | ) { 31 | return oppositePosition[position]; 32 | } 33 | return position; 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/types.ts: -------------------------------------------------------------------------------- 1 | export type IfEmpty = keyof T extends never 2 | ? { 3 | priority?: string; 4 | } 5 | : T; 6 | 7 | export type Priority = IfEmpty; 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 10 | export interface DefaultPriority {} 11 | -------------------------------------------------------------------------------- /src/static/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/check-filled-unchecked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/check-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/static/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/close-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/color-palette.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/static/icons/dots-horizontaldd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/eye-closed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/static/icons/eye-opened.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/static/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IconArrowDown } from './arrow-down.svg'; 2 | export { default as IconArrowLeft } from './arrow-left.svg'; 3 | export { default as IconCheck } from './check.svg'; 4 | export { default as IconCheckFilled } from './check-filled.svg'; 5 | export { default as IconEyeClosed } from './eye-closed.svg'; 6 | export { default as IconClose } from './close.svg'; 7 | export { default as IconInfo } from './info.svg'; 8 | export { default as IconDotsHorizontal } from './dots-horizontaldd.svg'; 9 | export { default as IconEyeOpened } from './eye-opened.svg'; 10 | export { default as IconPlus } from './plus.svg'; 11 | export { default as IconSearch } from './search.svg'; 12 | export { default as IconFilledUnchecked } from './check-filled-unchecked.svg'; 13 | export { default as IconSpinner } from './spinner.svg'; 14 | export { default as IconProfile } from './profile.svg'; 15 | export { default as IconColorPalette } from './color-palette.svg'; 16 | export { default as Threebars } from './threebars.svg'; 17 | -------------------------------------------------------------------------------- /src/static/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/static/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icons/threebars.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/styleguide/html/data-attribute/data-attribute.mdx: -------------------------------------------------------------------------------- 1 | # Styleguide for data-attributes 2 | 3 | Data-attributes allow us to store extra information on standard, semantic HTML elements. 4 | The data attribute can also be used for styling. 5 | You can access them from CSS to add styles to the tag that have specific data-attribute. 6 | 7 | It's simpler and cleaner to write data attributes as data-name, where a name is a concrete name of a data attribute. 8 | 9 | Use `data-element` attribute for styling (it is semantic and consistent): 10 | 11 | ```html 12 |
13 | ``` 14 | 15 | If you have several values of component states that you need to pass to a data attribute, make data attribute more unique with adding a name to data attribute. 16 | You can do it in this way: 17 | 18 | ```html 19 | data-name="value" 20 | ``` 21 | 22 | For example: 23 | 24 | ```html 25 |
horizontal block
26 | 27 |
vertical block
28 | ``` 29 | -------------------------------------------------------------------------------- /src/types/woly.d.ts: -------------------------------------------------------------------------------- 1 | declare interface DefaultPriority { 2 | priority?: 'default' | 'primary' | 'secondary' | 'danger' | 'success'; 3 | } 4 | -------------------------------------------------------------------------------- /src/upload/elements/index.ts: -------------------------------------------------------------------------------- 1 | export { UploadArea } from './upload-area'; 2 | -------------------------------------------------------------------------------- /src/upload/elements/upload-area/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled, { StyledComponent } from 'styled-components'; 3 | import { Priority } from 'lib/types'; 4 | 5 | interface UploadAreaProps { 6 | center?: boolean; 7 | className?: string; 8 | content?: React.ReactNode; 9 | disabled?: boolean; 10 | focus?: boolean; 11 | } 12 | 13 | const UploadAreaBase: React.FC = ({ 14 | center = false, 15 | className, 16 | content, 17 | focus = false, 18 | priority = 'default', 19 | }) => ( 20 |
21 |
22 | {content} 23 |
24 |
25 |
26 | ); 27 | 28 | export const UploadArea = styled(UploadAreaBase)` 29 | --local-border-color: var(--woly-canvas-hover); 30 | --local-background-color: var(--woly-canvas-disabled); 31 | position: relative; 32 | 33 | width: 100%; 34 | height: 100%; 35 | overflow: auto; 36 | 37 | background-color: var(--local-background-color); 38 | border: var(--woly-border-width) dashed var(--local-border-color); 39 | border-radius: var(--woly-rounding); 40 | 41 | [data-position-center='true'] { 42 | position: absolute; 43 | top: 50%; 44 | left: 50%; 45 | 46 | transform: translate(-50%, -50%); 47 | } 48 | 49 | &[data-focus='true'] { 50 | --local-border-color: var(--woly-canvas-active); 51 | 52 | outline: none; 53 | 54 | [data-element='overlay'] { 55 | position: absolute; 56 | top: 50%; 57 | left: 50%; 58 | z-index: 1; 59 | 60 | width: 100%; 61 | height: 100%; 62 | 63 | background: rgba(110, 59, 254, 0.05); 64 | transform: translate(-50%, -50%); 65 | } 66 | } 67 | ` as StyledComponent<'div', Record, UploadAreaProps & Priority>; 68 | -------------------------------------------------------------------------------- /src/upload/index.ts: -------------------------------------------------------------------------------- 1 | export * from './molecules'; 2 | -------------------------------------------------------------------------------- /src/upload/molecules/button-uploader/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Button } from 'ui/atoms'; 4 | import { DropzoneOptions, useDropzone } from 'react-dropzone'; 5 | import { Priority } from 'lib/types'; 6 | 7 | interface ButtonUploaderProps { 8 | accept?: DropzoneOptions['accept']; 9 | children?: never; 10 | className?: string; 11 | disabled?: boolean; 12 | icon?: React.ReactNode; 13 | maxFiles?: DropzoneOptions['maxFiles']; 14 | maxSize?: DropzoneOptions['maxFiles']; 15 | multiple?: DropzoneOptions['multiple']; 16 | onChange: DropzoneOptions['onDrop']; 17 | onDrop: never; 18 | validator?: DropzoneOptions['validator']; 19 | outlined?: boolean; 20 | text: string; 21 | } 22 | 23 | export const ButtonUploaderBase: React.FC = ({ 24 | accept, 25 | className, 26 | disabled, 27 | icon, 28 | maxFiles, 29 | maxSize, 30 | multiple = false, 31 | onChange, 32 | outlined, 33 | text, 34 | priority = 'secondary', 35 | validator, 36 | }) => { 37 | const { getRootProps, getInputProps } = useDropzone({ 38 | accept, 39 | disabled, 40 | maxFiles, 41 | maxSize, 42 | multiple, 43 | noDrag: true, 44 | onDrop: onChange, 45 | validator, 46 | }); 47 | 48 | return ( 49 |
50 |
53 | ); 54 | }; 55 | 56 | export const ButtonUploader = styled(ButtonUploaderBase)` 57 | outline: none; 58 | 59 | input[data-element='file'] { 60 | display: none; 61 | } 62 | `; 63 | -------------------------------------------------------------------------------- /src/upload/molecules/drag-uploader/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styled from 'styled-components'; 3 | import { DropzoneOptions, useDropzone } from 'react-dropzone'; 4 | import { Priority } from 'lib/types'; 5 | 6 | import { UploadArea } from '../../elements'; 7 | 8 | interface DragUploaderProps extends DropzoneOptions { 9 | accept?: DropzoneOptions['accept']; 10 | center?: boolean; 11 | className: string; 12 | content: React.ReactNode; 13 | disabled?: DropzoneOptions['disabled']; 14 | maxFiles?: DropzoneOptions['maxFiles']; 15 | maxSize?: DropzoneOptions['maxSize']; 16 | multiple?: DropzoneOptions['multiple']; 17 | onDrop?: DropzoneOptions['onDrop']; 18 | validator?: DropzoneOptions['validator']; 19 | } 20 | 21 | export const DragUploaderBase: React.FC = ({ 22 | accept, 23 | center = false, 24 | className, 25 | content, 26 | disabled = false, 27 | maxFiles, 28 | maxSize, 29 | multiple, 30 | onDrop, 31 | validator, 32 | priority = 'secondary', 33 | }) => { 34 | const [focus, setFocus] = React.useState(false); 35 | 36 | const { getRootProps, getInputProps } = useDropzone({ 37 | accept, 38 | disabled, 39 | maxFiles, 40 | maxSize, 41 | multiple, 42 | onDrop, 43 | validator, 44 | }); 45 | 46 | const { ref, ...rootProps } = getRootProps(); 47 | 48 | return ( 49 |
setFocus(true)} 53 | onBlur={() => setFocus(false)} 54 | > 55 | 56 | 57 |
58 | ); 59 | }; 60 | 61 | export const DragUploader = styled(DragUploaderBase)` 62 | width: 100%; 63 | height: 100%; 64 | 65 | outline: none; 66 | 67 | input[data-element='file'] { 68 | display: none; 69 | } 70 | `; 71 | -------------------------------------------------------------------------------- /src/upload/molecules/index.ts: -------------------------------------------------------------------------------- 1 | export { DragUploader } from './drag-uploader'; 2 | export { ButtonUploader } from './button-uploader'; 3 | -------------------------------------------------------------------------------- /src/woly/atoms/avatar/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React, { forwardRef } from 'react'; 3 | import { IconProfile } from 'static/icons'; 4 | 5 | import { useImageLoad } from './use-image-load'; 6 | 7 | type BaseAvatarProps = React.BaseHTMLAttributes; 8 | 9 | export type AvatarProps = BaseAvatarProps & { 10 | alt?: string; 11 | src?: string; 12 | srcSet?: string; 13 | children?: React.ReactNode; 14 | }; 15 | 16 | const AvatarBase = forwardRef( 17 | ({ alt, children: childrenProp, src, srcSet, ...rest }, avatarRef) => { 18 | const loadFailed = useImageLoad({ src, srcSet }); 19 | const hasImg = src || srcSet; 20 | let children = null; 21 | 22 | if (hasImg && !loadFailed) { 23 | children = {alt}; 24 | } else if (childrenProp) { 25 | children = childrenProp; 26 | } else { 27 | // render fallback if image loading failed or no src attributes / children provided 28 | children = ; 29 | } 30 | 31 | return ( 32 |
33 | {children} 34 |
35 | ); 36 | }, 37 | ); 38 | 39 | const Fallback = styled(IconProfile)` 40 | > circle:first-of-type { 41 | fill: var(--woly-canvas-text-default); 42 | fill-opacity: 0.1; 43 | } 44 | `; 45 | 46 | export const Avatar = styled(AvatarBase)` 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | 51 | --local-size: calc((var(--woly-component-level) + 2) * 2 * var(--woly-const-m)); 52 | 53 | width: var(--local-size); 54 | height: var(--local-size); 55 | 56 | & > * { 57 | width: 100%; 58 | height: 100%; 59 | 60 | border-radius: 50%; 61 | } 62 | `; 63 | -------------------------------------------------------------------------------- /src/woly/atoms/avatar/usage.mdx: -------------------------------------------------------------------------------- 1 | import {Avatar} from 'ui' 2 | import {Playground, block} from 'dev/playground' 3 | 4 | `Avatar` shows user avatar 5 | 6 | ### Example 7 | 8 | 9 | 10 | 11 | 12 | ### Sizes 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ### Fallback 36 | 37 | 38 | 39 | 40 | 41 | ### Custom child component 42 | 43 | 44 | 45 |
55 | RC 56 |
57 |
58 |
59 | 60 | ### Props 61 | 62 | | Name | Type | Default | Description | 63 | | ---------- | ----------------- | ----------- | ---------------------------------------- | 64 | | `alt` | `string` | `''` | text description of the image | 65 | | `children` | `React.ReactNode` | `undefined` | use if no src attributes provided | 66 | | `src` | `string` | `''` | avatar src | 67 | | `srcSet` | `string` | `''` | avatar src set for multiple screen sizes | 68 | -------------------------------------------------------------------------------- /src/woly/atoms/avatar/use-image-load.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useImageLoad({ src, srcSet }: { src?: string; srcSet?: string }) { 4 | const [failed, setFailed] = useState(false); 5 | 6 | useEffect(() => { 7 | if (!src && !srcSet) { 8 | return; 9 | } 10 | 11 | setFailed(false); 12 | 13 | const image = new Image(); 14 | image.src = src ?? ''; 15 | image.srcset = srcSet ?? ''; 16 | 17 | const onLoad = () => { 18 | setFailed(false); 19 | }; 20 | 21 | const onError = () => { 22 | setFailed(true); 23 | }; 24 | 25 | image.addEventListener('load', onLoad); 26 | image.addEventListener('error', onError); 27 | 28 | return () => { 29 | image.removeEventListener('load', onLoad); 30 | image.removeEventListener('error', onError); 31 | }; 32 | }, [src, srcSet]); 33 | 34 | return failed; 35 | } 36 | -------------------------------------------------------------------------------- /src/woly/atoms/backdrop/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Backdrop = styled.div` 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | 10 | background: var(--woly-backdrop); 11 | `; 12 | -------------------------------------------------------------------------------- /src/woly/atoms/backdrop/usage.mdx: -------------------------------------------------------------------------------- 1 | import {Playground, State} from 'dev/playground' 2 | import {Button, Field, Input, Modal} from 'ui' 3 | 4 | `Backdrop` is a dark layer that covers full screen. It can be used in Modal windows or Aside menu panel. 5 | 6 | ### Example 7 | 8 | Modal based on `Backdrop`. 9 | 10 | 11 | !i}> 12 | {(value, change) => ( 13 | <> 14 |
39 | 40 | 41 | 42 | )} 43 | 44 | 45 | 46 | ### Props 47 | 48 | | Name | Type | Default | Description | 49 | | ---------- | -------- | ------------- | ----------------------------------------- | 50 | | `priority` | `string` | `'secondary'` | Priority prop to style Backdrop component | 51 | -------------------------------------------------------------------------------- /src/woly/atoms/box/index.ts: -------------------------------------------------------------------------------- 1 | import styled, { StyledComponent } from 'styled-components'; 2 | import { box, boxVertical, lineBox } from 'ui/elements/box'; 3 | 4 | export const Box = styled.div` 5 | ${box} 6 | ` as StyledComponent<'div', Record>; 7 | 8 | export const LineBox = styled.div` 9 | ${lineBox} 10 | ` as StyledComponent<'div', Record>; 11 | 12 | export const BoxVertical = styled.div` 13 | ${boxVertical} 14 | ` as StyledComponent<'div', Record>; 15 | -------------------------------------------------------------------------------- /src/woly/atoms/box/usage.mdx: -------------------------------------------------------------------------------- 1 | import {Box, BoxVertical} from './index'; 2 | import {Text, Tooltip, Button, Accordion, TextArea, Input, Checkbox} from 'ui'; 3 | import {block, Playground, State} from 'dev/playground'; 4 | import {IconInfo} from 'static/icons'; 5 | 6 | `Box` and `BoxVertical` are wrapper components, that add a box model to elements that don't have it. 7 | 8 | ## Example with Box 9 | 10 | 11 | 12 | 13 | 14 | 17 | г. Комсомольск на Амуре, ул. 1-го октября, д. 145, корп. 9, кв. 16 18 | 19 | } 20 | position="bottom" 21 | > 22 | } 24 | name="adress" 25 | onChange={() => console.info('On input change')} 26 | type="text" 27 | priority="primary" 28 | value="г. Комсомольск на Амуре, ул. 1-го октября, д. 145, корп. 9, кв. 16" 29 | /> 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | Согласие на обработку персональных данных – это письменное разрешение гражданина 42 | Российской Федерации, которое он дает заинтересованной стороне на получение, сбор, 43 | хранение и использование персональных сведений о себе. 44 | 45 | 46 | 47 | 48 |