diff --git a/.babelrc.js b/.babelrc.js index 0a1cd16..b70c812 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,3 +1,3 @@ module.exports = { - plugins: ['styled-components'], + plugins: ['styled-components'], }; diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 64daca7..c13b666 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,34 +1,34 @@ { - "name": "Roleypoly (Go, Node)", - "image": "ghcr.io/roleypoly/dev-container:main", - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash" - }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint", - "golang.go", - "hashicorp.terraform", - "firsttris.vscode-jest-runner", - "esbenp.prettier-vscode", - "jpoissonnier.vscode-styled-components", - "eg2.vscode-npm-script", - "christian-kohler.npm-intellisense", - "ms-azuretools.vscode-docker", - "eamodio.gitlens", - "davidanson.vscode-markdownlint", - "stylelint.vscode-stylelint", - "pflannery.vscode-versionlens", - "visualstudioexptteam.vscodeintellicode", - "bungcip.better-toml" - ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "uname -a", - // Uncomment when using a ptrace-based debugger like C++, Go, and Rust - // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], - // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + "name": "Roleypoly (Go, Node)", + "image": "ghcr.io/roleypoly/dev-container:main", + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "dbaeumer.vscode-eslint", + "golang.go", + "hashicorp.terraform", + "firsttris.vscode-jest-runner", + "esbenp.prettier-vscode", + "jpoissonnier.vscode-styled-components", + "eg2.vscode-npm-script", + "christian-kohler.npm-intellisense", + "ms-azuretools.vscode-docker", + "eamodio.gitlens", + "davidanson.vscode-markdownlint", + "stylelint.vscode-stylelint", + "pflannery.vscode-versionlens", + "visualstudioexptteam.vscodeintellicode", + "bungcip.better-toml" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + // Uncomment when using a ptrace-based debugger like C++, Go, and Rust + // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" } diff --git a/.eslintrc.js b/.eslintrc.js index 13c4e15..fb4af04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,128 +1,128 @@ module.exports = { - env: { - browser: true, - es6: true, - node: true, - }, - extends: ['prettier', 'prettier/@typescript-eslint'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - }, - plugins: [ - 'eslint-plugin-import', - 'eslint-plugin-jsdoc', - 'eslint-plugin-react', - '@typescript-eslint', - '@typescript-eslint/tslint', + env: { + browser: true, + es6: true, + node: true, + }, + extends: ['prettier', 'prettier/@typescript-eslint'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module', + }, + plugins: [ + 'eslint-plugin-import', + 'eslint-plugin-jsdoc', + 'eslint-plugin-react', + '@typescript-eslint', + '@typescript-eslint/tslint', + ], + rules: { + 'react/jsx-uses-react': 'off', + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/consistent-type-assertions': 'error', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/member-delimiter-style': [ + 'off', + { + multiline: { + delimiter: 'none', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, ], - rules: { - 'react/jsx-uses-react': 'off', - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/consistent-type-assertions': 'error', - '@typescript-eslint/indent': 'off', - '@typescript-eslint/member-delimiter-style': [ - 'off', - { - multiline: { - delimiter: 'none', - requireLast: true, - }, - singleline: { - delimiter: 'semi', - requireLast: false, - }, - }, - ], - '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-misused-new': 'error', - '@typescript-eslint/no-unnecessary-qualifier': 'error', - '@typescript-eslint/no-unnecessary-type-assertion': 'error', - '@typescript-eslint/no-unused-expressions': [ - 'error', - { - allowTaggedTemplates: true, - allowShortCircuit: true, - }, - ], - '@typescript-eslint/prefer-namespace-keyword': 'error', - '@typescript-eslint/quotes': 'off', - '@typescript-eslint/semi': ['off', null], - '@typescript-eslint/triple-slash-reference': [ - 'error', - { - path: 'always', - types: 'prefer-import', - lib: 'always', - }, - ], - '@typescript-eslint/type-annotation-spacing': 'off', - '@typescript-eslint/unified-signatures': 'error', - 'arrow-parens': ['off', 'always'], - 'brace-style': ['off', 'off'], - 'comma-dangle': 'off', - curly: ['error', 'multi-line'], - 'eol-last': 'off', - eqeqeq: ['error', 'smart'], - 'id-blacklist': [ - 'error', - 'any', - 'Number', - 'number', - 'String', - 'string', - 'Boolean', - 'boolean', - 'Undefined', - 'undefined', - ], - 'id-match': 'error', - 'import/no-deprecated': 'error', - 'jsdoc/check-alignment': 'error', - 'jsdoc/check-indentation': 'error', - 'jsdoc/newline-after-description': 'error', - 'linebreak-style': 'off', - 'max-len': 'off', - 'new-parens': 'off', - 'newline-per-chained-call': 'off', - 'no-caller': 'error', - 'no-cond-assign': 'error', - 'no-constant-condition': 'error', - 'no-control-regex': 'error', - 'no-duplicate-imports': 'error', - 'no-empty': 'error', - 'no-eval': 'error', - 'no-extra-semi': 'off', - 'no-fallthrough': 'error', - 'no-invalid-regexp': 'error', - 'no-irregular-whitespace': 'off', - 'no-multiple-empty-lines': 'off', - 'no-redeclare': 'error', - 'no-regex-spaces': 'error', - 'no-return-await': 'error', - 'no-throw-literal': 'error', - 'no-trailing-spaces': 'off', - 'no-underscore-dangle': 'error', - 'no-unused-labels': 'error', - 'no-var': 'error', - 'one-var': ['error', 'never'], - 'quote-props': 'off', - radix: 'error', - 'react/jsx-curly-spacing': 'off', - 'react/jsx-equals-spacing': 'off', - 'react/jsx-wrap-multilines': 'off', - 'space-before-function-paren': 'off', - 'space-in-parens': ['off', 'never'], - 'spaced-comment': [ - 'error', - 'always', - { - markers: ['/'], - }, - ], - 'use-isnan': 'error', - }, + '@typescript-eslint/no-empty-function': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-new': 'error', + '@typescript-eslint/no-unnecessary-qualifier': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowTaggedTemplates: true, + allowShortCircuit: true, + }, + ], + '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/quotes': 'off', + '@typescript-eslint/semi': ['off', null], + '@typescript-eslint/triple-slash-reference': [ + 'error', + { + path: 'always', + types: 'prefer-import', + lib: 'always', + }, + ], + '@typescript-eslint/type-annotation-spacing': 'off', + '@typescript-eslint/unified-signatures': 'error', + 'arrow-parens': ['off', 'always'], + 'brace-style': ['off', 'off'], + 'comma-dangle': 'off', + curly: ['error', 'multi-line'], + 'eol-last': 'off', + eqeqeq: ['error', 'smart'], + 'id-blacklist': [ + 'error', + 'any', + 'Number', + 'number', + 'String', + 'string', + 'Boolean', + 'boolean', + 'Undefined', + 'undefined', + ], + 'id-match': 'error', + 'import/no-deprecated': 'error', + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'error', + 'jsdoc/newline-after-description': 'error', + 'linebreak-style': 'off', + 'max-len': 'off', + 'new-parens': 'off', + 'newline-per-chained-call': 'off', + 'no-caller': 'error', + 'no-cond-assign': 'error', + 'no-constant-condition': 'error', + 'no-control-regex': 'error', + 'no-duplicate-imports': 'error', + 'no-empty': 'error', + 'no-eval': 'error', + 'no-extra-semi': 'off', + 'no-fallthrough': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'off', + 'no-multiple-empty-lines': 'off', + 'no-redeclare': 'error', + 'no-regex-spaces': 'error', + 'no-return-await': 'error', + 'no-throw-literal': 'error', + 'no-trailing-spaces': 'off', + 'no-underscore-dangle': 'error', + 'no-unused-labels': 'error', + 'no-var': 'error', + 'one-var': ['error', 'never'], + 'quote-props': 'off', + radix: 'error', + 'react/jsx-curly-spacing': 'off', + 'react/jsx-equals-spacing': 'off', + 'react/jsx-wrap-multilines': 'off', + 'space-before-function-paren': 'off', + 'space-in-parens': ['off', 'never'], + 'spaced-comment': [ + 'error', + 'always', + { + markers: ['/'], + }, + ], + 'use-isnan': 'error', + }, }; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 27c65c3..ed72a4a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,21 +1,21 @@ version: 2 updates: - - package-ecosystem: 'npm' - directory: '/' - schedule: - interval: 'daily' + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'daily' - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'daily' + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'daily' - - package-ecosystem: 'gomod' - directory: '/' - schedule: - interval: 'daily' + - package-ecosystem: 'gomod' + directory: '/' + schedule: + interval: 'daily' - - package-ecosystem: 'terraform' - directory: '/terraform' - schedule: - interval: 'daily' + - package-ecosystem: 'terraform' + directory: '/terraform' + schedule: + interval: 'daily' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d5e427..6bb6604 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,211 +3,211 @@ name: Roleypoly CI on: push jobs: - go_test: - runs-on: ubuntu-latest - name: Go CI - steps: - - uses: actions/checkout@master - - uses: actions/cache@v2.1.4 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - uses: actions/setup-go@v2 - with: - go-version: '^1.15.5' + go_test: + runs-on: ubuntu-latest + name: Go CI + steps: + - uses: actions/checkout@master + - uses: actions/cache@v2.1.4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - uses: actions/setup-go@v2 + with: + go-version: '^1.15.5' - - run: go vet ./... + - run: go vet ./... - - run: go test ./... + - run: go test ./... - node_test: - runs-on: ubuntu-latest - name: Node CI - steps: - - uses: actions/checkout@master + node_test: + runs-on: ubuntu-latest + name: Node CI + steps: + - uses: actions/checkout@master - - uses: actions/setup-node@v2.1.5 - with: - node-version: '14' + - uses: actions/setup-node@v2.1.5 + with: + node-version: '14' - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2.1.4 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + - uses: actions/cache@v2.1.4 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - - run: yarn install --frozen-lockfile + - run: yarn install --frozen-lockfile - - run: yarn lint + - run: yarn lint - - run: yarn test + - run: yarn test - worker_build: - runs-on: ubuntu-latest - name: Worker Build & Publish - needs: - - node_test - steps: - - uses: actions/checkout@master + worker_build: + runs-on: ubuntu-latest + name: Worker Build & Publish + needs: + - node_test + steps: + - uses: actions/checkout@master - - uses: actions/setup-node@v2.1.5 - with: - node-version: '14' + - uses: actions/setup-node@v2.1.5 + with: + node-version: '14' - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@master - with: - project_id: ${{ secrets.GCS_PROJECT_ID }} - service_account_key: ${{ secrets.GCS_TF_KEY }} - export_default_credentials: true + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@master + with: + project_id: ${{ secrets.GCS_PROJECT_ID }} + service_account_key: ${{ secrets.GCS_TF_KEY }} + export_default_credentials: true - - name: Check if already deployed - id: check - run: | - gsutil stat gs://roleypoly-artifacts/backend-worker/${{ github.sha }}/script.js \ - && echo ::set-output name=skip::1 \ - || echo ::set-output name=skip::0 + - name: Check if already deployed + id: check + run: | + gsutil stat gs://roleypoly-artifacts/backend-worker/${{ github.sha }}/script.js \ + && echo ::set-output name=skip::1 \ + || echo ::set-output name=skip::0 - - run: npm i -g @cloudflare/wrangler - if: steps.check.outputs.skip == '0' + - run: npm i -g @cloudflare/wrangler + if: steps.check.outputs.skip == '0' - - name: Get yarn cache directory path - if: steps.check.outputs.skip == '0' - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Get yarn cache directory path + if: steps.check.outputs.skip == '0' + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v2.1.4 - if: steps.check.outputs.skip == '0' - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + - uses: actions/cache@v2.1.4 + if: steps.check.outputs.skip == '0' + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - - run: yarn install --frozen-lockfile - if: steps.check.outputs.skip == '0' + - run: yarn install --frozen-lockfile + if: steps.check.outputs.skip == '0' - - run: | - wrangler init - echo 'webpack_config = "packages/api/webpack.config.js"' | tee -a wrangler.toml - wrangler build - if: steps.check.outputs.skip == '0' + - run: | + wrangler init + echo 'webpack_config = "packages/api/webpack.config.js"' | tee -a wrangler.toml + wrangler build + if: steps.check.outputs.skip == '0' - - id: upload-file - if: steps.check.outputs.skip == '0' - uses: google-github-actions/upload-cloud-storage@main - with: - path: worker/script.js - destination: roleypoly-artifacts/backend-worker/${{ github.sha }} - credentials: ${{ secrets.GCS_TF_KEY }} + - id: upload-file + if: steps.check.outputs.skip == '0' + uses: google-github-actions/upload-cloud-storage@main + with: + path: worker/script.js + destination: roleypoly-artifacts/backend-worker/${{ github.sha }} + credentials: ${{ secrets.GCS_TF_KEY }} - docker_build: - name: Docker Build & Publish - runs-on: ubuntu-latest - needs: - - go_test - - node_test - strategy: - matrix: - dockerfile: - - bot - steps: - - uses: actions/checkout@master + docker_build: + name: Docker Build & Publish + runs-on: ubuntu-latest + needs: + - go_test + - node_test + strategy: + matrix: + dockerfile: + - bot + steps: + - uses: actions/checkout@master - - uses: actions/cache@v2.1.4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + - uses: actions/cache@v2.1.4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- - - name: Docker meta - id: docker_meta - uses: crazy-max/ghaction-docker-meta@v1 - with: - images: | - ghcr.io/roleypoly/${{matrix.dockerfile}} - tag-sha: true + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v1 + with: + images: | + ghcr.io/roleypoly/${{matrix.dockerfile}} + tag-sha: true - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - with: - install: true + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + with: + install: true - - name: Login to GHCR - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: roleypoly - password: ${{ secrets.GHCR_PAT }} + - name: Login to GHCR + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: roleypoly + password: ${{ secrets.GHCR_PAT }} - - name: Build and push - uses: docker/build-push-action@v2 - id: docker - with: - context: . - file: ./hack/dockerfiles/${{matrix.dockerfile}}.Dockerfile - push: true - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} + - name: Build and push + uses: docker/build-push-action@v2 + id: docker + with: + context: . + file: ./hack/dockerfiles/${{matrix.dockerfile}}.Dockerfile + push: true + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} - - name: Pre-deploy - Save digest.txt - run: | - echo "${{ steps.docker.outputs.digest }}" > digest.txt + - name: Pre-deploy - Save digest.txt + run: | + echo "${{ steps.docker.outputs.digest }}" > digest.txt - - name: Pre-deploy - Make digest artifact - uses: actions/upload-artifact@v2.2.2 - with: - name: ${{ matrix.dockerfile }}-digest - path: digest.txt + - name: Pre-deploy - Make digest artifact + uses: actions/upload-artifact@v2.2.2 + with: + name: ${{ matrix.dockerfile }}-digest + path: digest.txt - trigger_deploy: - name: Deploy to Stage - needs: - - docker_build - - worker_build - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - steps: - - name: Get Bot digest - uses: actions/download-artifact@v2 - with: - name: bot-digest - path: .digests/bot + trigger_deploy: + name: Deploy to Stage + needs: + - docker_build + - worker_build + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Get Bot digest + uses: actions/download-artifact@v2 + with: + name: bot-digest + path: .digests/bot - - name: Set digests as addressable - id: digests - env: - IMAGES: bot - run: | - set_digest_output() { - echo ::set-output name=$1::@$(cat .digests/$1/digest.txt) - } + - name: Set digests as addressable + id: digests + env: + IMAGES: bot + run: | + set_digest_output() { + echo ::set-output name=$1::@$(cat .digests/$1/digest.txt) + } - for image in $IMAGES; do - set_digest_output $image - done + for image in $IMAGES; do + set_digest_output $image + done - - name: Invoke Deploy workflow - uses: benc-uk/workflow-dispatch@v1 - with: - workflow: Deploy - token: ${{ secrets.GITOPS_TOKEN }} - inputs: |- - { - "environment": "stage", - "worker_tag": "${{ github.sha }}", - "bot_tag": "${{ steps.digests.output.bot }}" - } + - name: Invoke Deploy workflow + uses: benc-uk/workflow-dispatch@v1 + with: + workflow: Deploy + token: ${{ secrets.GITOPS_TOKEN }} + inputs: |- + { + "environment": "stage", + "worker_tag": "${{ github.sha }}", + "bot_tag": "${{ steps.digests.output.bot }}" + } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 71cbd24..c199be5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,51 +1,51 @@ name: 'Code Scanning - Action' on: - push: - pull_request: - schedule: - # ┌───────────── minute (0 - 59) - # │ ┌───────────── hour (0 - 23) - # │ │ ┌───────────── day of the month (1 - 31) - # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) - # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) - # │ │ │ │ │ - # │ │ │ │ │ - # │ │ │ │ │ - # * * * * * - - cron: '30 1 * * 0' + push: + pull_request: + schedule: + # ┌───────────── minute (0 - 59) + # │ ┌───────────── hour (0 - 23) + # │ │ ┌───────────── day of the month (1 - 31) + # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + # * * * * * + - cron: '30 1 * * 0' jobs: - CodeQL-Build: - # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest - runs-on: ubuntu-latest + CodeQL-Build: + # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest + runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 + steps: + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - # with: - # languages: go, javascript, csharp, python, cpp, java + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below). - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below). + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following - # three lines and modify them (or add more) to build your code if your - # project uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following + # three lines and modify them (or add more) to build your code if your + # project uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 98a5eb0..e5c8d87 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,128 +1,128 @@ name: Deploy on: - workflow_dispatch: - inputs: - environment: - description: 'One of: stage, prod' - required: true - default: stage - bot_tag: - description: 'tag/digest reference to a UI container build' - required: false - default: ':main' - worker_tag: - description: 'bucket key to fetch worker from' - required: false - default: '' # Empty will try using current main branch hash + workflow_dispatch: + inputs: + environment: + description: 'One of: stage, prod' + required: true + default: stage + bot_tag: + description: 'tag/digest reference to a UI container build' + required: false + default: ':main' + worker_tag: + description: 'bucket key to fetch worker from' + required: false + default: '' # Empty will try using current main branch hash jobs: - deploy_terraform: - name: Deploy Terraform - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master + deploy_terraform: + name: Deploy Terraform + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master - - uses: hashicorp/setup-terraform@v1.3.2 - with: - terraform_version: ^0.14.0 + - uses: hashicorp/setup-terraform@v1.3.2 + with: + terraform_version: ^0.14.0 - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@master - with: - project_id: ${{ secrets.GCS_PROJECT_ID }} - service_account_key: ${{ secrets.GCS_TF_KEY }} - export_default_credentials: true + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@master + with: + project_id: ${{ secrets.GCS_PROJECT_ID }} + service_account_key: ${{ secrets.GCS_TF_KEY }} + export_default_credentials: true - - name: Get Google Secrets (they keep them in a box under a tree) - id: secrets - uses: google-github-actions/get-secretmanager-secrets@main - with: - secrets: |- - secretJSON:${{ secrets.GCS_PROJECT_ID }}/${{github.event.inputs.environment}}-tfvars + - name: Get Google Secrets (they keep them in a box under a tree) + id: secrets + uses: google-github-actions/get-secretmanager-secrets@main + with: + secrets: |- + secretJSON:${{ secrets.GCS_PROJECT_ID }}/${{github.event.inputs.environment}}-tfvars - - name: Pull necessary artifacts - working-directory: ./terraform - run: | - currentHash=${{ github.sha }} - targetArtifact=${{ github.event.inputs.worker_tag }} - selected="${targetArtifact:-$currentHash}" + - name: Pull necessary artifacts + working-directory: ./terraform + run: | + currentHash=${{ github.sha }} + targetArtifact=${{ github.event.inputs.worker_tag }} + selected="${targetArtifact:-$currentHash}" - mkdir worker-dist - gsutil cp gs://roleypoly-artifacts/backend-worker/$selected/script.js worker-dist/backend-worker.js + mkdir worker-dist + gsutil cp gs://roleypoly-artifacts/backend-worker/$selected/script.js worker-dist/backend-worker.js - - name: Terraform init - working-directory: ./terraform - run: | - terraform init --backend-config "prefix=${{github.event.inputs.environment}}" + - name: Terraform init + working-directory: ./terraform + run: | + terraform init --backend-config "prefix=${{github.event.inputs.environment}}" - - name: Write *.auto.tfvars.json files - working-directory: ./terraform - run: | - echo \ - '{"bot_tag": "${{github.event.inputs.bot_tag}}", "api_path_to_worker": "./worker-dist/backend-worker.js"}' \ - | jq . \ - | tee tags.auto.tfvars.json + - name: Write *.auto.tfvars.json files + working-directory: ./terraform + run: | + echo \ + '{"bot_tag": "${{github.event.inputs.bot_tag}}", "api_path_to_worker": "./worker-dist/backend-worker.js"}' \ + | jq . \ + | tee tags.auto.tfvars.json - echo ${SECRET_TFVARS} > secrets.auto.tfvars.json - env: - SECRET_TFVARS: ${{ steps.secrets.outputs.secretJSON }} + echo ${SECRET_TFVARS} > secrets.auto.tfvars.json + env: + SECRET_TFVARS: ${{ steps.secrets.outputs.secretJSON }} - - name: Terraform plan - working-directory: ./terraform - run: | - terraform plan \ - -var-file variables/global.tfvars \ - -var-file variables/${{github.event.inputs.environment}}.tfvars \ - -out=./deployment.tfplan + - name: Terraform plan + working-directory: ./terraform + run: | + terraform plan \ + -var-file variables/global.tfvars \ + -var-file variables/${{github.event.inputs.environment}}.tfvars \ + -out=./deployment.tfplan - - name: Terraform apply - working-directory: ./terraform - run: | - terraform apply \ - -auto-approve \ - deployment.tfplan + - name: Terraform apply + working-directory: ./terraform + run: | + terraform apply \ + -auto-approve \ + deployment.tfplan - - name: Yell Success at Discord - if: success() - run: | - DATA='{ - "embeds": [ - { - "title": "Roleypoly Deployment Success", - "description": "Roleypoly was successfully deployed at '$(date)'", - "color": 4634182, - "author": { - "name": "Deployment Notification", - "url": "https://github.com/roleypoly/roleypoly/actions/runs/${{ github.run_id }}" - }, - "footer": { - "text": "GitHub Actions" - } - } - ] - }' + - name: Yell Success at Discord + if: success() + run: | + DATA='{ + "embeds": [ + { + "title": "Roleypoly Deployment Success", + "description": "Roleypoly was successfully deployed at '$(date)'", + "color": 4634182, + "author": { + "name": "Deployment Notification", + "url": "https://github.com/roleypoly/roleypoly/actions/runs/${{ github.run_id }}" + }, + "footer": { + "text": "GitHub Actions" + } + } + ] + }' - curl -X POST -H "content-type: application/json" --data "$DATA" ${{ secrets.DEPLOYMENT_WEBHOOK_URL }} + curl -X POST -H "content-type: application/json" --data "$DATA" ${{ secrets.DEPLOYMENT_WEBHOOK_URL }} - - name: Yell Failure at Discord - if: failure() - run: | - DATA='{ - "embeds": [ - { - "title": "Roleypoly Deployment Failed", - "description": "Roleypoly failed to be deployed at '$(date)'", - "color": 15291219, - "author": { - "name": "Deployment Notification", - "url": "https://github.com/roleypoly/roleypoly/actions/runs/${{ github.run_id }}" - }, - "footer": { - "text": "GitHub Actions" - } - } - ] - }' + - name: Yell Failure at Discord + if: failure() + run: | + DATA='{ + "embeds": [ + { + "title": "Roleypoly Deployment Failed", + "description": "Roleypoly failed to be deployed at '$(date)'", + "color": 15291219, + "author": { + "name": "Deployment Notification", + "url": "https://github.com/roleypoly/roleypoly/actions/runs/${{ github.run_id }}" + }, + "footer": { + "text": "GitHub Actions" + } + } + ] + }' - curl -X POST -H "content-type: application/json" --data "$DATA" ${{ secrets.DEPLOYMENT_WEBHOOK_URL }} + curl -X POST -H "content-type: application/json" --data "$DATA" ${{ secrets.DEPLOYMENT_WEBHOOK_URL }} diff --git a/.github/workflows/dev-container.yml b/.github/workflows/dev-container.yml index fc1ccb5..a5b0d88 100644 --- a/.github/workflows/dev-container.yml +++ b/.github/workflows/dev-container.yml @@ -1,53 +1,53 @@ name: Dev Container on: - push: - paths: - - hack/dockerfiles/dev-container.Dockerfile - - .github/workflows/dev-container.yml - schedule: - - cron: '0 12 * * 2' # 12 noon every tuesday + push: + paths: + - hack/dockerfiles/dev-container.Dockerfile + - .github/workflows/dev-container.yml + schedule: + - cron: '0 12 * * 2' # 12 noon every tuesday jobs: - docker_build: - name: Docker Build & Publish - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master + docker_build: + name: Docker Build & Publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master - - uses: actions/cache@v2.1.4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + - uses: actions/cache@v2.1.4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- - - name: Docker meta - id: docker_meta - uses: crazy-max/ghaction-docker-meta@v1 - with: - images: ghcr.io/roleypoly/dev-container - tag-sha: true + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v1 + with: + images: ghcr.io/roleypoly/dev-container + tag-sha: true - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - with: - install: true + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + with: + install: true - - name: Login to GitHub Packages Docker Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: roleypoly - password: ${{ secrets.GHCR_PAT }} + - name: Login to GitHub Packages Docker Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: roleypoly + password: ${{ secrets.GHCR_PAT }} - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: . - file: ./hack/dockerfiles/dev-container.Dockerfile - push: true - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - tags: ${{ steps.docker_meta.outputs.tags }} - labels: ${{ steps.docker_meta.outputs.labels }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./hack/dockerfiles/dev-container.Dockerfile + push: true + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} diff --git a/.prettierrc.js b/.prettierrc.js index d274104..d58c0ce 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,17 +1,9 @@ module.exports = { - printWidth: 90, - useTabs: false, - tabWidth: 4, - singleQuote: true, - trailingComma: 'es5', - bracketSpacing: true, - semi: true, - overrides: [ - { - files: '*.md', - options: { - tabWidth: 2, - }, - }, - ], + printWidth: 90, + useTabs: false, + tabWidth: 2, + singleQuote: true, + trailingComma: 'es5', + bracketSpacing: true, + semi: true, }; diff --git a/.vscode/settings.json b/.vscode/settings.json index 0195b68..fd631d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,15 @@ { - "[starlark]": { - "editor.tabSize": 4 - }, - "bazel.buildifierFixOnFormat": true, - "editor.formatOnSave": true, - "editor.insertSpaces": true, - "editor.tabSize": 2, - "go.inferGopath": false, - "search.exclude": { - "**/.yarn": true, - "**/.pnp.*": true - }, - "typescript.enablePromptUseWorkspaceTsdk": true + "[starlark]": { + "editor.tabSize": 4 + }, + "bazel.buildifierFixOnFormat": true, + "editor.formatOnSave": true, + "editor.insertSpaces": true, + "editor.tabSize": 2, + "go.inferGopath": false, + "search.exclude": { + "**/.yarn": true, + "**/.pnp.*": true + }, + "typescript.enablePromptUseWorkspaceTsdk": true } diff --git a/docker-compose.yaml b/docker-compose.yaml index 2385ad6..e106a68 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,13 +2,13 @@ version: '3.8' services: - dev: - image: node:14 - volumes: - - '.:/src' - ports: - - 6609:6609 - - 6601:6601 - - 6006:6006 - working_dir: /src - command: yarn start + dev: + image: node:14 + volumes: + - '.:/src' + ports: + - 6609:6609 + - 6601:6601 + - 6006:6006 + working_dir: /src + command: yarn start diff --git a/hack/dockerfiles/bot.Dockerfile b/hack/dockerfiles/bot.Dockerfile index e0098a9..7d00610 100644 --- a/hack/dockerfiles/bot.Dockerfile +++ b/hack/dockerfiles/bot.Dockerfile @@ -3,8 +3,8 @@ FROM golang:1.15-alpine AS builder # Create the user and group files that will be used in the running container to # run the process as an unprivileged user. RUN mkdir /user \ - && echo 'nobody:x:65534:65534:nobody:/:' >/user/passwd \ - && echo 'nobody:x:65534:' >/user/group + && echo 'nobody:x:65534:65534:nobody:/:' >/user/passwd \ + && echo 'nobody:x:65534:' >/user/group # Install the Certificate-Authority certificates for the app to be able to make # calls to HTTPS endpoints. @@ -24,8 +24,8 @@ COPY ./ ./ # Build the executable to `/app`. Mark the build as statically linked. RUN CGO_ENABLED=0 go build \ - -installsuffix "static" \ - -o /app ./src/discord-bot + -installsuffix "static" \ + -o /app ./src/discord-bot # Final stage: the running container. FROM scratch AS final diff --git a/jest.config.js b/jest.config.js index d0732b9..6c145e7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,12 +1,12 @@ module.exports = { - preset: 'ts-jest/presets/js-with-babel', - testEnvironment: 'enzyme', - reporters: ['default'], - setupFilesAfterEnv: ['jest-enzyme', 'jest-styled-components', './hack/jestSetup.ts'], - snapshotSerializers: ['enzyme-to-json/serializer'], - globals: { - 'ts-jest': { - tsconfig: './tsconfig.test.json', - }, + preset: 'ts-jest/presets/js-with-babel', + testEnvironment: 'enzyme', + reporters: ['default'], + setupFilesAfterEnv: ['jest-enzyme', 'jest-styled-components', './hack/jestSetup.ts'], + snapshotSerializers: ['enzyme-to-json/serializer'], + globals: { + 'ts-jest': { + tsconfig: './tsconfig.test.json', }, + }, }; diff --git a/package.json b/package.json index 6dd9cf7..825d5d8 100644 --- a/package.json +++ b/package.json @@ -1,80 +1,80 @@ { - "name": "roleypoly", - "version": "1.0.0", - "description": "https://roleypoly.com", - "repository": { - "type": "git", - "url": "git+https://github.com/roleypoly/roleypoly.git" - }, - "homepage": "https://github.com/roleypoly/roleypoly#readme", - "bugs": { - "url": "https://github.com/roleypoly/roleypoly/issues" - }, - "author": "Katalina Okano ", - "license": "MIT", - "private": true, - "workspaces": [ - "packages/*" + "name": "roleypoly", + "version": "1.0.0", + "description": "https://roleypoly.com", + "repository": { + "type": "git", + "url": "git+https://github.com/roleypoly/roleypoly.git" + }, + "homepage": "https://github.com/roleypoly/roleypoly#readme", + "bugs": { + "url": "https://github.com/roleypoly/roleypoly/issues" + }, + "author": "Katalina Okano ", + "license": "MIT", + "private": true, + "workspaces": [ + "packages/*" + ], + "scripts": { + "build": "run-p -c build:*", + "build:design-system": "yarn workspace @roleypoly/design-system run build", + "build:web": "yarn workspace @roleypoly/web run build", + "lint": "run-p -c lint:* --", + "lint:eslint": "eslint", + "lint:go": "go fmt ./...", + "lint:prettier": "cross-env prettier -c '**/*.{ts,tsx,css,yml,yaml,md,json,js,jsx,sh,gitignore,mdx,Dockerfile}'", + "lint:stylelint": "cross-env stylelint '**/*.{ts,tsx}'", + "lint:terraform": "terraform fmt -recursive -check ./terraform", + "lint:types": "tsc --noEmit", + "lint:types-api": "yarn workspace @roleypoly/api run lint:types", + "postinstall": "is-ci || husky install", + "start": "run-p -c start:*", + "start:design-system": "yarn workspace @roleypoly/design-system start", + "start:web": "yarn workspace @roleypoly/web start", + "start:worker": "yarn workspace @roleypoly/api start", + "test": "jest" + }, + "devDependencies": { + "@types/enzyme": "^3.10.8", + "@types/lodash": "^4.14.168", + "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.6", + "husky": "^5.1.3", + "is-ci": "^3.0.0", + "jest-enzyme": "^7.1.2", + "jest-react-hooks-shallow": "^1.5.1", + "jest-styled-components": "^7.0.3", + "lint-staged": "^10.5.4", + "npm-run-all": "^4.1.5", + "prettier": "^2.2.1", + "prettier-plugin-organize-imports": "^1.1.1", + "prettier-plugin-pkg": "^0.8.0", + "prettier-plugin-sh": "^0.6.0", + "stylelint": "^13.12.0", + "stylelint-config-prettier": "^8.0.2", + "stylelint-config-standard": "^21.0.0", + "stylelint-config-styled-components": "^0.1.1", + "stylelint-prettier": "^1.2.0", + "ts-jest": "^26.5.3", + "typescript": "^4.2.3" + }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "prettier --write" ], - "scripts": { - "build": "run-p -c build:*", - "build:design-system": "yarn workspace @roleypoly/design-system run build", - "build:web": "yarn workspace @roleypoly/web run build", - "lint": "run-p -c lint:* --", - "lint:eslint": "eslint", - "lint:go": "go fmt ./...", - "lint:prettier": "cross-env prettier -c '**/*.{ts,tsx,css,yml,yaml,md,json,js,jsx,sh,gitignore,mdx,Dockerfile}'", - "lint:stylelint": "cross-env stylelint '**/*.{ts,tsx}'", - "lint:terraform": "terraform fmt -recursive -check ./terraform", - "lint:types": "tsc --noEmit", - "lint:types-api": "yarn workspace @roleypoly/api run lint:types", - "postinstall": "is-ci || husky install", - "start": "run-p -c start:*", - "start:design-system": "yarn workspace @roleypoly/design-system start", - "start:web": "yarn workspace @roleypoly/web start", - "start:worker": "yarn workspace @roleypoly/api start", - "test": "jest" - }, - "devDependencies": { - "@types/enzyme": "^3.10.8", - "@types/lodash": "^4.14.168", - "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1", - "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", - "husky": "^5.1.3", - "is-ci": "^3.0.0", - "jest-enzyme": "^7.1.2", - "jest-react-hooks-shallow": "^1.5.1", - "jest-styled-components": "^7.0.3", - "lint-staged": "^10.5.4", - "npm-run-all": "^4.1.5", - "prettier": "^2.2.1", - "prettier-plugin-organize-imports": "^1.1.1", - "prettier-plugin-pkg": "^0.8.0", - "prettier-plugin-sh": "^0.6.0", - "stylelint": "^13.12.0", - "stylelint-config-prettier": "^8.0.2", - "stylelint-config-standard": "^21.0.0", - "stylelint-config-styled-components": "^0.1.1", - "stylelint-prettier": "^1.2.0", - "ts-jest": "^26.5.3", - "typescript": "^4.2.3" - }, - "lint-staged": { - "*.{ts,tsx,js,jsx}": [ - "prettier --write" - ], - "*.go": [ - "go fmt" - ], - "*.{json,Dockerfile,sh,md,env,mdx,yml,html}": [ - "prettier --write" - ], - ".*/*.{json,Dockerfile,sh,md,env,mdx,yml,html}": [ - "prettier --write" - ], - ".husky/pre-commit": [ - "prettier --write" - ] - } + "*.go": [ + "go fmt" + ], + "*.{json,Dockerfile,sh,md,env,mdx,yml,html}": [ + "prettier --write" + ], + ".*/*.{json,Dockerfile,sh,md,env,mdx,yml,html}": [ + "prettier --write" + ], + ".husky/pre-commit": [ + "prettier --write" + ] + } } diff --git a/packages/api/bindings.d.ts b/packages/api/bindings.d.ts index 281cb27..32180fa 100644 --- a/packages/api/bindings.d.ts +++ b/packages/api/bindings.d.ts @@ -1,14 +1,14 @@ export {}; declare global { - const BOT_CLIENT_ID: string; - const BOT_CLIENT_SECRET: string; - const UI_PUBLIC_URI: string; - const API_PUBLIC_URI: string; - const ROOT_USERS: string; - const ALLOWED_CALLBACK_HOSTS: string; + const BOT_CLIENT_ID: string; + const BOT_CLIENT_SECRET: string; + const UI_PUBLIC_URI: string; + const API_PUBLIC_URI: string; + const ROOT_USERS: string; + const ALLOWED_CALLBACK_HOSTS: string; - const KV_SESSIONS: KVNamespace; - const KV_GUILDS: KVNamespace; - const KV_GUILD_DATA: KVNamespace; + const KV_SESSIONS: KVNamespace; + const KV_GUILDS: KVNamespace; + const KV_GUILD_DATA: KVNamespace; } diff --git a/packages/api/handlers/bot-join.ts b/packages/api/handlers/bot-join.ts index c980ea4..3a6cc9e 100644 --- a/packages/api/handlers/bot-join.ts +++ b/packages/api/handlers/bot-join.ts @@ -4,33 +4,33 @@ import { botClientID } from '../utils/config'; const validGuildID = /^[0-9]+$/; type URLParams = { - clientID: string; - permissions: number; - guildID?: string; + clientID: string; + permissions: number; + guildID?: string; }; const buildURL = (params: URLParams) => { - let url = `https://discord.com/api/oauth2/authorize?client_id=${params.clientID}&scope=bot&permissions=${params.permissions}`; + let url = `https://discord.com/api/oauth2/authorize?client_id=${params.clientID}&scope=bot&permissions=${params.permissions}`; - if (params.guildID) { - url += `&guild_id=${params.guildID}&disable_guild_select=true`; - } + if (params.guildID) { + url += `&guild_id=${params.guildID}&disable_guild_select=true`; + } - return url; + return url; }; export const BotJoin = (request: Request): Response => { - let guildID = new URL(request.url).searchParams.get('guild') || ''; + let guildID = new URL(request.url).searchParams.get('guild') || ''; - if (guildID && !validGuildID.test(guildID)) { - guildID = ''; - } + if (guildID && !validGuildID.test(guildID)) { + guildID = ''; + } - return Bounce( - buildURL({ - clientID: botClientID, - permissions: 268435456, - guildID, - }) - ); + return Bounce( + buildURL({ + clientID: botClientID, + permissions: 268435456, + guildID, + }) + ); }; diff --git a/packages/api/handlers/create-roleypoly-data.ts b/packages/api/handlers/create-roleypoly-data.ts index c119daa..f790f85 100644 --- a/packages/api/handlers/create-roleypoly-data.ts +++ b/packages/api/handlers/create-roleypoly-data.ts @@ -5,90 +5,78 @@ import { GuildData } from '../utils/kv'; // Temporary use. export const CreateRoleypolyData = onlyRootUsers( - async (request: Request): Promise => { - const data: GuildDataT = { - id: '386659935687147521', - message: - 'Hey, this is kind of a demo setup so features/use cases can be shown off.\n\nThanks for using Roleypoly <3', - features: Features.Preview, - categories: [ - { - id: KSUID.randomSync().string, - name: 'Demo Roles', - type: CategoryType.Multi, - hidden: false, - position: 0, - roles: [ - '557825026406088717', - '557824994269200384', - '557824893241131029', - '557812915386843170', - '557812901717737472', - '557812805546541066', - ], - }, - { - id: KSUID.randomSync().string, - name: 'Colors', - type: CategoryType.Single, - hidden: false, - position: 1, - roles: [ - '394060232893923349', - '394060145799331851', - '394060192846839809', - ], - }, - { - id: KSUID.randomSync().string, - name: 'Test Roles', - type: CategoryType.Multi, - hidden: false, - position: 5, - roles: [ - '558104828216213505', - '558103534453653514', - '558297233582194728', - ], - }, - { - id: KSUID.randomSync().string, - name: 'Region', - type: CategoryType.Multi, - hidden: false, - position: 3, - roles: [ - '397296181803483136', - '397296137066774529', - '397296218809827329', - '397296267283267605', - ], - }, - { - id: KSUID.randomSync().string, - name: 'Opt-in Channels', - type: CategoryType.Multi, - hidden: false, - position: 4, - roles: ['414514823959674890', '764230661904007219'], - }, - { - id: KSUID.randomSync().string, - name: 'Pronouns', - type: CategoryType.Multi, - hidden: false, - position: 2, - roles: [ - '485916566790340608', - '485916566941335583', - '485916566311927808', - ], - }, - ], - }; + async (request: Request): Promise => { + const data: GuildDataT = { + id: '386659935687147521', + message: + 'Hey, this is kind of a demo setup so features/use cases can be shown off.\n\nThanks for using Roleypoly <3', + features: Features.Preview, + categories: [ + { + id: KSUID.randomSync().string, + name: 'Demo Roles', + type: CategoryType.Multi, + hidden: false, + position: 0, + roles: [ + '557825026406088717', + '557824994269200384', + '557824893241131029', + '557812915386843170', + '557812901717737472', + '557812805546541066', + ], + }, + { + id: KSUID.randomSync().string, + name: 'Colors', + type: CategoryType.Single, + hidden: false, + position: 1, + roles: ['394060232893923349', '394060145799331851', '394060192846839809'], + }, + { + id: KSUID.randomSync().string, + name: 'Test Roles', + type: CategoryType.Multi, + hidden: false, + position: 5, + roles: ['558104828216213505', '558103534453653514', '558297233582194728'], + }, + { + id: KSUID.randomSync().string, + name: 'Region', + type: CategoryType.Multi, + hidden: false, + position: 3, + roles: [ + '397296181803483136', + '397296137066774529', + '397296218809827329', + '397296267283267605', + ], + }, + { + id: KSUID.randomSync().string, + name: 'Opt-in Channels', + type: CategoryType.Multi, + hidden: false, + position: 4, + roles: ['414514823959674890', '764230661904007219'], + }, + { + id: KSUID.randomSync().string, + name: 'Pronouns', + type: CategoryType.Multi, + hidden: false, + position: 2, + roles: ['485916566790340608', '485916566941335583', '485916566311927808'], + }, + ], + }; - await GuildData.put(data.id, data); + await GuildData.put(data.id, data); - return respond({ ok: true }); - } + return respond({ ok: true }); + } ); diff --git a/packages/api/handlers/get-picker-data.ts b/packages/api/handlers/get-picker-data.ts index f5317a1..e8fcf2f 100644 --- a/packages/api/handlers/get-picker-data.ts +++ b/packages/api/handlers/get-picker-data.ts @@ -5,52 +5,52 @@ import { getGuild, getGuildData, getGuildMemberRoles } from '../utils/guild'; const fail = () => respond({ error: 'guild not found' }, { status: 404 }); export const GetPickerData = withSession( - (session: SessionData) => async (request: Request): Promise => { - const url = new URL(request.url); - const [, , guildID] = url.pathname.split('/'); + (session: SessionData) => async (request: Request): Promise => { + const url = new URL(request.url); + const [, , guildID] = url.pathname.split('/'); - if (!guildID) { - return respond({ error: 'missing guild id' }, { status: 400 }); - } - - const { id: userID } = session.user as DiscordUser; - const guilds = session.guilds as GuildSlug[]; - - // Save a Discord API request by checking if this user is a member by session first - const checkGuild = guilds.find((guild) => guild.id === guildID); - if (!checkGuild) { - return fail(); - } - - const guild = await getGuild(guildID, { - skipCachePull: url.searchParams.has('__no_cache'), - }); - if (!guild) { - return fail(); - } - - const memberRolesP = getGuildMemberRoles({ - serverID: guildID, - userID, - }); - - const guildDataP = getGuildData(guildID); - - const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]); - if (!memberRoles) { - return fail(); - } - - const presentableGuild: PresentableGuild = { - id: guildID, - guild: checkGuild, - roles: guild.roles, - member: { - roles: memberRoles, - }, - data: guildData, - }; - - return respond(presentableGuild); + if (!guildID) { + return respond({ error: 'missing guild id' }, { status: 400 }); } + + const { id: userID } = session.user as DiscordUser; + const guilds = session.guilds as GuildSlug[]; + + // Save a Discord API request by checking if this user is a member by session first + const checkGuild = guilds.find((guild) => guild.id === guildID); + if (!checkGuild) { + return fail(); + } + + const guild = await getGuild(guildID, { + skipCachePull: url.searchParams.has('__no_cache'), + }); + if (!guild) { + return fail(); + } + + const memberRolesP = getGuildMemberRoles({ + serverID: guildID, + userID, + }); + + const guildDataP = getGuildData(guildID); + + const [guildData, memberRoles] = await Promise.all([guildDataP, memberRolesP]); + if (!memberRoles) { + return fail(); + } + + const presentableGuild: PresentableGuild = { + id: guildID, + guild: checkGuild, + roles: guild.roles, + member: { + roles: memberRoles, + }, + data: guildData, + }; + + return respond(presentableGuild); + } ); diff --git a/packages/api/handlers/get-session.ts b/packages/api/handlers/get-session.ts index e597070..9b44b19 100644 --- a/packages/api/handlers/get-session.ts +++ b/packages/api/handlers/get-session.ts @@ -2,11 +2,11 @@ import { SessionData } from '@roleypoly/types'; import { respond, withSession } from '../utils/api-tools'; export const GetSession = withSession((session?: SessionData) => (): Response => { - const { user, guilds, sessionID } = session || {}; + const { user, guilds, sessionID } = session || {}; - return respond({ - user, - guilds, - sessionID, - }); + return respond({ + user, + guilds, + sessionID, + }); }); diff --git a/packages/api/handlers/get-slug.ts b/packages/api/handlers/get-slug.ts index 9a64407..f8b505b 100644 --- a/packages/api/handlers/get-slug.ts +++ b/packages/api/handlers/get-slug.ts @@ -3,38 +3,38 @@ import { respond } from '../utils/api-tools'; import { getGuild } from '../utils/guild'; export const GetSlug = async (request: Request): Promise => { - const reqURL = new URL(request.url); - const [, , serverID] = reqURL.pathname.split('/'); + const reqURL = new URL(request.url); + const [, , serverID] = reqURL.pathname.split('/'); - if (!serverID) { - return respond( - { - error: 'missing server ID', - }, - { - status: 400, - } - ); - } + if (!serverID) { + return respond( + { + error: 'missing server ID', + }, + { + status: 400, + } + ); + } - const guild = await getGuild(serverID); - if (!guild) { - return respond( - { - error: 'guild not found', - }, - { - status: 404, - } - ); - } + const guild = await getGuild(serverID); + if (!guild) { + return respond( + { + error: 'guild not found', + }, + { + status: 404, + } + ); + } - const { id, name, icon } = guild; - const guildSlug: GuildSlug = { - id, - name, - icon, - permissionLevel: 0, - }; - return respond(guildSlug); + const { id, name, icon } = guild; + const guildSlug: GuildSlug = { + id, + name, + icon, + permissionLevel: 0, + }; + return respond(guildSlug); }; diff --git a/packages/api/handlers/login-bounce.ts b/packages/api/handlers/login-bounce.ts index 3a51656..c889585 100644 --- a/packages/api/handlers/login-bounce.ts +++ b/packages/api/handlers/login-bounce.ts @@ -4,30 +4,30 @@ import { Bounce } from '../utils/bounce'; import { apiPublicURI, botClientID } from '../utils/config'; type URLParams = { - clientID: string; - redirectURI: string; - state: string; + clientID: string; + redirectURI: string; + state: string; }; const buildURL = (params: URLParams) => - `https://discord.com/api/oauth2/authorize?client_id=${ - params.clientID - }&response_type=code&scope=identify%20guilds&redirect_uri=${encodeURIComponent( - params.redirectURI - )}&state=${params.state}`; + `https://discord.com/api/oauth2/authorize?client_id=${ + params.clientID + }&response_type=code&scope=identify%20guilds&redirect_uri=${encodeURIComponent( + params.redirectURI + )}&state=${params.state}`; export const LoginBounce = async (request: Request): Promise => { - const stateSessionData: StateSession = {}; + const stateSessionData: StateSession = {}; - const { cbh: callbackHost } = getQuery(request); - if (callbackHost && isAllowedCallbackHost(callbackHost)) { - stateSessionData.callbackHost = callbackHost; - } + const { cbh: callbackHost } = getQuery(request); + if (callbackHost && isAllowedCallbackHost(callbackHost)) { + stateSessionData.callbackHost = callbackHost; + } - const state = await setupStateSession(stateSessionData); + const state = await setupStateSession(stateSessionData); - const redirectURI = `${apiPublicURI}/login-callback`; - const clientID = botClientID; + const redirectURI = `${apiPublicURI}/login-callback`; + const clientID = botClientID; - return Bounce(buildURL({ state, redirectURI, clientID })); + return Bounce(buildURL({ state, redirectURI, clientID })); }; diff --git a/packages/api/handlers/login-callback.ts b/packages/api/handlers/login-callback.ts index 71a3d34..eff593b 100644 --- a/packages/api/handlers/login-callback.ts +++ b/packages/api/handlers/login-callback.ts @@ -1,159 +1,159 @@ import { - AuthTokenResponse, - DiscordUser, - GuildSlug, - SessionData, - StateSession, + AuthTokenResponse, + DiscordUser, + GuildSlug, + SessionData, + StateSession, } from '@roleypoly/types'; import KSUID from 'ksuid'; import { - AuthType, - discordFetch, - formData, - getStateSession, - isAllowedCallbackHost, - parsePermissions, - resolveFailures, - userAgent, + AuthType, + discordFetch, + formData, + getStateSession, + isAllowedCallbackHost, + parsePermissions, + resolveFailures, + userAgent, } from '../utils/api-tools'; import { Bounce } from '../utils/bounce'; import { apiPublicURI, botClientID, botClientSecret, uiPublicURI } from '../utils/config'; import { Sessions } from '../utils/kv'; const AuthErrorResponse = (extra?: string) => - Bounce( - uiPublicURI + - `/machinery/error?error_code=authFailure${extra ? `&extra=${extra}` : ''}` - ); + Bounce( + uiPublicURI + + `/machinery/error?error_code=authFailure${extra ? `&extra=${extra}` : ''}` + ); export const LoginCallback = resolveFailures( - AuthErrorResponse, - async (request: Request): Promise => { - let bounceBaseUrl = uiPublicURI; + AuthErrorResponse, + async (request: Request): Promise => { + let bounceBaseUrl = uiPublicURI; - const query = new URL(request.url).searchParams; - const stateValue = query.get('state'); + const query = new URL(request.url).searchParams; + const stateValue = query.get('state'); - if (stateValue === null) { - return AuthErrorResponse('state missing'); - } - - try { - const state = KSUID.parse(stateValue); - const stateExpiry = state.date.getTime() + 1000 * 60 * 5; - const currentTime = Date.now(); - - if (currentTime > stateExpiry) { - return AuthErrorResponse('state expired'); - } - - const stateSession = await getStateSession(state.string); - if ( - stateSession?.callbackHost && - isAllowedCallbackHost(stateSession.callbackHost) - ) { - bounceBaseUrl = stateSession.callbackHost; - } - } catch (e) { - return AuthErrorResponse('state invalid'); - } - - const code = query.get('code'); - if (!code) { - return AuthErrorResponse('code missing'); - } - - const tokenRequest = { - client_id: botClientID, - client_secret: botClientSecret, - grant_type: 'authorization_code', - redirect_uri: apiPublicURI + '/login-callback', - scope: 'identify guilds', - code, - }; - - const tokenFetch = await fetch('https://discord.com/api/v8/oauth2/token', { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'user-agent': userAgent, - }, - body: formData(tokenRequest), - }); - - const tokens = (await tokenFetch.json()) as AuthTokenResponse; - - if (!tokens.access_token) { - return AuthErrorResponse('token response invalid'); - } - - const [sessionID, user, guilds] = await Promise.all([ - KSUID.random(), - getUser(tokens.access_token), - getGuilds(tokens.access_token), - ]); - - if (!user) { - return AuthErrorResponse('failed to fetch user'); - } - - const sessionData: SessionData = { - tokens, - sessionID: sessionID.string, - user, - guilds, - }; - - await Sessions.put(sessionID.string, sessionData, 60 * 60 * 6); - - return Bounce( - bounceBaseUrl + '/machinery/new-session?session_id=' + sessionID.string - ); + if (stateValue === null) { + return AuthErrorResponse('state missing'); } + + try { + const state = KSUID.parse(stateValue); + const stateExpiry = state.date.getTime() + 1000 * 60 * 5; + const currentTime = Date.now(); + + if (currentTime > stateExpiry) { + return AuthErrorResponse('state expired'); + } + + const stateSession = await getStateSession(state.string); + if ( + stateSession?.callbackHost && + isAllowedCallbackHost(stateSession.callbackHost) + ) { + bounceBaseUrl = stateSession.callbackHost; + } + } catch (e) { + return AuthErrorResponse('state invalid'); + } + + const code = query.get('code'); + if (!code) { + return AuthErrorResponse('code missing'); + } + + const tokenRequest = { + client_id: botClientID, + client_secret: botClientSecret, + grant_type: 'authorization_code', + redirect_uri: apiPublicURI + '/login-callback', + scope: 'identify guilds', + code, + }; + + const tokenFetch = await fetch('https://discord.com/api/v8/oauth2/token', { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'user-agent': userAgent, + }, + body: formData(tokenRequest), + }); + + const tokens = (await tokenFetch.json()) as AuthTokenResponse; + + if (!tokens.access_token) { + return AuthErrorResponse('token response invalid'); + } + + const [sessionID, user, guilds] = await Promise.all([ + KSUID.random(), + getUser(tokens.access_token), + getGuilds(tokens.access_token), + ]); + + if (!user) { + return AuthErrorResponse('failed to fetch user'); + } + + const sessionData: SessionData = { + tokens, + sessionID: sessionID.string, + user, + guilds, + }; + + await Sessions.put(sessionID.string, sessionData, 60 * 60 * 6); + + return Bounce( + bounceBaseUrl + '/machinery/new-session?session_id=' + sessionID.string + ); + } ); const getUser = async (accessToken: string): Promise => { - const user = await discordFetch( - '/users/@me', - accessToken, - AuthType.Bearer - ); + const user = await discordFetch( + '/users/@me', + accessToken, + AuthType.Bearer + ); - if (!user) { - return null; - } + if (!user) { + return null; + } - const { id, username, discriminator, bot, avatar } = user; + const { id, username, discriminator, bot, avatar } = user; - return { id, username, discriminator, bot, avatar }; + return { id, username, discriminator, bot, avatar }; }; type UserGuildsPayload = { - id: string; - name: string; - icon: string; - owner: boolean; - permissions: number; - features: string[]; + id: string; + name: string; + icon: string; + owner: boolean; + permissions: number; + features: string[]; }[]; const getGuilds = async (accessToken: string) => { - const guilds = await discordFetch( - '/users/@me/guilds', - accessToken, - AuthType.Bearer - ); + const guilds = await discordFetch( + '/users/@me/guilds', + accessToken, + AuthType.Bearer + ); - if (!guilds) { - return []; - } + if (!guilds) { + return []; + } - const guildSlugs = guilds.map((guild) => ({ - id: guild.id, - name: guild.name, - icon: guild.icon, - permissionLevel: parsePermissions(BigInt(guild.permissions), guild.owner), - })); + const guildSlugs = guilds.map((guild) => ({ + id: guild.id, + name: guild.name, + icon: guild.icon, + permissionLevel: parsePermissions(BigInt(guild.permissions), guild.owner), + })); - return guildSlugs; + return guildSlugs; }; diff --git a/packages/api/handlers/revoke-session.ts b/packages/api/handlers/revoke-session.ts index e9e4cbb..c6c0552 100644 --- a/packages/api/handlers/revoke-session.ts +++ b/packages/api/handlers/revoke-session.ts @@ -4,24 +4,24 @@ import { botClientID, botClientSecret } from '../utils/config'; import { Sessions } from '../utils/kv'; export const RevokeSession = withSession( - (session: SessionData) => async (request: Request) => { - const tokenRequest = { - token: session.tokens.access_token, - client_id: botClientID, - client_secret: botClientSecret, - }; + (session: SessionData) => async (request: Request) => { + const tokenRequest = { + token: session.tokens.access_token, + client_id: botClientID, + client_secret: botClientSecret, + }; - await fetch('https://discord.com/api/v8/oauth2/token/revoke', { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded', - 'user-agent': userAgent, - }, - body: formData(tokenRequest), - }); + await fetch('https://discord.com/api/v8/oauth2/token/revoke', { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'user-agent': userAgent, + }, + body: formData(tokenRequest), + }); - await Sessions.delete(session.sessionID); + await Sessions.delete(session.sessionID); - return respond({ ok: true }); - } + return respond({ ok: true }); + } ); diff --git a/packages/api/handlers/update-roles.ts b/packages/api/handlers/update-roles.ts index 1ef0f45..c85b0eb 100644 --- a/packages/api/handlers/update-roles.ts +++ b/packages/api/handlers/update-roles.ts @@ -1,141 +1,135 @@ import { - GuildData, - Member, - Role, - RoleSafety, - RoleTransaction, - RoleUpdate, - SessionData, - TransactionType, + GuildData, + Member, + Role, + RoleSafety, + RoleTransaction, + RoleUpdate, + SessionData, + TransactionType, } from '@roleypoly/types'; import { difference, groupBy, keyBy, union } from 'lodash'; import { AuthType, discordFetch, respond, withSession } from '../utils/api-tools'; import { botToken } from '../utils/config'; import { - getGuild, - getGuildData, - getGuildMemberRoles, - updateGuildMemberRoles, + getGuild, + getGuildData, + getGuildMemberRoles, + updateGuildMemberRoles, } from '../utils/guild'; const notFound = () => respond({ error: 'guild not found' }, { status: 404 }); export const UpdateRoles = withSession( - ({ guilds, user: { id: userID } }: SessionData) => async (request: Request) => { - const updateRequest = (await request.json()) as RoleUpdate; - const [, , guildID] = new URL(request.url).pathname.split('/'); + ({ guilds, user: { id: userID } }: SessionData) => async (request: Request) => { + const updateRequest = (await request.json()) as RoleUpdate; + const [, , guildID] = new URL(request.url).pathname.split('/'); - if (!guildID) { - return respond({ error: 'guild ID missing from URL' }, { status: 400 }); - } - - if (updateRequest.transactions.length === 0) { - return respond( - { error: 'must have as least one transaction' }, - { status: 400 } - ); - } - - const guildCheck = guilds.find((guild) => guild.id === guildID); - if (!guildCheck) { - return notFound(); - } - - const guild = await getGuild(guildID); - if (!guild) { - return notFound(); - } - - const guildMemberRoles = await getGuildMemberRoles( - { serverID: guildID, userID }, - { skipCachePull: true } - ); - if (!guildMemberRoles) { - return notFound(); - } - - const newRoles = calculateNewRoles({ - currentRoles: guildMemberRoles, - guildRoles: guild.roles, - guildData: await getGuildData(guildID), - updateRequest, - }); - - const patchMemberRoles = await discordFetch( - `/guilds/${guildID}/members/${userID}`, - botToken, - AuthType.Bot, - { - method: 'PATCH', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - roles: newRoles, - }), - } - ); - - if (!patchMemberRoles) { - return respond({ error: 'discord rejected the request' }, { status: 500 }); - } - - const updatedMember: Member = { - roles: patchMemberRoles.roles, - }; - - await updateGuildMemberRoles( - { serverID: guildID, userID }, - patchMemberRoles.roles - ); - - return respond(updatedMember); + if (!guildID) { + return respond({ error: 'guild ID missing from URL' }, { status: 400 }); } + + if (updateRequest.transactions.length === 0) { + return respond({ error: 'must have as least one transaction' }, { status: 400 }); + } + + const guildCheck = guilds.find((guild) => guild.id === guildID); + if (!guildCheck) { + return notFound(); + } + + const guild = await getGuild(guildID); + if (!guild) { + return notFound(); + } + + const guildMemberRoles = await getGuildMemberRoles( + { serverID: guildID, userID }, + { skipCachePull: true } + ); + if (!guildMemberRoles) { + return notFound(); + } + + const newRoles = calculateNewRoles({ + currentRoles: guildMemberRoles, + guildRoles: guild.roles, + guildData: await getGuildData(guildID), + updateRequest, + }); + + const patchMemberRoles = await discordFetch( + `/guilds/${guildID}/members/${userID}`, + botToken, + AuthType.Bot, + { + method: 'PATCH', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + roles: newRoles, + }), + } + ); + + if (!patchMemberRoles) { + return respond({ error: 'discord rejected the request' }, { status: 500 }); + } + + const updatedMember: Member = { + roles: patchMemberRoles.roles, + }; + + await updateGuildMemberRoles({ serverID: guildID, userID }, patchMemberRoles.roles); + + return respond(updatedMember); + } ); const calculateNewRoles = ({ - currentRoles, - guildData, - guildRoles, - updateRequest, + currentRoles, + guildData, + guildRoles, + updateRequest, }: { - currentRoles: string[]; - guildRoles: Role[]; - guildData: GuildData; - updateRequest: RoleUpdate; + currentRoles: string[]; + guildRoles: Role[]; + guildData: GuildData; + updateRequest: RoleUpdate; }): string[] => { - const roleMap = keyBy(guildRoles, 'id'); + const roleMap = keyBy(guildRoles, 'id'); - // These roles were ones changed between knownState (role picker page load/cache) and current (fresh from discord). - // We could cause issues, so we'll re-add them later. - // const diffRoles = difference(updateRequest.knownState, currentRoles); + // These roles were ones changed between knownState (role picker page load/cache) and current (fresh from discord). + // We could cause issues, so we'll re-add them later. + // const diffRoles = difference(updateRequest.knownState, currentRoles); - // Only these are safe - const allSafeRoles = guildData.categories.reduce( - (categorizedRoles, category) => - !category.hidden - ? [ - ...categorizedRoles, - ...category.roles.filter( - (roleID) => roleMap[roleID]?.safety === RoleSafety.Safe - ), - ] - : categorizedRoles, - [] - ); + // Only these are safe + const allSafeRoles = guildData.categories.reduce( + (categorizedRoles, category) => + !category.hidden + ? [ + ...categorizedRoles, + ...category.roles.filter( + (roleID) => roleMap[roleID]?.safety === RoleSafety.Safe + ), + ] + : categorizedRoles, + [] + ); - const safeTransactions = updateRequest.transactions.filter((tx: RoleTransaction) => - allSafeRoles.includes(tx.id) - ); + const safeTransactions = updateRequest.transactions.filter((tx: RoleTransaction) => + allSafeRoles.includes(tx.id) + ); - const changesByAction = groupBy(safeTransactions, 'action'); + const changesByAction = groupBy(safeTransactions, 'action'); - const rolesToAdd = (changesByAction[TransactionType.Add] ?? []).map((tx) => tx.id); - const rolesToRemove = (changesByAction[TransactionType.Remove] ?? []).map( - (tx) => tx.id - ); + const rolesToAdd = (changesByAction[TransactionType.Add] ?? []).map((tx) => tx.id); + const rolesToRemove = (changesByAction[TransactionType.Remove] ?? []).map( + (tx) => tx.id + ); - const final = union(difference(currentRoles, rolesToRemove), rolesToAdd); + const final = union(difference(currentRoles, rolesToRemove), rolesToAdd); - return final; + return final; }; diff --git a/packages/api/index.ts b/packages/api/index.ts index 6db467b..bb809ea 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -32,30 +32,30 @@ router.add('GET', 'x-create-roleypoly-data', CreateRoleypolyData); // Tester Routes router.add('GET', 'x-headers', (request) => { - const headers: { [x: string]: string } = {}; + const headers: { [x: string]: string } = {}; - for (let [key, value] of request.headers.entries()) { - headers[key] = value; - } + for (let [key, value] of request.headers.entries()) { + headers[key] = value; + } - return new Response(JSON.stringify(headers)); + return new Response(JSON.stringify(headers)); }); // Root Zen <3 router.addFallback('root', () => { - return respond({ - __warning: '🦊', - this: 'is', - a: 'fox-based', - web: 'application', - please: 'be', - mindful: 'of', - your: 'surroundings', - warning__: '🦊', - meta: uiPublicURI, - }); + return respond({ + __warning: '🦊', + this: 'is', + a: 'fox-based', + web: 'application', + please: 'be', + mindful: 'of', + your: 'surroundings', + warning__: '🦊', + meta: uiPublicURI, + }); }); addEventListener('fetch', (event: FetchEvent) => { - event.respondWith(router.handle(event.request)); + event.respondWith(router.handle(event.request)); }); diff --git a/packages/api/package.json b/packages/api/package.json index 7523484..a944c3c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,18 +1,18 @@ { - "name": "@roleypoly/api", - "version": "0.1.0", - "scripts": { - "build": "yarn workspace @roleypoly/worker-emulator build --basePath `pwd`", - "lint:types": "tsc --noEmit", - "start": "yarn workspace @roleypoly/worker-emulator start --basePath `pwd`" - }, - "devDependencies": { - "@cloudflare/workers-types": "^2.1.0", - "@roleypoly/misc-utils": "*", - "@roleypoly/types": "*", - "@roleypoly/worker-emulator": "*", - "ksuid": "^2.0.0", - "lodash": "^4.17.21", - "ts-loader": "^8.0.18" - } + "name": "@roleypoly/api", + "version": "0.1.0", + "scripts": { + "build": "yarn workspace @roleypoly/worker-emulator build --basePath `pwd`", + "lint:types": "tsc --noEmit", + "start": "yarn workspace @roleypoly/worker-emulator start --basePath `pwd`" + }, + "devDependencies": { + "@cloudflare/workers-types": "^2.1.0", + "@roleypoly/misc-utils": "*", + "@roleypoly/types": "*", + "@roleypoly/worker-emulator": "*", + "ksuid": "^2.0.0", + "lodash": "^4.17.21", + "ts-loader": "^8.0.18" + } } diff --git a/packages/api/router.ts b/packages/api/router.ts index 363d434..dbd1313 100644 --- a/packages/api/router.ts +++ b/packages/api/router.ts @@ -4,81 +4,81 @@ import { uiPublicURI } from './utils/config'; export type Handler = (request: Request) => Promise | Response; type RoutingTree = { - [method: string]: { - [path: string]: Handler; - }; + [method: string]: { + [path: string]: Handler; + }; }; type Fallbacks = { - root: Handler; - 404: Handler; - 500: Handler; + root: Handler; + 404: Handler; + 500: Handler; }; export class Router { - private routingTree: RoutingTree = {}; - private fallbacks: Fallbacks = { - root: this.respondToRoot, - 404: this.notFound, - 500: this.serverError, - }; + private routingTree: RoutingTree = {}; + private fallbacks: Fallbacks = { + root: this.respondToRoot, + 404: this.notFound, + 500: this.serverError, + }; - private uiURL = new URL(uiPublicURI); + private uiURL = new URL(uiPublicURI); - addFallback(which: keyof Fallbacks, handler: Handler) { - this.fallbacks[which] = handler; + addFallback(which: keyof Fallbacks, handler: Handler) { + this.fallbacks[which] = handler; + } + + add(method: string, rootPath: string, handler: Handler) { + const lowerMethod = method.toLowerCase(); + + if (!this.routingTree[lowerMethod]) { + this.routingTree[lowerMethod] = {}; } - add(method: string, rootPath: string, handler: Handler) { - const lowerMethod = method.toLowerCase(); + this.routingTree[lowerMethod][rootPath] = handler; + } - if (!this.routingTree[lowerMethod]) { - this.routingTree[lowerMethod] = {}; - } + async handle(request: Request): Promise { + const url = new URL(request.url); - this.routingTree[lowerMethod][rootPath] = handler; + if (url.pathname === '/' || url.pathname === '') { + return this.fallbacks.root(request); + } + const lowerMethod = request.method.toLowerCase(); + const rootPath = url.pathname.split('/')[1]; + const handler = this.routingTree[lowerMethod]?.[rootPath]; + + if (handler) { + try { + const response = await handler(request); + return response; + } catch (e) { + console.error(e); + return this.fallbacks[500](request); + } } - async handle(request: Request): Promise { - const url = new URL(request.url); - - if (url.pathname === '/' || url.pathname === '') { - return this.fallbacks.root(request); - } - const lowerMethod = request.method.toLowerCase(); - const rootPath = url.pathname.split('/')[1]; - const handler = this.routingTree[lowerMethod]?.[rootPath]; - - if (handler) { - try { - const response = await handler(request); - return response; - } catch (e) { - console.error(e); - return this.fallbacks[500](request); - } - } - - if (lowerMethod === 'options') { - return new Response(null, addCORS({})); - } - - return this.fallbacks[404](request); + if (lowerMethod === 'options') { + return new Response(null, addCORS({})); } - private respondToRoot(): Response { - return new Response('Hi there!'); - } + return this.fallbacks[404](request); + } - private notFound(): Response { - return new Response(JSON.stringify({ error: 'not_found' }), { - status: 404, - }); - } + private respondToRoot(): Response { + return new Response('Hi there!'); + } - private serverError(): Response { - return new Response(JSON.stringify({ error: 'internal_server_error' }), { - status: 500, - }); - } + private notFound(): Response { + return new Response(JSON.stringify({ error: 'not_found' }), { + status: 404, + }); + } + + private serverError(): Response { + return new Response(JSON.stringify({ error: 'internal_server_error' }), { + status: 500, + }); + } } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 5842e9f..1b989a0 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "outDir": "./dist", - "lib": ["esnext", "webworker", "ES2020.BigInt", "ES2020.Promise"], - "types": ["@cloudflare/workers-types"], - "target": "ES2019" - }, - "include": [ - "./*.ts", - "./**/*.ts", - "../../node_modules/@cloudflare/workers-types/index.d.ts" - ], - "exclude": ["./**/*.spec.ts", "./dist/**"], - "extends": "../../tsconfig.json" + "compilerOptions": { + "outDir": "./dist", + "lib": ["esnext", "webworker", "ES2020.BigInt", "ES2020.Promise"], + "types": ["@cloudflare/workers-types"], + "target": "ES2019" + }, + "include": [ + "./*.ts", + "./**/*.ts", + "../../node_modules/@cloudflare/workers-types/index.d.ts" + ], + "exclude": ["./**/*.spec.ts", "./dist/**"], + "extends": "../../tsconfig.json" } diff --git a/packages/api/utils/api-tools.ts b/packages/api/utils/api-tools.ts index cbee2aa..d86d862 100644 --- a/packages/api/utils/api-tools.ts +++ b/packages/api/utils/api-tools.ts @@ -1,6 +1,6 @@ import { - evaluatePermission, - permissions as Permissions, + evaluatePermission, + permissions as Permissions, } from '@roleypoly/misc-utils/hasPermission'; import { SessionData, UserGuildPermissions } from '@roleypoly/types'; import KSUID from 'ksuid'; @@ -9,207 +9,204 @@ import { allowedCallbackHosts, apiPublicURI, rootUsers } from './config'; import { Sessions, WrappedKVNamespace } from './kv'; export const formData = (obj: Record): string => { - return Object.keys(obj) - .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) - .join('&'); + return Object.keys(obj) + .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`) + .join('&'); }; export const addCORS = (init: ResponseInit = {}) => ({ - ...init, - headers: { - ...(init.headers || {}), - 'access-control-allow-origin': '*', - 'access-control-allow-methods': '*', - 'access-control-allow-headers': '*', - }, + ...init, + headers: { + ...(init.headers || {}), + 'access-control-allow-origin': '*', + 'access-control-allow-methods': '*', + 'access-control-allow-headers': '*', + }, }); export const respond = (obj: Record, init: ResponseInit = {}) => - new Response(JSON.stringify(obj), addCORS(init)); + new Response(JSON.stringify(obj), addCORS(init)); export const resolveFailures = ( - handleWith: () => Response, - handler: (request: Request) => Promise | Response + handleWith: () => Response, + handler: (request: Request) => Promise | Response ) => async (request: Request): Promise => { - try { - return handler(request); - } catch (e) { - console.error(e); - return ( - handleWith() || respond({ error: 'internal server error' }, { status: 500 }) - ); - } + try { + return handler(request); + } catch (e) { + console.error(e); + return handleWith() || respond({ error: 'internal server error' }, { status: 500 }); + } }; export const parsePermissions = ( - permissions: bigint, - owner: boolean = false + permissions: bigint, + owner: boolean = false ): UserGuildPermissions => { - if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) { - return UserGuildPermissions.Admin; - } + if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) { + return UserGuildPermissions.Admin; + } - if (evaluatePermission(permissions, Permissions.MANAGE_ROLES)) { - return UserGuildPermissions.Manager; - } + if (evaluatePermission(permissions, Permissions.MANAGE_ROLES)) { + return UserGuildPermissions.Manager; + } - return UserGuildPermissions.User; + return UserGuildPermissions.User; }; export const getSessionID = (request: Request): { type: string; id: string } | null => { - const sessionID = request.headers.get('authorization'); - if (!sessionID) { - return null; - } + const sessionID = request.headers.get('authorization'); + if (!sessionID) { + return null; + } - const [type, id] = sessionID.split(' '); - if (type !== 'Bearer') { - return null; - } + const [type, id] = sessionID.split(' '); + if (type !== 'Bearer') { + return null; + } - return { type, id }; + return { type, id }; }; export const userAgent = - 'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)'; + 'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)'; export enum AuthType { - Bearer = 'Bearer', - Bot = 'Bot', + Bearer = 'Bearer', + Bot = 'Bot', } export const discordFetch = async ( - url: string, - auth: string, - authType: AuthType = AuthType.Bearer, - init?: RequestInit + url: string, + auth: string, + authType: AuthType = AuthType.Bearer, + init?: RequestInit ): Promise => { - const response = await fetch('https://discord.com/api/v8' + url, { - ...(init || {}), - headers: { - ...(init?.headers || {}), - authorization: `${AuthType[authType]} ${auth}`, - 'user-agent': userAgent, - }, + const response = await fetch('https://discord.com/api/v8' + url, { + ...(init || {}), + headers: { + ...(init?.headers || {}), + authorization: `${AuthType[authType]} ${auth}`, + 'user-agent': userAgent, + }, + }); + + if (response.status >= 400) { + console.error('discordFetch failed', { + url, + authType, + payload: await response.text(), }); + } - if (response.status >= 400) { - console.error('discordFetch failed', { - url, - authType, - payload: await response.text(), - }); - } - - if (response.ok) { - return (await response.json()) as T; - } else { - return null; - } + if (response.ok) { + return (await response.json()) as T; + } else { + return null; + } }; export const cacheLayer = ( - kv: WrappedKVNamespace, - keyFactory: (identity: Identity) => string, - missHandler: (identity: Identity) => Promise, - ttlSeconds?: number + kv: WrappedKVNamespace, + keyFactory: (identity: Identity) => string, + missHandler: (identity: Identity) => Promise, + ttlSeconds?: number ) => async ( - identity: Identity, - options: { skipCachePull?: boolean } = {} + identity: Identity, + options: { skipCachePull?: boolean } = {} ): Promise => { - const key = keyFactory(identity); + const key = keyFactory(identity); - if (!options.skipCachePull) { - const value = await kv.get(key); - if (value) { - return value; - } + if (!options.skipCachePull) { + const value = await kv.get(key); + if (value) { + return value; } + } - const fallbackValue = await missHandler(identity); - if (!fallbackValue) { - return null; - } + const fallbackValue = await missHandler(identity); + if (!fallbackValue) { + return null; + } - await kv.put(key, fallbackValue, ttlSeconds); + await kv.put(key, fallbackValue, ttlSeconds); - return fallbackValue; + return fallbackValue; }; const NotAuthenticated = (extra?: string) => - respond( - { - error: extra || 'not authenticated', - }, - { status: 403 } - ); + respond( + { + error: extra || 'not authenticated', + }, + { status: 403 } + ); export const withSession = ( - wrappedHandler: (session: SessionData) => Handler + wrappedHandler: (session: SessionData) => Handler ): Handler => async (request: Request): Promise => { - const sessionID = getSessionID(request); - if (!sessionID) { - return NotAuthenticated('missing authentication'); - } + const sessionID = getSessionID(request); + if (!sessionID) { + return NotAuthenticated('missing authentication'); + } - const session = await Sessions.get(sessionID.id); - if (!session) { - return NotAuthenticated('authentication expired or not found'); - } + const session = await Sessions.get(sessionID.id); + if (!session) { + return NotAuthenticated('authentication expired or not found'); + } - return await wrappedHandler(session)(request); + return await wrappedHandler(session)(request); }; export const setupStateSession = async (data: T): Promise => { - const stateID = (await KSUID.random()).string; + const stateID = (await KSUID.random()).string; - await Sessions.put(`state_${stateID}`, { data }, 60 * 5); + await Sessions.put(`state_${stateID}`, { data }, 60 * 5); - return stateID; + return stateID; }; export const getStateSession = async (stateID: string): Promise => { - const stateSession = await Sessions.get<{ data: T }>(`state_${stateID}`); + const stateSession = await Sessions.get<{ data: T }>(`state_${stateID}`); - return stateSession?.data; + return stateSession?.data; }; export const isRoot = (userID: string): boolean => rootUsers.includes(userID); export const onlyRootUsers = (handler: Handler): Handler => - withSession((session) => (request: Request) => { - if (isRoot(session.user.id)) { - return handler(request); - } - - return respond( - { - error: 'not_found', - }, - { - status: 404, - } - ); - }); - -export const getQuery = (request: Request): { [x: string]: string } => { - const output: { [x: string]: string } = {}; - - for (let [key, value] of new URL(request.url).searchParams.entries()) { - output[key] = value; + withSession((session) => (request: Request) => { + if (isRoot(session.user.id)) { + return handler(request); } - return output; + return respond( + { + error: 'not_found', + }, + { + status: 404, + } + ); + }); + +export const getQuery = (request: Request): { [x: string]: string } => { + const output: { [x: string]: string } = {}; + + for (let [key, value] of new URL(request.url).searchParams.entries()) { + output[key] = value; + } + + return output; }; export const isAllowedCallbackHost = (host: string): boolean => { - return ( - host === apiPublicURI || - allowedCallbackHosts.includes(host) || - allowedCallbackHosts - .filter((callbackHost) => callbackHost.includes('*')) - .find((wildcard) => - new RegExp(wildcard.replace('*', '[a-z0-9-]+')).test(host) - ) !== null - ); + return ( + host === apiPublicURI || + allowedCallbackHosts.includes(host) || + allowedCallbackHosts + .filter((callbackHost) => callbackHost.includes('*')) + .find((wildcard) => new RegExp(wildcard.replace('*', '[a-z0-9-]+')).test(host)) !== + null + ); }; diff --git a/packages/api/utils/bounce.ts b/packages/api/utils/bounce.ts index 3aec260..9cccd74 100644 --- a/packages/api/utils/bounce.ts +++ b/packages/api/utils/bounce.ts @@ -1,7 +1,7 @@ export const Bounce = (url: string): Response => - new Response(null, { - status: 303, - headers: { - location: url, - }, - }); + new Response(null, { + status: 303, + headers: { + location: url, + }, + }); diff --git a/packages/api/utils/guild.ts b/packages/api/utils/guild.ts index a70addf..562368a 100644 --- a/packages/api/utils/guild.ts +++ b/packages/api/utils/guild.ts @@ -1,163 +1,163 @@ import { evaluatePermission, permissions } from '@roleypoly/misc-utils/hasPermission'; import { - Features, - Guild, - GuildData as GuildDataT, - OwnRoleInfo, - Role, - RoleSafety, + Features, + Guild, + GuildData as GuildDataT, + OwnRoleInfo, + Role, + RoleSafety, } from '@roleypoly/types'; import { AuthType, cacheLayer, discordFetch } from './api-tools'; import { botClientID, botToken } from './config'; import { GuildData, Guilds } from './kv'; type APIGuild = { - // Only relevant stuff - id: string; - name: string; - icon: string; - roles: APIRole[]; + // Only relevant stuff + id: string; + name: string; + icon: string; + roles: APIRole[]; }; type APIRole = { - id: string; - name: string; - color: number; - position: number; - permissions: string; - managed: boolean; + id: string; + name: string; + color: number; + position: number; + permissions: string; + managed: boolean; }; export const getGuild = cacheLayer( - Guilds, - (id: string) => `guilds/${id}`, - async (id: string) => { - const guildRaw = await discordFetch( - `/guilds/${id}`, - botToken, - AuthType.Bot - ); + Guilds, + (id: string) => `guilds/${id}`, + async (id: string) => { + const guildRaw = await discordFetch( + `/guilds/${id}`, + botToken, + AuthType.Bot + ); - if (!guildRaw) { - return null; - } + if (!guildRaw) { + return null; + } - const botMemberRoles = - (await getGuildMemberRoles({ - serverID: id, - userID: botClientID, - })) || []; + const botMemberRoles = + (await getGuildMemberRoles({ + serverID: id, + userID: botClientID, + })) || []; - const highestRolePosition = botMemberRoles.reduce((highest, roleID) => { - const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID); - if (!role) { - return highest; - } + const highestRolePosition = botMemberRoles.reduce((highest, roleID) => { + const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID); + if (!role) { + return highest; + } - // If highest is a bigger number, it stays the highest. - if (highest > role.position) { - return highest; - } + // If highest is a bigger number, it stays the highest. + if (highest > role.position) { + return highest; + } - return role.position; - }, 0); + return role.position; + }, 0); - const roles = guildRaw.roles.map((role) => ({ - id: role.id, - name: role.name, - color: role.color, - managed: role.managed, - position: role.position, - permissions: role.permissions, - safety: calculateRoleSafety(role, highestRolePosition), - })); + const roles = guildRaw.roles.map((role) => ({ + id: role.id, + name: role.name, + color: role.color, + managed: role.managed, + position: role.position, + permissions: role.permissions, + safety: calculateRoleSafety(role, highestRolePosition), + })); - // Filters the raw guild data into data we actually want - const guild: Guild & OwnRoleInfo = { - id: guildRaw.id, - name: guildRaw.name, - icon: guildRaw.icon, - roles, - highestRolePosition, - }; + // Filters the raw guild data into data we actually want + const guild: Guild & OwnRoleInfo = { + id: guildRaw.id, + name: guildRaw.name, + icon: guildRaw.icon, + roles, + highestRolePosition, + }; - return guild; - }, - 60 * 60 * 2 // 2 hour TTL + return guild; + }, + 60 * 60 * 2 // 2 hour TTL ); type GuildMemberIdentity = { - serverID: string; - userID: string; + serverID: string; + userID: string; }; type APIMember = { - // Only relevant stuff, again. - roles: string[]; + // Only relevant stuff, again. + roles: string[]; }; const guildMemberRolesIdentity = ({ serverID, userID }: GuildMemberIdentity) => - `guilds/${serverID}/members/${userID}/roles`; + `guilds/${serverID}/members/${userID}/roles`; export const getGuildMemberRoles = cacheLayer( - Guilds, - guildMemberRolesIdentity, - async ({ serverID, userID }) => { - const discordMember = await discordFetch( - `/guilds/${serverID}/members/${userID}`, - botToken, - AuthType.Bot - ); + Guilds, + guildMemberRolesIdentity, + async ({ serverID, userID }) => { + const discordMember = await discordFetch( + `/guilds/${serverID}/members/${userID}`, + botToken, + AuthType.Bot + ); - if (!discordMember) { - return null; - } + if (!discordMember) { + return null; + } - return discordMember.roles; - }, - 60 * 5 // 5 minute TTL + return discordMember.roles; + }, + 60 * 5 // 5 minute TTL ); export const updateGuildMemberRoles = async ( - identity: GuildMemberIdentity, - roles: Role['id'][] + identity: GuildMemberIdentity, + roles: Role['id'][] ) => { - await Guilds.put(guildMemberRolesIdentity(identity), roles, 60 * 5); + await Guilds.put(guildMemberRolesIdentity(identity), roles, 60 * 5); }; export const getGuildData = async (id: string): Promise => { - const guildData = await GuildData.get(id); + const guildData = await GuildData.get(id); - if (!guildData) { - return { - id, - message: '', - categories: [], - features: Features.None, - }; - } + if (!guildData) { + return { + id, + message: '', + categories: [], + features: Features.None, + }; + } - return guildData; + return guildData; }; const calculateRoleSafety = (role: Role | APIRole, highestBotRolePosition: number) => { - let safety = RoleSafety.Safe; + let safety = RoleSafety.Safe; - if (role.managed) { - safety |= RoleSafety.ManagedRole; - } + if (role.managed) { + safety |= RoleSafety.ManagedRole; + } - if (role.position > highestBotRolePosition) { - safety |= RoleSafety.HigherThanBot; - } + if (role.position > highestBotRolePosition) { + safety |= RoleSafety.HigherThanBot; + } - const permBigInt = BigInt(role.permissions); - if ( - evaluatePermission(permBigInt, permissions.ADMINISTRATOR) || - evaluatePermission(permBigInt, permissions.MANAGE_ROLES) - ) { - safety |= RoleSafety.DangerousPermissions; - } + const permBigInt = BigInt(role.permissions); + if ( + evaluatePermission(permBigInt, permissions.ADMINISTRATOR) || + evaluatePermission(permBigInt, permissions.MANAGE_ROLES) + ) { + safety |= RoleSafety.DangerousPermissions; + } - return safety; + return safety; }; diff --git a/packages/api/utils/kv.ts b/packages/api/utils/kv.ts index b1b8e2e..e25b62a 100644 --- a/packages/api/utils/kv.ts +++ b/packages/api/utils/kv.ts @@ -1,87 +1,87 @@ export class WrappedKVNamespace { - constructor(private kvNamespace: KVNamespace) {} + constructor(private kvNamespace: KVNamespace) {} - async get(key: string): Promise { - const data = await this.kvNamespace.get(key, 'text'); - if (!data) { - return null; - } - - return JSON.parse(data) as T; + async get(key: string): Promise { + const data = await this.kvNamespace.get(key, 'text'); + if (!data) { + return null; } - async put(key: string, value: T, ttlSeconds?: number) { - await this.kvNamespace.put(key, JSON.stringify(value), { - expirationTtl: ttlSeconds, - }); - } + return JSON.parse(data) as T; + } - list = this.kvNamespace.list; - getWithMetadata = this.kvNamespace.getWithMetadata; - delete = this.kvNamespace.delete; + async put(key: string, value: T, ttlSeconds?: number) { + await this.kvNamespace.put(key, JSON.stringify(value), { + expirationTtl: ttlSeconds, + }); + } + + list = this.kvNamespace.list; + getWithMetadata = this.kvNamespace.getWithMetadata; + delete = this.kvNamespace.delete; } class EmulatedKV implements KVNamespace { - constructor() { - console.warn('EmulatedKV used. Data will be lost.'); + constructor() { + console.warn('EmulatedKV used. Data will be lost.'); + } + + private data: Map = new Map(); + + async get(key: string): Promise { + if (!this.data.has(key)) { + return null; } - private data: Map = new Map(); + return this.data.get(key); + } - async get(key: string): Promise { - if (!this.data.has(key)) { - return null; - } + async getWithMetadata( + key: string + ): KVValueWithMetadata { + return { + value: await this.get(key), + metadata: {} as Metadata, + }; + } - return this.data.get(key); + async put(key: string, value: string | ReadableStream | ArrayBuffer | FormData) { + this.data.set(key, value); + } + + async delete(key: string) { + this.data.delete(key); + } + + async list(options?: { + prefix?: string; + limit?: number; + cursor?: string; + }): Promise<{ + keys: { name: string; expiration?: number; metadata?: unknown }[]; + list_complete: boolean; + cursor: string; + }> { + let keys: { name: string }[] = []; + + for (let key of this.data.keys()) { + if (options?.prefix && !key.startsWith(options.prefix)) { + continue; + } + + keys.push({ name: key }); } - async getWithMetadata( - key: string - ): KVValueWithMetadata { - return { - value: await this.get(key), - metadata: {} as Metadata, - }; - } - - async put(key: string, value: string | ReadableStream | ArrayBuffer | FormData) { - this.data.set(key, value); - } - - async delete(key: string) { - this.data.delete(key); - } - - async list(options?: { - prefix?: string; - limit?: number; - cursor?: string; - }): Promise<{ - keys: { name: string; expiration?: number; metadata?: unknown }[]; - list_complete: boolean; - cursor: string; - }> { - let keys: { name: string }[] = []; - - for (let key of this.data.keys()) { - if (options?.prefix && !key.startsWith(options.prefix)) { - continue; - } - - keys.push({ name: key }); - } - - return { - keys, - cursor: '0', - list_complete: true, - }; - } + return { + keys, + cursor: '0', + list_complete: true, + }; + } } const kvOrLocal = (namespace: KVNamespace | null): KVNamespace => - namespace || new EmulatedKV(); + namespace || new EmulatedKV(); const self = (global as any) as Record; diff --git a/packages/api/webpack.config.js b/packages/api/webpack.config.js index cfe44fc..bc319c2 100644 --- a/packages/api/webpack.config.js +++ b/packages/api/webpack.config.js @@ -3,26 +3,26 @@ const path = require('path'); const mode = process.env.NODE_ENV || 'production'; module.exports = { - target: 'webworker', - entry: path.join(__dirname, 'index.ts'), - output: { - filename: `worker.${mode}.js`, - path: path.join(__dirname, 'dist'), - }, - mode, - resolve: { - extensions: ['.ts', '.tsx', '.js'], - }, - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { - transpileOnly: true, - configFile: path.join(__dirname, 'tsconfig.json'), - }, - }, - ], - }, + target: 'webworker', + entry: path.join(__dirname, 'index.ts'), + output: { + filename: `worker.${mode}.js`, + path: path.join(__dirname, 'dist'), + }, + mode, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: 'ts-loader', + options: { + transpileOnly: true, + configFile: path.join(__dirname, 'tsconfig.json'), + }, + }, + ], + }, }; diff --git a/packages/api/worker.config.js b/packages/api/worker.config.js index f26bde6..3cb26d9 100644 --- a/packages/api/worker.config.js +++ b/packages/api/worker.config.js @@ -1,16 +1,16 @@ const reexportEnv = (keys = []) => { - return keys.reduce((acc, key) => ({ ...acc, [key]: process.env[key] }), {}); + return keys.reduce((acc, key) => ({ ...acc, [key]: process.env[key] }), {}); }; module.exports = { - environment: reexportEnv([ - 'BOT_CLIENT_ID', - 'BOT_CLIENT_SECRET', - 'BOT_TOKEN', - 'UI_PUBLIC_URI', - 'API_PUBLIC_URI', - 'ROOT_USERS', - 'ALLOWED_CALLBACK_HOSTS', - ]), - kv: ['KV_SESSIONS', 'KV_GUILDS', 'KV_GUILD_DATA'], + environment: reexportEnv([ + 'BOT_CLIENT_ID', + 'BOT_CLIENT_SECRET', + 'BOT_TOKEN', + 'UI_PUBLIC_URI', + 'API_PUBLIC_URI', + 'ROOT_USERS', + 'ALLOWED_CALLBACK_HOSTS', + ]), + kv: ['KV_SESSIONS', 'KV_GUILDS', 'KV_GUILD_DATA'], }; diff --git a/packages/backend-emulator/kv.js b/packages/backend-emulator/kv.js index a51545a..524590e 100644 --- a/packages/backend-emulator/kv.js +++ b/packages/backend-emulator/kv.js @@ -5,98 +5,98 @@ const fs = require('fs'); let hasWarned = false; const getConversion = { - text: (x) => x, - json: (x) => JSON.parse(x), - arrayBuffer: (x) => Buffer.from(x).buffer, - stream: (x) => Buffer.from(x), + text: (x) => x, + json: (x) => JSON.parse(x), + arrayBuffer: (x) => Buffer.from(x).buffer, + stream: (x) => Buffer.from(x), }; class KVShim { - constructor(namespace) { - this.namespace = namespace; + constructor(namespace) { + this.namespace = namespace; - fs.mkdirSync(path.resolve(__dirname, '../../.devdbs'), { - recursive: true, - }); + fs.mkdirSync(path.resolve(__dirname, '../../.devdbs'), { + recursive: true, + }); - (async () => { - this.level = level(path.resolve(__dirname, '../../.devdbs', namespace)); - })(); + (async () => { + this.level = level(path.resolve(__dirname, '../../.devdbs', namespace)); + })(); + } + + makeValue(value, expirationTtl) { + if (!expirationTtl) { + return JSON.stringify({ + value, + expires: false, + }); } - makeValue(value, expirationTtl) { - if (!expirationTtl) { - return JSON.stringify({ - value, - expires: false, - }); - } + return JSON.stringify({ + value, + expires: Date.now() + 1000 * expirationTtl, + }); + } - return JSON.stringify({ - value, - expires: Date.now() + 1000 * expirationTtl, - }); + validate(value) { + if (!value) { + return false; } - validate(value) { - if (!value) { - return false; - } - - if (value.expires && value.expires < Date.now()) { - return false; - } - - return true; + if (value.expires && value.expires < Date.now()) { + return false; } - async get(key, type = 'text') { - try { - const result = JSON.parse(await this.level.get(key)); + return true; + } - if (!this.validate(result)) { - return null; - } + async get(key, type = 'text') { + try { + const result = JSON.parse(await this.level.get(key)); - return getConversion[type](result.value); - } catch (e) { - return null; - } + if (!this.validate(result)) { + return null; + } + + return getConversion[type](result.value); + } catch (e) { + return null; } + } - async getWithMetadata(key, type) { - return { - value: await this.get(key, type), - metadata: {}, - }; - } - - async put(key, value, { expirationTtl, expiration, metadata }) { - if ((expiration || metadata) && !hasWarned) { - console.warn( - 'expiration and metadata is lost in the emulator. Use expirationTtl, please.' - ); - hasWarned = true; - } - - return await this.level.put(key, this.makeValue(value, expirationTtl)); - } - - // This loses scope for some unknown reason - delete = async (key) => { - return this.level.del(key); + async getWithMetadata(key, type) { + return { + value: await this.get(key, type), + metadata: {}, }; + } - list() { - console.warn('List is frowned upon and will fail to fetch keys in the emulator.'); - return { - keys: [], - cursor: '0', - list_complete: true, - }; + async put(key, value, { expirationTtl, expiration, metadata }) { + if ((expiration || metadata) && !hasWarned) { + console.warn( + 'expiration and metadata is lost in the emulator. Use expirationTtl, please.' + ); + hasWarned = true; } + + return await this.level.put(key, this.makeValue(value, expirationTtl)); + } + + // This loses scope for some unknown reason + delete = async (key) => { + return this.level.del(key); + }; + + list() { + console.warn('List is frowned upon and will fail to fetch keys in the emulator.'); + return { + keys: [], + cursor: '0', + list_complete: true, + }; + } } module.exports = { - KVShim, + KVShim, }; diff --git a/packages/backend-emulator/main.js b/packages/backend-emulator/main.js index 8bb7777..4e1b80a 100644 --- a/packages/backend-emulator/main.js +++ b/packages/backend-emulator/main.js @@ -13,17 +13,17 @@ const args = require('minimist')(process.argv.slice(2)); const basePath = args.basePath; if (!basePath) { - throw new Error('--basePath is not set.'); + throw new Error('--basePath is not set.'); } const workerConfig = require(`${basePath}/worker.config.js`); const getKVs = (namespaces = []) => - namespaces.reduce((acc, ns) => ({ ...acc, [ns]: new KVShim(ns) }), {}); + namespaces.reduce((acc, ns) => ({ ...acc, [ns]: new KVShim(ns) }), {}); const workerShims = { - ...workerConfig.environment, - ...getKVs(workerConfig.kv), + ...workerConfig.environment, + ...getKVs(workerConfig.kv), }; let listeners = []; @@ -35,160 +35,158 @@ let isResponseConstructorAllowed = false; * Cloudflare will reject all Response objects that aren't created during a request, so no pre-generation is allowed. */ class SafeResponse extends fetch.Response { - constructor(...args) { - super(...args); + constructor(...args) { + super(...args); - if (!isResponseConstructorAllowed) { - throw new Error( - 'Response object created outside of request context. This will be rejected by Cloudflare.' - ); - } + if (!isResponseConstructorAllowed) { + throw new Error( + 'Response object created outside of request context. This will be rejected by Cloudflare.' + ); } + } } const context = () => - vm.createContext( - { - addEventListener: (a, fn) => { - if (a === 'fetch') { - console.log('addEventListeners: added fetch'); - listeners.push(fn); - } - }, - Response: SafeResponse, - URL: URL, - crypto: crypto, - setTimeout: setTimeout, - setInterval: setInterval, - clearInterval: clearInterval, - clearTimeout: clearTimeout, - fetch: fetch, - console: console, - ...workerShims, - }, - { - codeGeneration: { - strings: false, - wasm: false, - }, + vm.createContext( + { + addEventListener: (a, fn) => { + if (a === 'fetch') { + console.log('addEventListeners: added fetch'); + listeners.push(fn); } - ); + }, + Response: SafeResponse, + URL: URL, + crypto: crypto, + setTimeout: setTimeout, + setInterval: setInterval, + clearInterval: clearInterval, + clearTimeout: clearTimeout, + fetch: fetch, + console: console, + ...workerShims, + }, + { + codeGeneration: { + strings: false, + wasm: false, + }, + } + ); const server = http.createServer((req, res) => { - const event = { - respondWith: async (value) => { - const timeStart = Date.now(); - let loggedStatus; - try { - const response = await value; - if (!response) { - throw new Error( - `response was invalid, got ${JSON.stringify(response)}` - ); - } - res.statusCode = response.status; - loggedStatus = String(response.status); - response.headers.forEach((value, key) => res.setHeader(key, value)); - res.end(response.body); - } catch (e) { - console.error(e); - res.statusCode = 500; - loggedStatus = '500'; - res.end(JSON.stringify({ error: 'internal server error' })); - } - const timeEnd = Date.now(); - console.log( - `${loggedStatus} [${timeEnd - timeStart}ms] - ${req.method} ${req.url}` - ); - isResponseConstructorAllowed = false; - }, - request: new fetch.Request( - new URL(`http://${req.headers.host || 'localhost'}${req.url}`), - { - body: ['GET', 'HEAD'].includes(req.method) ? undefined : req, - headers: req.headers, - method: req.method, - } - ), - }; - - event.request.headers.set('cf-client-ip', req.connection.remoteAddress); - - if (listeners.length === 0) { - res.statusCode = 503; - res.end('No handlers are available.'); - console.error('No handlers are available'); - return; - } - - isResponseConstructorAllowed = true; - for (let listener of listeners) { - try { - listener(event); - } catch (e) { - console.error('listener errored', e); + const event = { + respondWith: async (value) => { + const timeStart = Date.now(); + let loggedStatus; + try { + const response = await value; + if (!response) { + throw new Error(`response was invalid, got ${JSON.stringify(response)}`); } + res.statusCode = response.status; + loggedStatus = String(response.status); + response.headers.forEach((value, key) => res.setHeader(key, value)); + res.end(response.body); + } catch (e) { + console.error(e); + res.statusCode = 500; + loggedStatus = '500'; + res.end(JSON.stringify({ error: 'internal server error' })); + } + const timeEnd = Date.now(); + console.log( + `${loggedStatus} [${timeEnd - timeStart}ms] - ${req.method} ${req.url}` + ); + isResponseConstructorAllowed = false; + }, + request: new fetch.Request( + new URL(`http://${req.headers.host || 'localhost'}${req.url}`), + { + body: ['GET', 'HEAD'].includes(req.method) ? undefined : req, + headers: req.headers, + method: req.method, + } + ), + }; + + event.request.headers.set('cf-client-ip', req.connection.remoteAddress); + + if (listeners.length === 0) { + res.statusCode = 503; + res.end('No handlers are available.'); + console.error('No handlers are available'); + return; + } + + isResponseConstructorAllowed = true; + for (let listener of listeners) { + try { + listener(event); + } catch (e) { + console.error('listener errored', e); } + } }); const fork = async (fn) => fn(); const reload = () => { - // Clear listeners... - listeners = []; + // Clear listeners... + listeners = []; - // Fork and re-run - fork(async () => - vm.runInContext( - fs.readFileSync(path.resolve(__dirname, `${basePath}/dist/worker.js`)), - context(), - { - displayErrors: true, - filename: 'worker.js', - } - ) - ); + // Fork and re-run + fork(async () => + vm.runInContext( + fs.readFileSync(path.resolve(__dirname, `${basePath}/dist/worker.js`)), + context(), + { + displayErrors: true, + filename: 'worker.js', + } + ) + ); }; const rebuild = () => - new Promise((resolve, reject) => { - const webpackConfig = require(`${basePath}/webpack.config.js`); - webpackConfig.output.filename = 'worker.js'; - webpack(webpackConfig).run((err, stats) => { - if (err) { - console.log('Compilation failed.', err); - reject(err); - } else { - if (stats.hasErrors()) { - console.error('Compilation errored:', stats.compilation.errors); - return; - } + new Promise((resolve, reject) => { + const webpackConfig = require(`${basePath}/webpack.config.js`); + webpackConfig.output.filename = 'worker.js'; + webpack(webpackConfig).run((err, stats) => { + if (err) { + console.log('Compilation failed.', err); + reject(err); + } else { + if (stats.hasErrors()) { + console.error('Compilation errored:', stats.compilation.errors); + return; + } - console.log('Compilation done.'); - resolve(); - } - }); + console.log('Compilation done.'); + resolve(); + } }); + }); const watcher = chokidar.watch(path.resolve(__dirname, basePath), { - ignoreInitial: true, - ignore: '**/dist', + ignoreInitial: true, + ignore: '**/dist', }); watcher.on('all', async (type, path) => { - if (path.includes('dist')) { - return; - } + if (path.includes('dist')) { + return; + } - console.log('change detected, rebuilding and reloading', { type, path }); + console.log('change detected, rebuilding and reloading', { type, path }); - await rebuild(); - reload(); + await rebuild(); + reload(); }); fork(async () => { - await rebuild(); - reload(); + await rebuild(); + reload(); }); console.log('starting on http://localhost:6609'); diff --git a/packages/backend-emulator/package.json b/packages/backend-emulator/package.json index 3d5b6bc..09dfc44 100644 --- a/packages/backend-emulator/package.json +++ b/packages/backend-emulator/package.json @@ -1,17 +1,17 @@ { - "name": "@roleypoly/worker-emulator", - "version": "0.1.0", - "scripts": { - "build": "node main.js --build", - "start": "node main.js" - }, - "devDependencies": { - "@peculiar/webcrypto": "^1.1.6", - "chokidar": "^3.5.1", - "dotenv": "^8.2.0", - "level": "^6.0.1", - "minimist": "^1.2.5", - "node-fetch": "^2.6.1", - "webpack": "^4.x" - } + "name": "@roleypoly/worker-emulator", + "version": "0.1.0", + "scripts": { + "build": "node main.js --build", + "start": "node main.js" + }, + "devDependencies": { + "@peculiar/webcrypto": "^1.1.6", + "chokidar": "^3.5.1", + "dotenv": "^8.2.0", + "level": "^6.0.1", + "minimist": "^1.2.5", + "node-fetch": "^2.6.1", + "webpack": "^4.x" + } } diff --git a/packages/design-system/.storybook/main.js b/packages/design-system/.storybook/main.js index 360b544..f625358 100644 --- a/packages/design-system/.storybook/main.js +++ b/packages/design-system/.storybook/main.js @@ -1,4 +1,4 @@ module.exports = { - stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'], - addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-links', '@storybook/addon-essentials'], }; diff --git a/packages/design-system/.storybook/manager.js b/packages/design-system/.storybook/manager.js index 69a5b09..f4ef531 100644 --- a/packages/design-system/.storybook/manager.js +++ b/packages/design-system/.storybook/manager.js @@ -2,5 +2,5 @@ import { addons } from '@storybook/addons'; import { roleypolyTheme } from './theme'; addons.setConfig({ - theme: roleypolyTheme, + theme: roleypolyTheme, }); diff --git a/packages/design-system/.storybook/mocks/next_link.tsx b/packages/design-system/.storybook/mocks/next_link.tsx index b921931..393ff9d 100644 --- a/packages/design-system/.storybook/mocks/next_link.tsx +++ b/packages/design-system/.storybook/mocks/next_link.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; type Props = { - children: React.ReactNode; + children: React.ReactNode; }; const Link = (props: Props) => <>{props.children}; diff --git a/packages/design-system/.storybook/preview.js b/packages/design-system/.storybook/preview.js index d32ed34..0bf478f 100644 --- a/packages/design-system/.storybook/preview.js +++ b/packages/design-system/.storybook/preview.js @@ -2,9 +2,9 @@ import { roleypolyTheme } from './theme'; import { mdxComponents } from '../atoms/typography/mdx'; export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - docs: { - theme: roleypolyTheme, - components: mdxComponents, - }, + actions: { argTypesRegex: '^on[A-Z].*' }, + docs: { + theme: roleypolyTheme, + components: mdxComponents, + }, }; diff --git a/packages/design-system/.storybook/theme.js b/packages/design-system/.storybook/theme.js index 4c73525..a4706de 100644 --- a/packages/design-system/.storybook/theme.js +++ b/packages/design-system/.storybook/theme.js @@ -2,33 +2,33 @@ import { create } from '@storybook/theming'; import { palette } from '../atoms/colors'; export const roleypolyTheme = create({ - base: 'dark', + base: 'dark', - colorPrimary: palette.green400, - colorSecondary: palette.taupe200, + colorPrimary: palette.green400, + colorSecondary: palette.taupe200, - // UI - appBg: palette.taupe300, - appContentBg: palette.taupe200, - appBorderColor: palette.taupe100, - appBorderRadius: 0, + // UI + appBg: palette.taupe300, + appContentBg: palette.taupe200, + appBorderColor: palette.taupe100, + appBorderRadius: 0, - // Typography - fontBase: 'system-ui, sans-serif', - fontCode: 'monospace', + // Typography + fontBase: 'system-ui, sans-serif', + fontCode: 'monospace', - // Text colors - textColor: palette.grey600, - textInverseColor: palette.grey100, + // Text colors + textColor: palette.grey600, + textInverseColor: palette.grey100, - // Toolbar default and active colors - barTextColor: palette.taupe500, - barSelectedColor: palette.taupe600, - barBg: palette.taupe100, + // Toolbar default and active colors + barTextColor: palette.taupe500, + barSelectedColor: palette.taupe600, + barBg: palette.taupe100, - // Form colors - inputBg: 'rgba(0,0,0,0.24)', - inputBorder: palette.taupe100, - inputTextColor: palette.grey600, - inputBorderRadius: 0, + // Form colors + inputBg: 'rgba(0,0,0,0.24)', + inputBorder: palette.taupe100, + inputTextColor: palette.grey600, + inputBorderRadius: 0, }); diff --git a/packages/design-system/atoms/avatar/Avatar.stories.tsx b/packages/design-system/atoms/avatar/Avatar.stories.tsx index b734fca..30934a6 100644 --- a/packages/design-system/atoms/avatar/Avatar.stories.tsx +++ b/packages/design-system/atoms/avatar/Avatar.stories.tsx @@ -2,33 +2,33 @@ import * as React from 'react'; import { Avatar, AvatarProps } from './Avatar'; export default { - title: 'Atoms/Avatar', - component: Avatar, - argTypes: { - initials: { control: 'text' }, - }, - args: { - initials: 'KR', - hash: 'aa', - }, + title: 'Atoms/Avatar', + component: Avatar, + argTypes: { + initials: { control: 'text' }, + }, + args: { + initials: 'KR', + hash: 'aa', + }, }; type StoryArgs = { - initials?: string; + initials?: string; } & AvatarProps; export const WithInitials = ({ initials, ...rest }: StoryArgs) => ( - - {initials} - + + {initials} + ); export const WithText = ({ initials, ...rest }: StoryArgs) => ( - - {initials} - + + {initials} + ); export const Empty = (args: StoryArgs) => ; export const DeliberatelyEmpty = (args: StoryArgs) => ( - + ); diff --git a/packages/design-system/atoms/avatar/Avatar.styled.ts b/packages/design-system/atoms/avatar/Avatar.styled.ts index 2f829e0..5b59c3f 100644 --- a/packages/design-system/atoms/avatar/Avatar.styled.ts +++ b/packages/design-system/atoms/avatar/Avatar.styled.ts @@ -4,41 +4,41 @@ import { AvatarProps } from './Avatar'; type ContainerProps = Pick & Pick; export const Container = styled.div` - border-radius: 100%; - box-sizing: border-box; - width: ${(props: ContainerProps) => props.size || 48}px; - height: ${(props: ContainerProps) => props.size || 48}px; - min-width: ${(props: ContainerProps) => props.size || 48}px; - min-height: ${(props: ContainerProps) => props.size || 48}px; - display: flex; - justify-content: center; - align-items: center; - color: ${palette.grey100}; - position: relative; - background-color: ${palette.grey500}; - font-weight: bold; - text-align: center; - line-height: 1; - overflow: hidden; - font-size: ${(props: ContainerProps) => props.size}; - ${(props) => - props.deliberatelyEmpty && - css` - border: 4px solid rgba(0, 0, 0, 0.25); - background-color: ${palette.taupe400}; - color: ${palette.taupe600}; - `} + border-radius: 100%; + box-sizing: border-box; + width: ${(props: ContainerProps) => props.size || 48}px; + height: ${(props: ContainerProps) => props.size || 48}px; + min-width: ${(props: ContainerProps) => props.size || 48}px; + min-height: ${(props: ContainerProps) => props.size || 48}px; + display: flex; + justify-content: center; + align-items: center; + color: ${palette.grey100}; + position: relative; + background-color: ${palette.grey500}; + font-weight: bold; + text-align: center; + line-height: 1; + overflow: hidden; + font-size: ${(props: ContainerProps) => props.size}; + ${(props) => + props.deliberatelyEmpty && + css` + border: 4px solid rgba(0, 0, 0, 0.25); + background-color: ${palette.taupe400}; + color: ${palette.taupe600}; + `} `; type ImageProps = Pick; export const Image = styled.div` - background-size: cover; - background-repeat: no-repeat; - background-position: 50% 50%; - top: 0; - left: 0; - right: 0; - bottom: 0; - position: absolute; - border-radius: 100%; + background-size: cover; + background-repeat: no-repeat; + background-position: 50% 50%; + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + border-radius: 100%; `; diff --git a/packages/design-system/atoms/avatar/Avatar.tsx b/packages/design-system/atoms/avatar/Avatar.tsx index 15ea17a..329506e 100644 --- a/packages/design-system/atoms/avatar/Avatar.tsx +++ b/packages/design-system/atoms/avatar/Avatar.tsx @@ -2,28 +2,28 @@ import React from 'react'; import { Container, Image } from './Avatar.styled'; export type AvatarProps = { - src?: string; - children?: string | React.ReactNode; - size?: number; - hash?: string; - deliberatelyEmpty?: boolean; + src?: string; + children?: string | React.ReactNode; + size?: number; + hash?: string; + deliberatelyEmpty?: boolean; }; /** Chuldren is recommended to not be larger than 2 uppercase letters. */ export const Avatar = (props: AvatarProps) => ( - - {props.src && props.hash && ( - - )} -
- {props.children || ( - /* needs specifically   to prevent layout issues. */ - <>  - )} -
-
+ + {props.src && props.hash && ( + + )} +
+ {props.children || ( + /* needs specifically   to prevent layout issues. */ + <>  + )} +
+
); diff --git a/packages/design-system/atoms/avatar/avatarUtils.tsx b/packages/design-system/atoms/avatar/avatarUtils.tsx index 97318ca..7b063bd 100644 --- a/packages/design-system/atoms/avatar/avatarUtils.tsx +++ b/packages/design-system/atoms/avatar/avatarUtils.tsx @@ -1,16 +1,16 @@ export const initialsFromName = (name: string) => - !!name - ? name - .split(' ') - .slice(0, 2) - .map((x) => x[0]) - .join('') - .toUpperCase() - : ''; + !!name + ? name + .split(' ') + .slice(0, 2) + .map((x) => x[0]) + .join('') + .toUpperCase() + : ''; export const avatarHash = ( - id: string, - hash: string, - bucket: 'icons' | 'avatars' = 'icons', - size: number = 256 + id: string, + hash: string, + bucket: 'icons' | 'avatars' = 'icons', + size: number = 256 ) => `https://cdn.discordapp.com/${bucket}/${id}/${hash}.webp?size=${size}`; diff --git a/packages/design-system/atoms/branding/Branding.stories.tsx b/packages/design-system/atoms/branding/Branding.stories.tsx index 2ae06e4..c956f87 100644 --- a/packages/design-system/atoms/branding/Branding.stories.tsx +++ b/packages/design-system/atoms/branding/Branding.stories.tsx @@ -4,22 +4,22 @@ import { palette } from '../colors'; import { Logomark as BrandingLogomark, Logotype as BrandingLogotype } from './Branding'; export default { - title: 'Atoms/Branding', + title: 'Atoms/Branding', }; const Wrapper = styled.div` - background-color: ${palette.taupe100}; - padding: 2em; + background-color: ${palette.taupe100}; + padding: 2em; `; export const Logomark = () => ( - - - + + + ); export const Logotype = () => ( - - - + + + ); diff --git a/packages/design-system/atoms/branding/Branding.tsx b/packages/design-system/atoms/branding/Branding.tsx index c0db26e..bcae071 100644 --- a/packages/design-system/atoms/branding/Branding.tsx +++ b/packages/design-system/atoms/branding/Branding.tsx @@ -2,153 +2,153 @@ import { palette } from '@roleypoly/design-system/atoms/colors'; import * as React from 'react'; export type LogoProps = { - fill: string; - width: number; - height: number; - circleFill: string; - circleOuterFill: string; - typeFill: string; - style: object; - className: string; - 'data-for'?: string; - 'data-tip'?: string; + fill: string; + width: number; + height: number; + circleFill: string; + circleOuterFill: string; + typeFill: string; + style: object; + className: string; + 'data-for'?: string; + 'data-tip'?: string; }; export const Logotype = ({ - typeFill = palette.taupe400, - circleFill = palette.red200, - circleOuterFill = palette.green200, - ...props + typeFill = palette.taupe400, + circleFill = palette.red200, + circleOuterFill = palette.green200, + ...props }: Partial) => ( - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + ); export const Logomark = ({ - circleFill = palette.red200, - circleOuterFill = palette.green200, - ...props + circleFill = palette.red200, + circleOuterFill = palette.green200, + ...props }: Partial) => ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/packages/design-system/atoms/branding/BrandingOld.tsx b/packages/design-system/atoms/branding/BrandingOld.tsx index 2118661..07814e4 100644 --- a/packages/design-system/atoms/branding/BrandingOld.tsx +++ b/packages/design-system/atoms/branding/BrandingOld.tsx @@ -3,83 +3,83 @@ import { palette } from '../colors'; import { LogoProps } from './Branding'; export const LogotypeOld = ({ - fill = palette.taupe500, - width, - height, - circleFill = palette.taupe200, - typeFill, - style, - className, + fill = palette.taupe500, + width, + height, + circleFill = palette.taupe200, + typeFill, + style, + className, }: Partial = {}) => ( - - - - - - - - - - - - + + + + + + + + + + + + ); export const LogomarkOld = ({ - fill = palette.taupe500, - width, - height, - circleFill = palette.taupe200, - typeFill, - style, - className, + fill = palette.taupe500, + width, + height, + circleFill = palette.taupe200, + typeFill, + style, + className, }: Partial) => ( - - - - - - - - - - - - + + + + + + + + + + + + ); diff --git a/packages/design-system/atoms/branding/DynamicBranding.stories.tsx b/packages/design-system/atoms/branding/DynamicBranding.stories.tsx index 26a7193..de37fd8 100644 --- a/packages/design-system/atoms/branding/DynamicBranding.stories.tsx +++ b/packages/design-system/atoms/branding/DynamicBranding.stories.tsx @@ -7,72 +7,72 @@ import { Logomark, Logotype } from './Branding'; import { AllVariants, DynamicLogomark, DynamicLogotype } from './DynamicBranding'; export default { - title: 'Atoms/Branding/Dynamic', - component: DynamicLogotype, + title: 'Atoms/Branding/Dynamic', + component: DynamicLogotype, }; const WrapperDiv = styled.div` - background-color: ${palette.taupe100}; - padding: 2em; + background-color: ${palette.taupe100}; + padding: 2em; `; const Wrapper = (props: { children: React.ReactNode }) => ( - <> - {props.children} - - + <> + {props.children} + + ); export const dynamicLogotype = (args) => { - return ( - - - - ); + return ( + + + + ); }; export const dynamicLogomark = (args) => { - return ( - - - - ); + return ( + + + + ); }; export const AllCustomizedLogotypes = () => { - return ( - -
- Base Logo -
- -
- {AllVariants.map((variant, idx) => ( -
- {variant.name} -
- -
- ))} -
- ); + return ( + +
+ Base Logo +
+ +
+ {AllVariants.map((variant, idx) => ( +
+ {variant.name} +
+ +
+ ))} +
+ ); }; export const AllCustomizedLogomarks = () => { - return ( - -
- Base Logo -
- -
- {AllVariants.map((variant, idx) => ( -
- {variant.name} -
- -
- ))} -
- ); + return ( + +
+ Base Logo +
+ +
+ {AllVariants.map((variant, idx) => ( +
+ {variant.name} +
+ +
+ ))} +
+ ); }; diff --git a/packages/design-system/atoms/branding/DynamicBranding.tsx b/packages/design-system/atoms/branding/DynamicBranding.tsx index 0cd8603..1f94d09 100644 --- a/packages/design-system/atoms/branding/DynamicBranding.tsx +++ b/packages/design-system/atoms/branding/DynamicBranding.tsx @@ -6,65 +6,65 @@ import { Logomark, Logotype } from './Branding'; import { LogoFlagProps, LogomarkFlag, LogotypeFlag } from './FlagBranding'; type DynamicLogoProps = LogoFlagProps & { - currentDate?: Date; + currentDate?: Date; }; export const DynamicLogomark = (props: Partial) => { - const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ - props.currentDate, - ]); + const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ + props.currentDate, + ]); - if (!variant) { - return ; - } + if (!variant) { + return ; + } - let tooltipProps = {}; - if (variant.tooltip) { - tooltipProps = { - 'data-tip': variant.tooltip, - // 'data-for': 'dynamic-logomark', - }; - } + let tooltipProps = {}; + if (variant.tooltip) { + tooltipProps = { + 'data-tip': variant.tooltip, + // 'data-for': 'dynamic-logomark', + }; + } - return ( - <> - - - - ); + return ( + <> + + + + ); }; export const DynamicLogotype = (props: Partial) => { - const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ - props.currentDate, - ]); + const variant = React.useMemo(() => getRelevantVariant(props.currentDate), [ + props.currentDate, + ]); - if (!variant) { - return ; - } + if (!variant) { + return ; + } - let tooltipProps = {}; - if (variant.tooltip) { - tooltipProps = { - 'data-tip': variant.tooltip, - // 'data-for': 'dynamic-logomark', - }; - } + let tooltipProps = {}; + if (variant.tooltip) { + tooltipProps = { + 'data-tip': variant.tooltip, + // 'data-for': 'dynamic-logomark', + }; + } - return ( - <> - - - - ); + return ( + <> + + + + ); }; const getRelevantVariant = (currentDate?: Date) => { - for (let variant of AllVariants) { - if (variant.activeIf(currentDate)) return variant; - } + for (let variant of AllVariants) { + if (variant.activeIf(currentDate)) return variant; + } - return null; + return null; }; // These should be updated for 2021. @@ -76,352 +76,338 @@ const getRelevantVariant = (currentDate?: Date) => { // - 4/20 is just for 4/20. const matchDay = ( - start: Date, - end: Date, - currentDate: Date = new Date(), - staticDate: boolean = false + start: Date, + end: Date, + currentDate: Date = new Date(), + staticDate: boolean = false ) => { - if (!staticDate) { - // pre-fill start/end years to simplify - start.setFullYear(currentDate.getFullYear()); - end.setFullYear(currentDate.getFullYear()); - } + if (!staticDate) { + // pre-fill start/end years to simplify + start.setFullYear(currentDate.getFullYear()); + end.setFullYear(currentDate.getFullYear()); + } - start.setHours(0, 0, 0, 0); - end.setHours(0, 0, 0, 0); + start.setHours(0, 0, 0, 0); + end.setHours(0, 0, 0, 0); - if (currentDate > start && currentDate < end) { - return true; - } + if (currentDate > start && currentDate < end) { + return true; + } - return false; + return false; }; type Variant = { - name: string; - activeIf: (currentDate?: Date) => boolean; - sharedProps?: Partial; - flagStripes?: string[]; - tooltip?: string; - Logomark: React.FunctionComponent; - Logotype: React.FunctionComponent; + name: string; + activeIf: (currentDate?: Date) => boolean; + sharedProps?: Partial; + flagStripes?: string[]; + tooltip?: string; + Logomark: React.FunctionComponent; + Logotype: React.FunctionComponent; }; export const Trans: Variant = { - // March 31, Nov 13-20+1 - name: 'Trans Pride', - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Mar-31'), new Date('2021-Apr-1'), currentDate) || - matchDay(new Date('2021-Nov-13'), new Date('2021-Nov-22'), currentDate), - sharedProps: { - circleFill: '#F7A8B8', - circleOuterFill: palette.taupe200, - typeFill: palette.grey500, - stripes: ['#55CDFC', '#F7A8B8', palette.grey600, '#F7A8B8', '#55CDFC'], - }, - tooltip: 'Roleypoly says trans rights!', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + // March 31, Nov 13-20+1 + name: 'Trans Pride', + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Mar-31'), new Date('2021-Apr-1'), currentDate) || + matchDay(new Date('2021-Nov-13'), new Date('2021-Nov-22'), currentDate), + sharedProps: { + circleFill: '#F7A8B8', + circleOuterFill: palette.taupe200, + typeFill: palette.grey500, + stripes: ['#55CDFC', '#F7A8B8', palette.grey600, '#F7A8B8', '#55CDFC'], + }, + tooltip: 'Roleypoly says trans rights!', + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( + + ), }; export const Bi: Variant = { - // Sept 16-23 - name: 'Bi Week', - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Sep-16'), new Date('2021-Sep-24'), currentDate), - sharedProps: { - circleFill: '#D60270', - circleOuterFill: palette.taupe200, - typeFill: '#9B4F96', - stripes: ['#0038A8', '#0038A8', '#9B4F96', '#D60270', '#D60270'], - }, - tooltip: 'Being bi is a lot like a riding a bicycle since they can go both ways.', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + // Sept 16-23 + name: 'Bi Week', + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Sep-16'), new Date('2021-Sep-24'), currentDate), + sharedProps: { + circleFill: '#D60270', + circleOuterFill: palette.taupe200, + typeFill: '#9B4F96', + stripes: ['#0038A8', '#0038A8', '#9B4F96', '#D60270', '#D60270'], + }, + tooltip: 'Being bi is a lot like a riding a bicycle since they can go both ways.', + Logomark: (props: DynamicLogoProps) => , + Logotype: (props: DynamicLogoProps) => , }; export const Lesbian: Variant = { - // Apr 26 - name: 'Lesbian Pride', - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Apr-25'), new Date('2021-Apt-27'), currentDate), - sharedProps: { - circleFill: '#D362A4', - circleOuterFill: palette.taupe200, - typeFill: '#FF9A56', - stripes: ['#D52D00', '#FF9A56', palette.grey600, '#D362A4', '#A30262'], - }, - tooltip: "I'm a lesbiab... lesbiam... Less Bien... Girls.", - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + // Apr 26 + name: 'Lesbian Pride', + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Apr-25'), new Date('2021-Apt-27'), currentDate), + sharedProps: { + circleFill: '#D362A4', + circleOuterFill: palette.taupe200, + typeFill: '#FF9A56', + stripes: ['#D52D00', '#FF9A56', palette.grey600, '#D362A4', '#A30262'], + }, + tooltip: "I'm a lesbiab... lesbiam... Less Bien... Girls.", + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( + + ), }; export const Ace: Variant = { - // Oct 24-30 - name: 'Ace Week', - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Oct-24'), new Date('2021-Oct-31'), currentDate), - sharedProps: { - circleFill: '#333', - circleOuterFill: palette.taupe200, - typeFill: '#CCC', - stripes: ['#84067C', palette.grey600, '#CCCCCC', palette.grey100], - }, - tooltip: "Sexualn't", - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + // Oct 24-30 + name: 'Ace Week', + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Oct-24'), new Date('2021-Oct-31'), currentDate), + sharedProps: { + circleFill: '#333', + circleOuterFill: palette.taupe200, + typeFill: '#CCC', + stripes: ['#84067C', palette.grey600, '#CCCCCC', palette.grey100], + }, + tooltip: "Sexualn't", + Logomark: (props: DynamicLogoProps) => , + Logotype: (props: DynamicLogoProps) => , }; export const Birthday: Variant = { - // Jan 15 - name: "Roleypoly's Birthday", - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Jan-15'), new Date('2021-Jan-16'), currentDate), - sharedProps: { - circleFill: 'none', - circleOuterFill: palette.taupe300, - typeFill: palette.taupe500, - }, - tooltip: '🎉 HAPPY BIRTHDAY ROLEYPOLY 🎉', - Logomark: (props: DynamicLogoProps) => ( - - - + // Jan 15 + name: "Roleypoly's Birthday", + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Jan-15'), new Date('2021-Jan-16'), currentDate), + sharedProps: { + circleFill: 'none', + circleOuterFill: palette.taupe300, + typeFill: palette.taupe500, + }, + tooltip: '🎉 HAPPY BIRTHDAY ROLEYPOLY 🎉', + Logomark: (props: DynamicLogoProps) => ( + + + - - - - - - - - - - - - - - ), - Logotype: (props: DynamicLogoProps) => ( -
- - - -
- ), + + + + + + + + + + + + + + ), + Logotype: (props: DynamicLogoProps) => ( +
+ + + +
+ ), }; export const DevilsLettuce: Variant = { - name: 'Meme #1', - // Apr 20 - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Apr-20'), new Date('2021-Apr-21'), currentDate), - sharedProps: { - circleFill: palette.green400, - circleOuterFill: palette.green200, - typeFill: palette.green400, - }, - tooltip: 'Legalize it.', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + name: 'Meme #1', + // Apr 20 + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Apr-20'), new Date('2021-Apr-21'), currentDate), + sharedProps: { + circleFill: palette.green400, + circleOuterFill: palette.green200, + typeFill: palette.green400, + }, + tooltip: 'Legalize it.', + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( + + ), }; export const BicycleDay: Variant = { - name: 'Meme #2', - // Apr 19 - // TODO: hexagon is bestagon - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Apr-19'), new Date('2021-Apr-20'), currentDate), - sharedProps: { - circleFill: palette.gold400, - circleOuterFill: palette.taupe200, - typeFill: palette.discord400, - stripes: Object.values(palette), - }, - tooltip: 'It increases brain complexity.', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + name: 'Meme #2', + // Apr 19 + // TODO: hexagon is bestagon + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Apr-19'), new Date('2021-Apr-20'), currentDate), + sharedProps: { + circleFill: palette.gold400, + circleOuterFill: palette.taupe200, + typeFill: palette.discord400, + stripes: Object.values(palette), + }, + tooltip: 'It increases brain complexity.', + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( + + ), }; export const Christmas: Variant = { - name: 'Christmas!', - // Dec 20-27 - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Dec-20'), new Date('2021-Dec-28'), currentDate), - sharedProps: { - circleFill: palette.green200, - circleOuterFill: palette.red200, - typeFill: palette.green400, - stripes: [ - palette.grey600, - palette.red400, - palette.grey600, - palette.green400, - palette.grey600, - palette.red400, - palette.grey600, - palette.green400, - palette.grey600, - palette.red400, - palette.grey600, - ], - }, - tooltip: 'Have yourself a merry little Christmas~', - Logomark: (props: DynamicLogoProps) => ( - - - - ), - Logotype: (props: DynamicLogoProps) => ( - - - - ), + name: 'Christmas!', + // Dec 20-27 + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Dec-20'), new Date('2021-Dec-28'), currentDate), + sharedProps: { + circleFill: palette.green200, + circleOuterFill: palette.red200, + typeFill: palette.green400, + stripes: [ + palette.grey600, + palette.red400, + palette.grey600, + palette.green400, + palette.grey600, + palette.red400, + palette.grey600, + palette.green400, + palette.grey600, + palette.red400, + palette.grey600, + ], + }, + tooltip: 'Have yourself a merry little Christmas~', + Logomark: (props: DynamicLogoProps) => ( + + + + ), + Logotype: (props: DynamicLogoProps) => ( + + + + ), }; export const NewYear: Variant = { - name: "New Year's Day", - // Dec 30 - Jan 2 - // TODO: sparkle - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Dec-30'), new Date('2021-Jan-3'), currentDate), - sharedProps: { - circleFill: '#222', - circleOuterFill: palette.red400, - typeFill: '#aaa', - }, - tooltip: 'Fuck 2020. 🎆🎇🎆🎇', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( -
- - - -
- ), + name: "New Year's Day", + // Dec 30 - Jan 2 + // TODO: sparkle + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Dec-30'), new Date('2021-Jan-3'), currentDate), + sharedProps: { + circleFill: '#222', + circleOuterFill: palette.red400, + typeFill: '#aaa', + }, + tooltip: 'Fuck 2020. 🎆🎇🎆🎇', + Logomark: (props: DynamicLogoProps) => , + Logotype: (props: DynamicLogoProps) => ( +
+ + + +
+ ), }; export const LunarNewYear: Variant = { - name: 'Lunar New Year', - // Feb 12, 2021 - // Feb 1, 2022 - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Feb-10'), new Date('2021-Feb-13'), currentDate, true) || - matchDay(new Date('2022-Jan-30'), new Date('2022-Feb-3'), currentDate, true), - sharedProps: { - circleFill: palette.red200, - circleOuterFill: palette.gold400, - typeFill: palette.taupe300, - }, - tooltip: '恭喜发财! 🎊🎆🎇', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( -
- - - -
- ), + name: 'Lunar New Year', + // Feb 12, 2021 + // Feb 1, 2022 + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Feb-10'), new Date('2021-Feb-13'), currentDate, true) || + matchDay(new Date('2022-Jan-30'), new Date('2022-Feb-3'), currentDate, true), + sharedProps: { + circleFill: palette.red200, + circleOuterFill: palette.gold400, + typeFill: palette.taupe300, + }, + tooltip: '恭喜发财! 🎊🎆🎇', + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( +
+ + + +
+ ), }; export const Pride: Variant = { - name: 'LGBTQPOC Pride Month', - // June - activeIf: (currentDate?: Date) => - matchDay(new Date('2021-Jun-1'), new Date('2021-Jul-1'), currentDate), - sharedProps: { - circleOuterFill: palette.taupe200, - typeFill: palette.grey500, - stripes: [ - '#593BB5', - '#26B186', - '#FFC468', - '#F97C21', - '#F62C8B', - '#8B5950', - '#2D3234', - ], - }, - tooltip: 'LOVE WINS. 💖🌈🌟', - Logomark: (props: DynamicLogoProps) => ( - - ), - Logotype: (props: DynamicLogoProps) => ( - - ), + name: 'LGBTQPOC Pride Month', + // June + activeIf: (currentDate?: Date) => + matchDay(new Date('2021-Jun-1'), new Date('2021-Jul-1'), currentDate), + sharedProps: { + circleOuterFill: palette.taupe200, + typeFill: palette.grey500, + stripes: [ + '#593BB5', + '#26B186', + '#FFC468', + '#F97C21', + '#F62C8B', + '#8B5950', + '#2D3234', + ], + }, + tooltip: 'LOVE WINS. 💖🌈🌟', + Logomark: (props: DynamicLogoProps) => ( + + ), + Logotype: (props: DynamicLogoProps) => ( + + ), }; export const AllVariants: Variant[] = [ - Trans, - Pride, - Bi, - Lesbian, - Ace, - Birthday, - DevilsLettuce, - BicycleDay, - Christmas, - NewYear, - LunarNewYear, + Trans, + Pride, + Bi, + Lesbian, + Ace, + Birthday, + DevilsLettuce, + BicycleDay, + Christmas, + NewYear, + LunarNewYear, ]; diff --git a/packages/design-system/atoms/branding/FlagBranding.stories.tsx b/packages/design-system/atoms/branding/FlagBranding.stories.tsx index 478dbb5..7083877 100644 --- a/packages/design-system/atoms/branding/FlagBranding.stories.tsx +++ b/packages/design-system/atoms/branding/FlagBranding.stories.tsx @@ -1,12 +1,12 @@ import { LogoFlagProps, LogomarkFlag, LogotypeFlag } from './FlagBranding'; export default { - title: 'Atoms/Branding/Flags', - component: LogomarkFlag, - args: { - stripes: ['#F9238B', '#FB7B04', '#FFCA66', '#00B289', '#5A38B5', '#B413F5'], - height: 50, - }, + title: 'Atoms/Branding/Flags', + component: LogomarkFlag, + args: { + stripes: ['#F9238B', '#FB7B04', '#FFCA66', '#00B289', '#5A38B5', '#B413F5'], + height: 50, + }, }; export const logomarkFlag = (args: LogoFlagProps) => ; diff --git a/packages/design-system/atoms/branding/FlagBranding.tsx b/packages/design-system/atoms/branding/FlagBranding.tsx index 8d1e0c6..6ab915a 100644 --- a/packages/design-system/atoms/branding/FlagBranding.tsx +++ b/packages/design-system/atoms/branding/FlagBranding.tsx @@ -3,190 +3,190 @@ import { palette } from '../colors'; import { LogoProps } from './Branding'; export type LogoFlagProps = LogoProps & { - stripes: string[]; + stripes: string[]; }; export const generateStripes = (stripes: string[]) => { - const barWidth = 100 / stripes.length; - return ( - - {stripes.map((stripeFill, idx) => ( - - ))} - - ); + const barWidth = 100 / stripes.length; + return ( + + {stripes.map((stripeFill, idx) => ( + + ))} + + ); }; export const LogomarkFlag = (props: LogoFlagProps) => ( - - - - - - - - - - - - {generateStripes(props.stripes)} - - - + + + + + + + + + + + + {generateStripes(props.stripes)} + - - - - - - + + + + + + + + ); export const LogotypeFlag = (props: LogoFlagProps) => ( - - - - - - - - - - - - - - - - - - - - - - - {generateStripes(props.stripes)} - - - + + + + + + + + + + + + + + + + + + + + + + + {generateStripes(props.stripes)} + - - - - - - + + + + + + + + ); diff --git a/packages/design-system/atoms/breakpoints/BreakpointProvider.tsx b/packages/design-system/atoms/breakpoints/BreakpointProvider.tsx index f9a461a..f092144 100644 --- a/packages/design-system/atoms/breakpoints/BreakpointProvider.tsx +++ b/packages/design-system/atoms/breakpoints/BreakpointProvider.tsx @@ -3,66 +3,64 @@ import { mediaQueryDefs } from './Breakpoints'; import { BreakpointContext, ScreenSize } from './Context'; const resetScreen: ScreenSize = { - onSmallScreen: false, - onTablet: false, - onDesktop: false, + onSmallScreen: false, + onTablet: false, + onDesktop: false, }; export class BreakpointsProvider extends React.Component<{}, ScreenSize> { - public state = { - ...resetScreen, - onSmallScreen: true, - }; + public state = { + ...resetScreen, + onSmallScreen: true, + }; - private mediaQueries: { [key in keyof ScreenSize]: MediaQueryList } = { - onSmallScreen: window.matchMedia( - mediaQueryDefs.onSmallScreen.replace('@media screen and', '') - ), - onTablet: window.matchMedia( - mediaQueryDefs.onTablet.replace('@media screen and', '') - ), - onDesktop: window.matchMedia( - mediaQueryDefs.onDesktop.replace('@media screen and', '') - ), - }; + private mediaQueries: { [key in keyof ScreenSize]: MediaQueryList } = { + onSmallScreen: window.matchMedia( + mediaQueryDefs.onSmallScreen.replace('@media screen and', '') + ), + onTablet: window.matchMedia(mediaQueryDefs.onTablet.replace('@media screen and', '')), + onDesktop: window.matchMedia( + mediaQueryDefs.onDesktop.replace('@media screen and', '') + ), + }; - componentDidMount() { - Object.entries(this.mediaQueries).forEach(([key, mediaQuery]) => - mediaQuery.addEventListener('change', this.handleMediaEvent) - ); + componentDidMount() { + Object.entries(this.mediaQueries).forEach(([key, mediaQuery]) => + mediaQuery.addEventListener('change', this.handleMediaEvent) + ); + } + + componentWillUnmount() { + Object.entries(this.mediaQueries).forEach(([key, mediaQuery]) => + mediaQuery.removeEventListener('change', this.handleMediaEvent) + ); + } + + handleMediaEvent = (event: MediaQueryListEvent) => { + console.log('handleMediaEvent', { event }); + this.setState({ + ...resetScreen, + ...this.calculateScreen(), + }); + }; + + calculateScreen = () => { + if (this.mediaQueries.onDesktop.matches) { + return { onDesktop: true }; } - componentWillUnmount() { - Object.entries(this.mediaQueries).forEach(([key, mediaQuery]) => - mediaQuery.removeEventListener('change', this.handleMediaEvent) - ); + if (this.mediaQueries.onTablet.matches) { + return { onTablet: true }; } - handleMediaEvent = (event: MediaQueryListEvent) => { - console.log('handleMediaEvent', { event }); - this.setState({ - ...resetScreen, - ...this.calculateScreen(), - }); - }; + return { onSmallScreen: true }; + }; - calculateScreen = () => { - if (this.mediaQueries.onDesktop.matches) { - return { onDesktop: true }; - } - - if (this.mediaQueries.onTablet.matches) { - return { onTablet: true }; - } - - return { onSmallScreen: true }; - }; - - render() { - return ( - - {this.props.children} - - ); - } + render() { + return ( + + {this.props.children} + + ); + } } diff --git a/packages/design-system/atoms/breakpoints/Breakpoints.stories.tsx b/packages/design-system/atoms/breakpoints/Breakpoints.stories.tsx index 9ae9ff4..2281868 100644 --- a/packages/design-system/atoms/breakpoints/Breakpoints.stories.tsx +++ b/packages/design-system/atoms/breakpoints/Breakpoints.stories.tsx @@ -3,9 +3,9 @@ import { BreakpointsProvider } from './BreakpointProvider'; import { BreakpointDebugTool } from './DebugTool'; export default { - title: 'Atoms/Breakpoints', - decorators: [(story) => {story()}], - component: BreakpointDebugTool, + title: 'Atoms/Breakpoints', + decorators: [(story) => {story()}], + component: BreakpointDebugTool, }; export const DebugTool = () => ; diff --git a/packages/design-system/atoms/breakpoints/Breakpoints.ts b/packages/design-system/atoms/breakpoints/Breakpoints.ts index 5b0ee40..f4d6640 100644 --- a/packages/design-system/atoms/breakpoints/Breakpoints.ts +++ b/packages/design-system/atoms/breakpoints/Breakpoints.ts @@ -1,16 +1,16 @@ export const breakpoints = { - onTablet: 768, - onDesktop: 1024, + onTablet: 768, + onDesktop: 1024, }; export const mediaQueryDefs = { - onSmallScreen: `@media screen and (max-width: ${breakpoints.onTablet - 1}px)`, - onTablet: `@media screen and (min-width: ${breakpoints.onTablet}px)`, - onDesktop: `@media screen and (min-width: ${breakpoints.onDesktop}px)`, + onSmallScreen: `@media screen and (max-width: ${breakpoints.onTablet - 1}px)`, + onTablet: `@media screen and (min-width: ${breakpoints.onTablet}px)`, + onDesktop: `@media screen and (min-width: ${breakpoints.onDesktop}px)`, }; export const onTablet = (...expressions: any) => { - return ` + return ` ${mediaQueryDefs.onTablet} { ${expressions.join()} } @@ -18,7 +18,7 @@ export const onTablet = (...expressions: any) => { }; export const onDesktop = (...expressions: any) => { - return ` + return ` ${mediaQueryDefs.onDesktop} { ${expressions.join()} } @@ -26,7 +26,7 @@ export const onDesktop = (...expressions: any) => { }; export const onSmallScreen = (...expressions: any) => { - return ` + return ` ${mediaQueryDefs.onSmallScreen} { ${expressions.join()} } diff --git a/packages/design-system/atoms/breakpoints/Context.ts b/packages/design-system/atoms/breakpoints/Context.ts index 33c610a..7227459 100644 --- a/packages/design-system/atoms/breakpoints/Context.ts +++ b/packages/design-system/atoms/breakpoints/Context.ts @@ -2,21 +2,21 @@ import { withContext } from '@roleypoly/misc-utils/withContext'; import * as React from 'react'; export type ScreenSize = { - onSmallScreen: boolean; - onTablet: boolean; - onDesktop: boolean; + onSmallScreen: boolean; + onTablet: boolean; + onDesktop: boolean; }; export type BreakpointProps = { - screenSize: ScreenSize; + screenSize: ScreenSize; }; const defaultScreenSize: BreakpointProps = { - screenSize: { - onSmallScreen: true, - onDesktop: false, - onTablet: false, - }, + screenSize: { + onSmallScreen: true, + onDesktop: false, + onTablet: false, + }, }; export const BreakpointContext = React.createContext(defaultScreenSize); @@ -24,4 +24,4 @@ export const BreakpointContext = React.createContext(defaultScreenSize); export const useBreakpointContext = () => React.useContext(BreakpointContext); export const withBreakpoints = (Component: React.ComponentType) => - withContext(BreakpointContext, Component as any); + withContext(BreakpointContext, Component as any); diff --git a/packages/design-system/atoms/breakpoints/DebugTool.tsx b/packages/design-system/atoms/breakpoints/DebugTool.tsx index 9e6cd84..9c1597c 100644 --- a/packages/design-system/atoms/breakpoints/DebugTool.tsx +++ b/packages/design-system/atoms/breakpoints/DebugTool.tsx @@ -4,53 +4,53 @@ import { onDesktop, onTablet } from './Breakpoints'; import { useBreakpointContext } from './Context'; const DebuggerPosition = styled.div` - position: fixed; - top: 0; - left: 0; - font-family: monospace; - & > div { - display: flex; - } + position: fixed; + top: 0; + left: 0; + font-family: monospace; + & > div { + display: flex; + } `; const OnSmallScreen = styled.div` - display: block; + display: block; `; const OnTablet = styled.div` - display: none; - ${onTablet(`display: block;`)} + display: none; + ${onTablet(`display: block;`)} `; const OnDesktop = styled.div` - display: none; - ${onDesktop`display: block;`} + display: none; + ${onDesktop`display: block;`} `; const CSSBreakpointDebugger = () => ( -
- S - T - D -
+
+ S + T + D +
); const JSBreakpointDebugger = () => { - const { - screenSize: { onTablet, onDesktop, onSmallScreen }, - } = useBreakpointContext(); + const { + screenSize: { onTablet, onDesktop, onSmallScreen }, + } = useBreakpointContext(); - return ( -
- {onSmallScreen &&
S
} - {onTablet &&
T
} - {onDesktop &&
D
} -
- ); + return ( +
+ {onSmallScreen &&
S
} + {onTablet &&
T
} + {onDesktop &&
D
} +
+ ); }; export const BreakpointDebugTool = () => ( - - - - + + + + ); diff --git a/packages/design-system/atoms/button/Button.spec.tsx b/packages/design-system/atoms/button/Button.spec.tsx index 749c42a..217f188 100644 --- a/packages/design-system/atoms/button/Button.spec.tsx +++ b/packages/design-system/atoms/button/Button.spec.tsx @@ -3,9 +3,9 @@ import * as React from 'react'; import { Button } from './Button'; it('fires an onClick callback when clicked', () => { - const mock = jest.fn(); - const view = shallow(); + const mock = jest.fn(); + const view = shallow(); - view.simulate('click'); - expect(mock).toBeCalled(); + view.simulate('click'); + expect(mock).toBeCalled(); }); diff --git a/packages/design-system/atoms/button/Button.stories.tsx b/packages/design-system/atoms/button/Button.stories.tsx index cb46e35..153a661 100644 --- a/packages/design-system/atoms/button/Button.stories.tsx +++ b/packages/design-system/atoms/button/Button.stories.tsx @@ -2,24 +2,24 @@ import * as React from 'react'; import { Button as ButtonComponent } from './Button'; export default { - title: 'Atoms/Button', - component: ButtonComponent, - argTypes: { - content: { control: 'text' }, - }, - args: { - content: 'Press me!', - size: 'large', - }, + title: 'Atoms/Button', + component: ButtonComponent, + argTypes: { + content: { control: 'text' }, + }, + args: { + content: 'Press me!', + size: 'large', + }, }; export const Large = ({ content, ...args }) => ( - {content} + {content} ); export const Small = ({ content, ...args }) => ( - {content} + {content} ); Small.args = { - size: 'small', + size: 'small', }; diff --git a/packages/design-system/atoms/button/Button.styled.ts b/packages/design-system/atoms/button/Button.styled.ts index 0b73250..26d3247 100644 --- a/packages/design-system/atoms/button/Button.styled.ts +++ b/packages/design-system/atoms/button/Button.styled.ts @@ -4,105 +4,105 @@ import { text300, text400 } from '@roleypoly/design-system/atoms/typography'; import styled, { css } from 'styled-components'; export const IconContainer = styled.div` - margin-right: 0.6rem; - font-size: 1.75em; + margin-right: 0.6rem; + font-size: 1.75em; `; const base = css` - ${fontCSS} + ${fontCSS} - appearance: none; - display: block; - background-color: ${palette.taupe300}; - color: ${palette.grey500}; - border-radius: 3px; - border: 2px solid rgba(0, 0, 0, 0.55); + appearance: none; + display: block; + background-color: ${palette.taupe300}; + color: ${palette.grey500}; + border-radius: 3px; + border: 2px solid rgba(0, 0, 0, 0.55); + transition: all 0.15s ease-in-out; + outline: 0; + position: relative; + user-select: none; + cursor: pointer; + white-space: nowrap; + + ::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: #000; + opacity: 0; transition: all 0.15s ease-in-out; - outline: 0; - position: relative; - user-select: none; - cursor: pointer; - white-space: nowrap; + } + :hover { + transform: translateY(-1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); + } + + :active { + transform: translateY(1px); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.25); ::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: #000; - opacity: 0; - transition: all 0.15s ease-in-out; - } - - :hover { - transform: translateY(-1px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); - } - - :active { - transform: translateY(1px); - box-shadow: 0 0 2px rgba(0, 0, 0, 0.25); - ::after { - opacity: 0.1; - } + opacity: 0.1; } + } `; const colors = { - primary: css` - background-color: ${palette.green400}; - color: ${palette.taupe100}; - `, - secondary: css``, - discord: css` - background-color: ${palette.discord400}; - border: 2px solid ${palette.discord200}; - `, - muted: css` - border: 2px solid rgba(0, 0, 0, 0.15); - background: none; - :hover { - background-color: ${palette.taupe200}; - } - `, + primary: css` + background-color: ${palette.green400}; + color: ${palette.taupe100}; + `, + secondary: css``, + discord: css` + background-color: ${palette.discord400}; + border: 2px solid ${palette.discord200}; + `, + muted: css` + border: 2px solid rgba(0, 0, 0, 0.15); + background: none; + :hover { + background-color: ${palette.taupe200}; + } + `, }; const sizes = { - small: css` - ${text300} + small: css` + ${text300} - padding: 4px 8px; - `, - large: css` - ${text400} + padding: 4px 8px; + `, + large: css` + ${text400} - padding: 12px 32px; - width: 100%; - `, + padding: 12px 32px; + width: 100%; + `, }; const modifiers = { - withIcon: css` - display: flex; - align-items: center; - justify-content: center; - `, - withLoading: css` - pointer-events: none; - `, + withIcon: css` + display: flex; + align-items: center; + justify-content: center; + `, + withLoading: css` + pointer-events: none; + `, }; export type ButtonComposerOptions = { - size: keyof typeof sizes; - color: keyof typeof colors; - modifiers?: Array; + size: keyof typeof sizes; + color: keyof typeof colors; + modifiers?: Array; }; export const Button = styled.button` - ${base} - ${(props) => props.size in sizes && sizes[props.size]} + ${base} + ${(props) => props.size in sizes && sizes[props.size]} ${(props) => props.color in colors && colors[props.color]} ${(props) => props.modifiers?.map((m) => modifiers[m])} `; diff --git a/packages/design-system/atoms/button/Button.tsx b/packages/design-system/atoms/button/Button.tsx index a08a7ed..e48d1c9 100644 --- a/packages/design-system/atoms/button/Button.tsx +++ b/packages/design-system/atoms/button/Button.tsx @@ -1,38 +1,38 @@ import * as React from 'react'; import { - Button as StyledButton, - ButtonComposerOptions, - IconContainer, + Button as StyledButton, + ButtonComposerOptions, + IconContainer, } from './Button.styled'; export type ButtonProps = Partial & { - children: React.ReactNode; - icon?: React.ReactNode; - loading?: boolean; - onClick?: () => void; - disabled?: boolean; + children: React.ReactNode; + icon?: React.ReactNode; + loading?: boolean; + onClick?: () => void; + disabled?: boolean; }; export const Button = (props: ButtonProps) => { - const modifiers: ButtonProps['modifiers'] = []; - if (props.loading) { - modifiers.push('withLoading'); - } + const modifiers: ButtonProps['modifiers'] = []; + if (props.loading) { + modifiers.push('withLoading'); + } - if (props.icon) { - modifiers.push('withIcon'); - } + if (props.icon) { + modifiers.push('withIcon'); + } - return ( - - {props.icon && {props.icon}} -
{props.children}
-
- ); + return ( + + {props.icon && {props.icon}} +
{props.children}
+
+ ); }; diff --git a/packages/design-system/atoms/collapse/Collapse.stories.tsx b/packages/design-system/atoms/collapse/Collapse.stories.tsx index 70611b0..ca45949 100644 --- a/packages/design-system/atoms/collapse/Collapse.stories.tsx +++ b/packages/design-system/atoms/collapse/Collapse.stories.tsx @@ -2,12 +2,12 @@ import { SmallTitle } from '@roleypoly/design-system/atoms/typography'; import { Collapse } from './Collapse'; export default { - title: 'Atoms/Collapse', - component: Collapse, + title: 'Atoms/Collapse', + component: Collapse, }; export const collapse = (args) => ( - - Hello, small world! - + + Hello, small world! + ); diff --git a/packages/design-system/atoms/collapse/Collapse.tsx b/packages/design-system/atoms/collapse/Collapse.tsx index 3c25bcb..7fd2e6a 100644 --- a/packages/design-system/atoms/collapse/Collapse.tsx +++ b/packages/design-system/atoms/collapse/Collapse.tsx @@ -2,9 +2,9 @@ import styled, { css } from 'styled-components'; import { onSmallScreen } from '../breakpoints'; export const Collapse = styled.span<{ preventCollapse?: boolean }>` - ${(props) => - !props.preventCollapse && - onSmallScreen(css` - display: none; - `)} + ${(props) => + !props.preventCollapse && + onSmallScreen(css` + display: none; + `)} `; diff --git a/packages/design-system/atoms/colors/colors.stories.tsx b/packages/design-system/atoms/colors/colors.stories.tsx index 3dd591f..da6e138 100644 --- a/packages/design-system/atoms/colors/colors.stories.tsx +++ b/packages/design-system/atoms/colors/colors.stories.tsx @@ -5,158 +5,158 @@ import styled from 'styled-components'; import { palette } from './colors'; type RatioList = { - color1: string[]; - color2: string[]; - ratio: string; + color1: string[]; + color2: string[]; + ratio: string; }; export default { - title: 'Atoms/Colors', + title: 'Atoms/Colors', }; const Swatch = styled.div` - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); - width: 250px; - height: 100px; - margin: 10px; - display: inline-block; - background-color: #fff; - border: 1px solid #fff; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); + width: 250px; + height: 100px; + margin: 10px; + display: inline-block; + background-color: #fff; + border: 1px solid #fff; `; const SwatchColor = styled.div` - height: 72px; + height: 72px; `; const Label = styled.div` - font-size: 12px; - display: flex; - justify-content: space-between; - padding: 6px; - color: ${palette.taupe100}; - p { - margin: 0; - } + font-size: 12px; + display: flex; + justify-content: space-between; + padding: 6px; + color: ${palette.taupe100}; + p { + margin: 0; + } `; export const Colors = () => { - return ( -
- {Object.entries(palette).map(([name, color], i) => ( - - - - - ))} -
- ); + return ( +
+ {Object.entries(palette).map(([name, color], i) => ( + + + + + ))} +
+ ); }; export const ContrastRatios = () => { - const allRatios = getAllRatios(palette); + const allRatios = getAllRatios(palette); - return ( -
-

- WCAG Contrast Calculations. -
- Marked in Green is 7.0+ or AAA. - Acceptable for Text. -
- Marked in Orange is 4.5+ or AA. - Acceptable for UI. -
- All below 4.5 is unacceptable. -
- WCAG Contrast testing disabled for this page. -

- - - - Swatch - Ratio - Color 1 - Color 2 - - - - {allRatios.map((ratio, i) => ( - -   -   - {ratio.ratio} - {ratio.color1[0]} - {ratio.color2[0]} - - oh my god my - - - shin how dare you - - - ))} - - -
- ); + return ( +
+

+ WCAG Contrast Calculations. +
+ Marked in Green is 7.0+ or AAA. Acceptable + for Text. +
+ Marked in Orange is 4.5+ or AA. Acceptable + for UI. +
+ All below 4.5 is unacceptable. +
+ WCAG Contrast testing disabled for this page. +

+ + + + Swatch + Ratio + Color 1 + Color 2 + + + + {allRatios.map((ratio, i) => ( + +   +   + {ratio.ratio} + {ratio.color1[0]} + {ratio.color2[0]} + + oh my god my + + + shin how dare you + + + ))} + + +
+ ); }; const ContrastTable = styled.table` - td, - th { - padding: 6px 10px; - } + td, + th { + padding: 6px 10px; + } `; const getWCAGStyle = (ratio: number): React.CSSProperties => { - if (ratio >= 7) { - return { color: 'green', fontWeight: 'bold' }; - } + if (ratio >= 7) { + return { color: 'green', fontWeight: 'bold' }; + } - if (ratio >= 4.5) { - return { color: 'orange', fontWeight: 'bold' }; - } + if (ratio >= 4.5) { + return { color: 'orange', fontWeight: 'bold' }; + } - return {}; + return {}; }; const getAllRatios = (input: typeof palette) => - Object.entries(input) - .filter(([name]) => !name.startsWith('discord')) - .reduce((acc, [name, color]) => { - return [ - ...acc, - ...Object.entries(palette) - .filter(([name]) => !name.startsWith('discord')) - .map(([matchName, matchColor]) => ({ - color1: [name, color], - color2: [matchName, matchColor], - ratio: chroma.contrast(color, matchColor).toFixed(2), - })), - ]; - }, [] as RatioList[]) - .filter(({ ratio }) => +ratio !== 1) - .sort((a, b) => { - if (+a.ratio > +b.ratio) { - return -1; - } - return 1; - }) - .filter((_, i) => i % 2 === 0); + Object.entries(input) + .filter(([name]) => !name.startsWith('discord')) + .reduce((acc, [name, color]) => { + return [ + ...acc, + ...Object.entries(palette) + .filter(([name]) => !name.startsWith('discord')) + .map(([matchName, matchColor]) => ({ + color1: [name, color], + color2: [matchName, matchColor], + ratio: chroma.contrast(color, matchColor).toFixed(2), + })), + ]; + }, [] as RatioList[]) + .filter(({ ratio }) => +ratio !== 1) + .sort((a, b) => { + if (+a.ratio > +b.ratio) { + return -1; + } + return 1; + }) + .filter((_, i) => i % 2 === 0); diff --git a/packages/design-system/atoms/colors/colors.tsx b/packages/design-system/atoms/colors/colors.tsx index 3546de0..1b07397 100644 --- a/packages/design-system/atoms/colors/colors.tsx +++ b/packages/design-system/atoms/colors/colors.tsx @@ -2,36 +2,36 @@ import chroma from 'chroma-js'; import { createGlobalStyle, css } from 'styled-components'; export const palette = { - taupe100: '#332D2D', - taupe200: '#453E3D', - taupe300: '#5D5352', - taupe400: '#756867', - taupe500: '#AB9B9A', - taupe600: '#EBD6D4', + taupe100: '#332D2D', + taupe200: '#453E3D', + taupe300: '#5D5352', + taupe400: '#756867', + taupe500: '#AB9B9A', + taupe600: '#EBD6D4', - discord100: '#23272A', - discord200: '#2C2F33', - discord400: '#7289DA', - discord500: '#99AAB5', + discord100: '#23272A', + discord200: '#2C2F33', + discord400: '#7289DA', + discord500: '#99AAB5', - green400: '#46B646', - green200: '#1D8227', + green400: '#46B646', + green200: '#1D8227', - red400: '#E95353', - red200: '#F14343', + red400: '#E95353', + red200: '#F14343', - gold400: '#EFCF24', + gold400: '#EFCF24', - grey100: '#1C1010', - grey500: '#DBD9D9', - grey600: '#F2EFEF', + grey100: '#1C1010', + grey500: '#DBD9D9', + grey600: '#F2EFEF', }; const getPaletteCSS = () => - Object.entries(palette).reduce( - (acc, [key, color]) => ({ ...acc, [`--${key}`]: color }), - {} - ); + Object.entries(palette).reduce( + (acc, [key, color]) => ({ ...acc, [`--${key}`]: color }), + {} + ); export const colorVars = css(getPaletteCSS()); @@ -42,5 +42,5 @@ export const GlobalStyleColors = createGlobalStyle` `; export const numberToChroma = (colorInt: number) => { - return chroma(colorInt); + return chroma(colorInt); }; diff --git a/packages/design-system/atoms/colors/withColors.tsx b/packages/design-system/atoms/colors/withColors.tsx index fc6aac3..331cf63 100644 --- a/packages/design-system/atoms/colors/withColors.tsx +++ b/packages/design-system/atoms/colors/withColors.tsx @@ -3,9 +3,9 @@ import styled from 'styled-components'; import { colorVars } from './colors'; const ColorsContainer = styled.div` - ${colorVars} + ${colorVars} `; export const withColors = (storyFn: () => React.ReactNode) => ( - {storyFn()} + {storyFn()} ); diff --git a/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx b/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx index f2b162e..4047561 100644 --- a/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx +++ b/packages/design-system/atoms/dot-overlay/DotOverlay.stories.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { DotOverlay } from './DotOverlay'; export default { - title: 'Atoms/Dot Overlay', + title: 'Atoms/Dot Overlay', }; export const Dark = () => ; diff --git a/packages/design-system/atoms/dot-overlay/DotOverlay.tsx b/packages/design-system/atoms/dot-overlay/DotOverlay.tsx index 5d68f73..1a84233 100644 --- a/packages/design-system/atoms/dot-overlay/DotOverlay.tsx +++ b/packages/design-system/atoms/dot-overlay/DotOverlay.tsx @@ -2,37 +2,37 @@ import * as React from 'react'; import styled from 'styled-components'; const dotOverlayBase = styled.div` - opacity: 0.6; - pointer-events: none; - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: -10; - background-size: 27px 27px; + opacity: 0.6; + pointer-events: none; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: -10; + background-size: 27px 27px; `; const DotOverlayDark = styled(dotOverlayBase)` - background-image: radial-gradient( - circle, - #332d2d, - #332d2d 1px, - transparent 1px, - transparent - ); + background-image: radial-gradient( + circle, + #332d2d, + #332d2d 1px, + transparent 1px, + transparent + ); `; const DotOverlayLight = styled(dotOverlayBase)` - background-image: radial-gradient( - circle, - #dbd9d9, - #dbd9d9 1px, - transparent 1px, - transparent - ); + background-image: radial-gradient( + circle, + #dbd9d9, + #dbd9d9 1px, + transparent 1px, + transparent + ); `; export const DotOverlay = ({ light }: { light?: boolean }) => { - return light ? : ; + return light ? : ; }; diff --git a/packages/design-system/atoms/fader/Fader.stories.tsx b/packages/design-system/atoms/fader/Fader.stories.tsx index b9bd46a..8bb8741 100644 --- a/packages/design-system/atoms/fader/Fader.stories.tsx +++ b/packages/design-system/atoms/fader/Fader.stories.tsx @@ -4,25 +4,25 @@ import * as React from 'react'; import { FaderOpacity, FaderSlide } from './Fader'; export default { - title: 'Atoms/Fader', - component: FaderSlide, - args: { - isVisible: true, - }, + title: 'Atoms/Fader', + component: FaderSlide, + args: { + isVisible: true, + }, }; export const Opacity = (args) => { - return ( - - - - ); + return ( + + + + ); }; export const Slide = (args) => { - return ( - - - - ); + return ( + + + + ); }; diff --git a/packages/design-system/atoms/fader/Fader.tsx b/packages/design-system/atoms/fader/Fader.tsx index 78d5843..f034ee5 100644 --- a/packages/design-system/atoms/fader/Fader.tsx +++ b/packages/design-system/atoms/fader/Fader.tsx @@ -2,34 +2,32 @@ import * as React from 'react'; import styled from 'styled-components'; export type FaderProps = { - isVisible: boolean; - children: React.ReactNode; + isVisible: boolean; + children: React.ReactNode; }; const FaderOpacityStyled = styled.div>` - opacity: ${(props) => (props.isVisible ? 1 : 0)}; - pointer-events: ${(props) => (props.isVisible ? 'unset' : 'none')}; - transition: opacity 0.35s ease-in-out; + opacity: ${(props) => (props.isVisible ? 1 : 0)}; + pointer-events: ${(props) => (props.isVisible ? 'unset' : 'none')}; + transition: opacity 0.35s ease-in-out; `; export const FaderOpacity = (props: FaderProps) => { - return ( - - {props.children} - - ); + return ( + {props.children} + ); }; const FaderSlideStyled = styled.div>` - max-height: ${(props) => (props.isVisible ? '4em' : '0')}; - pointer-events: ${(props) => (props.isVisible ? 'unset' : 'none')}; - transition: max-height 0.35s ease-in-out; - overflow: hidden; - transform: translateZ(0); + max-height: ${(props) => (props.isVisible ? '4em' : '0')}; + pointer-events: ${(props) => (props.isVisible ? 'unset' : 'none')}; + transition: max-height 0.35s ease-in-out; + overflow: hidden; + transform: translateZ(0); `; export const FaderSlide = (props: FaderProps) => { - return ( - {props.children} - ); + return ( + {props.children} + ); }; diff --git a/packages/design-system/atoms/feature-gate/FeatureGate.stories.tsx b/packages/design-system/atoms/feature-gate/FeatureGate.stories.tsx index d9b9dc5..8192467 100644 --- a/packages/design-system/atoms/feature-gate/FeatureGate.stories.tsx +++ b/packages/design-system/atoms/feature-gate/FeatureGate.stories.tsx @@ -3,14 +3,14 @@ import * as React from 'react'; import { FeatureGate } from './FeatureGate'; export default { - title: 'Atoms/Feature Gate', - decorators: [FeatureFlagDecorator(['AllowListBlockList'])], + title: 'Atoms/Feature Gate', + decorators: [FeatureFlagDecorator(['AllowListBlockList'])], }; export const ActiveGate = () => ( - {() =>
hello!
}
+ {() =>
hello!
}
); export const InactiveGate = () => ( - {() =>
hello!
}
+ {() =>
hello!
}
); diff --git a/packages/design-system/atoms/feature-gate/FeatureGate.tsx b/packages/design-system/atoms/feature-gate/FeatureGate.tsx index 2d50379..7ec26c5 100644 --- a/packages/design-system/atoms/feature-gate/FeatureGate.tsx +++ b/packages/design-system/atoms/feature-gate/FeatureGate.tsx @@ -1,20 +1,20 @@ import { - FeatureFlag, - FeatureFlagsContext, + FeatureFlag, + FeatureFlagsContext, } from '@roleypoly/misc-utils/featureFlags/react'; import * as React from 'react'; export type FeatureGateProps = { - featureFlag: FeatureFlag; - children: () => React.ReactNode; + featureFlag: FeatureFlag; + children: () => React.ReactNode; }; export const FeatureGate = (props: FeatureGateProps) => { - const featureContext = React.useContext(FeatureFlagsContext); + const featureContext = React.useContext(FeatureFlagsContext); - if (featureContext.has(props.featureFlag)) { - return props.children(); - } else { - return <>; - } + if (featureContext.has(props.featureFlag)) { + return props.children(); + } else { + return <>; + } }; diff --git a/packages/design-system/atoms/fonts/fonts.stories.tsx b/packages/design-system/atoms/fonts/fonts.stories.tsx index 9cc092c..0e3d7dc 100644 --- a/packages/design-system/atoms/fonts/fonts.stories.tsx +++ b/packages/design-system/atoms/fonts/fonts.stories.tsx @@ -1,6 +1,6 @@ import { - MediumTitle, - Text as TextBlock, + MediumTitle, + Text as TextBlock, } from '@roleypoly/design-system/atoms/typography'; import * as React from 'react'; import styled from 'styled-components'; @@ -9,54 +9,52 @@ import { UseFontStyled } from './fonts'; const resetFont = (storyFn: () => React.ReactNode) => {storyFn()}; export default { - title: 'Atoms/Fonts', - decorators: [resetFont], + title: 'Atoms/Fonts', + decorators: [resetFont], }; const FontReset = styled.div` - font-family: sans-serif; + font-family: sans-serif; `; const CorrectlyFontedH2 = (props: { children: React.ReactNode }) => ( - - {props.children} - + + {props.children} + ); const Text = () => ( - <> -

- Lorem ipsum dolor sit, amet consectetur adipisicing elit. Et facilis alias - placeat cumque sapiente ad delectus omnis quae. Reiciendis quibusdam deserunt - repellat. Exercitationem modi incidunt autem nemo tempore eaque soluta. -

-

- 帯カノ需混モイ一録43旧百12共ドレ能生ホクユ禁度ヨ材図クほはそ護関ラト郵張エノヨ議件クめざ県読れみとぶ論税クょンど慎転リつぎみ松期ほへド. - 縦投記ふで覧速っだせあ過先課フ演無ぎぱべ習併相ーす気6元ゆる領気希ぎ投代ラ我関レ森郎由系堂ず. - 読ケリ夜指ーっトせ認平引ウシ間花ヱクム年6台ぐ山婦ラスエ子著コア掲中ロ像属戸メソユ職諏ルど詐児題たに書希ク幕値長ラそめド. -

-

- 🔸🐕🔺💱🎊👽🐛 👨📼🕦📞 👱👆🍗👚🌈 🔝🔟🍉🔰🍲🏁🕗 🎡🐉🍲📻🔢🔄 💟💲🍻💜💩🔼 - 🎱🌸📛👫🌻 🗽🕜🐥👕🍈. 🐒🍚🔓📱🏦 🎦🌑🔛💙👣🔚 🔆🗻🌿🎳📲🍯 🌞💟🎌🍌 🔪📯🐎💮 - 👌👭🎋🏉🏰 📓🕃🎂💉🔩 🐟🌇👺🌊🌒 📪👅🍂🍁 🌖🐮🔽🌒📊. 🔤🍍🌸📷🎴 💏🍌📎👥👉👒 - 👝💜🔶🍣 💨🗼👈💉💉💰 🍐🕖🌰👝🕓🏊🐕 🏀📅📼📒 🐕🌈👋 -

- + <> +

+ Lorem ipsum dolor sit, amet consectetur adipisicing elit. Et facilis alias placeat + cumque sapiente ad delectus omnis quae. Reiciendis quibusdam deserunt repellat. + Exercitationem modi incidunt autem nemo tempore eaque soluta. +

+

+ 帯カノ需混モイ一録43旧百12共ドレ能生ホクユ禁度ヨ材図クほはそ護関ラト郵張エノヨ議件クめざ県読れみとぶ論税クょンど慎転リつぎみ松期ほへド. + 縦投記ふで覧速っだせあ過先課フ演無ぎぱべ習併相ーす気6元ゆる領気希ぎ投代ラ我関レ森郎由系堂ず. + 読ケリ夜指ーっトせ認平引ウシ間花ヱクム年6台ぐ山婦ラスエ子著コア掲中ロ像属戸メソユ職諏ルど詐児題たに書希ク幕値長ラそめド. +

+

+ 🔸🐕🔺💱🎊👽🐛 👨📼🕦📞 👱👆🍗👚🌈 🔝🔟🍉🔰🍲🏁🕗 🎡🐉🍲📻🔢🔄 💟💲🍻💜💩🔼 + 🎱🌸📛👫🌻 🗽🕜🐥👕🍈. 🐒🍚🔓📱🏦 🎦🌑🔛💙👣🔚 🔆🗻🌿🎳📲🍯 🌞💟🎌🍌 🔪📯🐎💮 + 👌👭🎋🏉🏰 📓🕃🎂💉🔩 🐟🌇👺🌊🌒 📪👅🍂🍁 🌖🐮🔽🌒📊. 🔤🍍🌸📷🎴 💏🍌📎👥👉👒 + 👝💜🔶🍣 💨🗼👈💉💉💰 🍐🕖🌰👝🕓🏊🐕 🏀📅📼📒 🐕🌈👋 +

+ ); export const Fonts = () => ( - -
- Unstyled Default - -
-
- - Main (Source Han Sans Japanese, Source Sans) - - - - -
-
+ +
+ Unstyled Default + +
+
+ Main (Source Han Sans Japanese, Source Sans) + + + +
+
); diff --git a/packages/design-system/atoms/fonts/fonts.tsx b/packages/design-system/atoms/fonts/fonts.tsx index fe15b8d..7b23501 100644 --- a/packages/design-system/atoms/fonts/fonts.tsx +++ b/packages/design-system/atoms/fonts/fonts.tsx @@ -1,10 +1,10 @@ import styled, { css } from 'styled-components'; export const fontCSS = css` - font-family: 'source-han-sans-japanese', 'Source Sans Pro', sans-serif, - 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !important; + font-family: 'source-han-sans-japanese', 'Source Sans Pro', sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !important; `; export const UseFontStyled = styled.div` - ${fontCSS} + ${fontCSS} `; diff --git a/packages/design-system/atoms/halfsies/Halfsies.stories.tsx b/packages/design-system/atoms/halfsies/Halfsies.stories.tsx index 4968426..5c3801d 100644 --- a/packages/design-system/atoms/halfsies/Halfsies.stories.tsx +++ b/packages/design-system/atoms/halfsies/Halfsies.stories.tsx @@ -2,12 +2,12 @@ import * as React from 'react'; import { HalfsiesContainer, HalfsiesItem } from './Halfsies'; export default { - title: 'Atoms/Halfsies', + title: 'Atoms/Halfsies', }; export const Container = () => ( - - Lefty doo - Righty doo - + + Lefty doo + Righty doo + ); diff --git a/packages/design-system/atoms/halfsies/Halfsies.tsx b/packages/design-system/atoms/halfsies/Halfsies.tsx index 385e096..2727722 100644 --- a/packages/design-system/atoms/halfsies/Halfsies.tsx +++ b/packages/design-system/atoms/halfsies/Halfsies.tsx @@ -2,16 +2,16 @@ import { onTablet } from '@roleypoly/design-system/atoms/breakpoints'; import styled, { css } from 'styled-components'; export const HalfsiesContainer = styled.div` - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: center; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; `; export const HalfsiesItem = styled.div` - box-sizing: border-box; - flex: 1 1 100%; - ${onTablet(css` - flex: 1 2 50%; - `)} + box-sizing: border-box; + flex: 1 1 100%; + ${onTablet(css` + flex: 1 2 50%; + `)} `; diff --git a/packages/design-system/atoms/hero/Hero.stories.tsx b/packages/design-system/atoms/hero/Hero.stories.tsx index b97ca96..bfe11a3 100644 --- a/packages/design-system/atoms/hero/Hero.stories.tsx +++ b/packages/design-system/atoms/hero/Hero.stories.tsx @@ -2,69 +2,69 @@ import * as React from 'react'; import { Hero as HeroComponent } from './Hero'; export default { - title: 'Atoms/Hero', - component: HeroComponent, - args: { - topSpacing: 75, - bottomSpacing: 25, - }, + title: 'Atoms/Hero', + component: HeroComponent, + args: { + topSpacing: 75, + bottomSpacing: 25, + }, }; export const Hero = ({ topSpacing, bottomSpacing }) => { - return ( - - -

This is it.

-
-
- ); + return ( + + +

This is it.

+
+
+ ); }; type WrapperProps = { - children: React.ReactNode; - topSpacing: number; - bottomSpacing: number; + children: React.ReactNode; + topSpacing: number; + bottomSpacing: number; }; const StoryWrapper = ({ topSpacing, bottomSpacing, ...props }: WrapperProps) => ( -
-
-
- topSpacing -
-
- bottomSpacing -
-
- {props.children} +
+
+
+ topSpacing +
+
+ bottomSpacing +
+ {props.children} +
); diff --git a/packages/design-system/atoms/hero/Hero.tsx b/packages/design-system/atoms/hero/Hero.tsx index 3e958ce..266e66c 100644 --- a/packages/design-system/atoms/hero/Hero.tsx +++ b/packages/design-system/atoms/hero/Hero.tsx @@ -2,29 +2,29 @@ import * as React from 'react'; import styled from 'styled-components'; type HeroContainerProps = { - topSpacing: number; - bottomSpacing: number; + topSpacing: number; + bottomSpacing: number; }; type HeroProps = Partial & { - children: React.ReactNode; + children: React.ReactNode; }; const HeroContainer = styled.div` - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - overflow-x: hidden; - min-height: calc(100vh - ${(props) => props.topSpacing + props.bottomSpacing}px); - margin-top: ${(props) => props.topSpacing}px; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + overflow-x: hidden; + min-height: calc(100vh - ${(props) => props.topSpacing + props.bottomSpacing}px); + margin-top: ${(props) => props.topSpacing}px; `; export const Hero = (props: HeroProps) => ( - - {props.children} - + + {props.children} + ); diff --git a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.stories.tsx b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.stories.tsx index a8a2c45..5288440 100644 --- a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.stories.tsx +++ b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.stories.tsx @@ -2,32 +2,32 @@ import * as React from 'react'; import { HorizontalSwitch } from './HorizontalSwitch'; export default { - title: 'Atoms/Horizontal Switch', - component: HorizontalSwitch, - args: { - items: ['true', 'false'], - value: 'true', - }, + title: 'Atoms/Horizontal Switch', + component: HorizontalSwitch, + args: { + items: ['true', 'false'], + value: 'true', + }, }; const Story = (args) => { - const [value, setValue] = React.useState(args.value); + const [value, setValue] = React.useState(args.value); - return ( - { - setValue(a); - args.onChange(a); - }} - /> - ); + return ( + { + setValue(a); + args.onChange(a); + }} + /> + ); }; export const Switch = Story.bind({}); export const SwitchThree = Story.bind({}); SwitchThree.args = { - items: ['aaa', 'bbb', 'ccc'], - value: 'aaa', + items: ['aaa', 'bbb', 'ccc'], + value: 'aaa', }; diff --git a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.styled.ts b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.styled.ts index 4a5351d..3aad5d0 100644 --- a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.styled.ts +++ b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.styled.ts @@ -3,21 +3,21 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled, { css } from 'styled-components'; export const Item = styled.div<{ selected: boolean }>` - padding: 10px; - box-sizing: border-box; - transition: background-color ease-in-out ${transitions.actionable}s; - ${(props) => - props.selected && - css` - background-color: ${palette.taupe300}; - `} + padding: 10px; + box-sizing: border-box; + transition: background-color ease-in-out ${transitions.actionable}s; + ${(props) => + props.selected && + css` + background-color: ${palette.taupe300}; + `} `; export const Wrapper = styled.div` - display: inline-flex; - user-select: none; - cursor: pointer; - border: 1px solid ${palette.taupe200}; - border-radius: calc(1em + 20px); - overflow: hidden; + display: inline-flex; + user-select: none; + cursor: pointer; + border: 1px solid ${palette.taupe200}; + border-radius: calc(1em + 20px); + overflow: hidden; `; diff --git a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.tsx b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.tsx index 42a4ff9..f0b2418 100644 --- a/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.tsx +++ b/packages/design-system/atoms/horizontal-switch/HorizontalSwitch.tsx @@ -2,27 +2,23 @@ import * as React from 'react'; import { Item, Wrapper } from './HorizontalSwitch.styled'; export type SwitchProps = { - items: string[]; - value: string; - onChange: (value: string) => void; + items: string[]; + value: string; + onChange: (value: string) => void; }; export const HorizontalSwitch = (props: SwitchProps) => { - const handleClick = (item: typeof props.value) => () => { - props.onChange?.(item); - }; + const handleClick = (item: typeof props.value) => () => { + props.onChange?.(item); + }; - return ( - - {props.items.map((item, idx) => ( - - {item} - - ))} - - ); + return ( + + {props.items.map((item, idx) => ( + + {item} + + ))} + + ); }; diff --git a/packages/design-system/atoms/key-events/KeyEvents.ts b/packages/design-system/atoms/key-events/KeyEvents.ts index f7e43c7..c762570 100644 --- a/packages/design-system/atoms/key-events/KeyEvents.ts +++ b/packages/design-system/atoms/key-events/KeyEvents.ts @@ -1,19 +1,19 @@ import { useEffect } from 'react'; export const globalOnKeyUp = ( - key: string[], - action: () => any, - isActive: boolean = true + key: string[], + action: () => any, + isActive: boolean = true ) => { - useEffect(() => { - const onKeyUp = (event: KeyboardEvent) => { - if (isActive && key.includes(event.key)) { - action(); - } - }; + useEffect(() => { + const onKeyUp = (event: KeyboardEvent) => { + if (isActive && key.includes(event.key)) { + action(); + } + }; - document.body.addEventListener('keyup', onKeyUp); + document.body.addEventListener('keyup', onKeyUp); - return () => document.body.removeEventListener('keyup', onKeyUp); - }, [key, action, isActive]); + return () => document.body.removeEventListener('keyup', onKeyUp); + }, [key, action, isActive]); }; diff --git a/packages/design-system/atoms/popover/Popover.stories.tsx b/packages/design-system/atoms/popover/Popover.stories.tsx index c61911c..6b9167b 100644 --- a/packages/design-system/atoms/popover/Popover.stories.tsx +++ b/packages/design-system/atoms/popover/Popover.stories.tsx @@ -3,32 +3,32 @@ import * as React from 'react'; import { Popover as PopoverComponent } from './Popover'; export default { - title: 'Atoms/Popover', - argTypes: { - canDefocus: { control: 'boolean' }, - }, - args: { - canDefocus: true, - }, + title: 'Atoms/Popover', + argTypes: { + canDefocus: { control: 'boolean' }, + }, + args: { + canDefocus: true, + }, }; export const Popover = ({ canDefocus }) => { - const [isOpen, setIsOpen] = React.useState(false); + const [isOpen, setIsOpen] = React.useState(false); - return ( -
- - setIsOpen(false)} - canDefocus={canDefocus} - headContent={<>Hello c:} - > - stuff - -
- ); + return ( +
+ + setIsOpen(false)} + canDefocus={canDefocus} + headContent={<>Hello c:} + > + stuff + +
+ ); }; diff --git a/packages/design-system/atoms/popover/Popover.styled.ts b/packages/design-system/atoms/popover/Popover.styled.ts index 8210e46..6fa9623 100644 --- a/packages/design-system/atoms/popover/Popover.styled.ts +++ b/packages/design-system/atoms/popover/Popover.styled.ts @@ -4,87 +4,87 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled, { css } from 'styled-components'; type PopoverStyledProps = { - active: boolean; - preferredWidth?: number; + active: boolean; + preferredWidth?: number; }; export const PopoverBase = styled.div` - box-sizing: border-box; - position: absolute; - background-color: ${palette.taupe100}; - padding: 5px; - border: 2px solid rgba(0, 0, 0, 0.15); - border-radius: 3px; - z-index: 10; - transition: opacity ${transitions.out2in}s ease-in, - transform ${transitions.out2in}s ease-in; - min-width: ${(props) => props.preferredWidth || 320}px; - max-width: 100vw; - ${(props) => - !props.active && - css` - transform: translateY(-2vh); - opacity: 0; - pointer-events: none; - `} - ${onSmallScreen( - css` - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - min-width: unset; - width: 100vw; - height: 100vh; - ` - )}; + box-sizing: border-box; + position: absolute; + background-color: ${palette.taupe100}; + padding: 5px; + border: 2px solid rgba(0, 0, 0, 0.15); + border-radius: 3px; + z-index: 10; + transition: opacity ${transitions.out2in}s ease-in, + transform ${transitions.out2in}s ease-in; + min-width: ${(props) => props.preferredWidth || 320}px; + max-width: 100vw; + ${(props) => + !props.active && + css` + transform: translateY(-2vh); + opacity: 0; + pointer-events: none; + `} + ${onSmallScreen( + css` + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + min-width: unset; + width: 100vw; + height: 100vh; + ` + )}; `; export const DefocusHandler = styled.div` - background-color: rgba(0, 0, 0, 0.01); - position: fixed; - z-index: -1; - top: 0; - bottom: 0; - left: 0; - right: 0; - ${(props) => - !props.active && - css` - display: none; - pointer-events: none; - `} + background-color: rgba(0, 0, 0, 0.01); + position: fixed; + z-index: -1; + top: 0; + bottom: 0; + left: 0; + right: 0; + ${(props) => + !props.active && + css` + display: none; + pointer-events: none; + `} `; export const PopoverHead = styled.div` - display: flex; - align-items: center; + display: flex; + align-items: center; `; export const PopoverHeadCloser = styled.div` - flex: 0; - font-size: 2em; - cursor: pointer; - margin-right: 10px; - border-radius: 2em; - min-width: 1.4em; - height: 1.4em; - display: flex; - align-items: center; - justify-content: center; - ${onTablet( - css` - display: none; - ` - )} + flex: 0; + font-size: 2em; + cursor: pointer; + margin-right: 10px; + border-radius: 2em; + min-width: 1.4em; + height: 1.4em; + display: flex; + align-items: center; + justify-content: center; + ${onTablet( + css` + display: none; + ` + )} - &:hover { - background: rgba(0, 0, 0, 0.1); - } + &:hover { + background: rgba(0, 0, 0, 0.1); + } `; export const PopoverContent = styled.div` - padding: 5px; - overflow-y: hidden; + padding: 5px; + overflow-y: hidden; `; diff --git a/packages/design-system/atoms/popover/Popover.tsx b/packages/design-system/atoms/popover/Popover.tsx index 579e0e3..a283e49 100644 --- a/packages/design-system/atoms/popover/Popover.tsx +++ b/packages/design-system/atoms/popover/Popover.tsx @@ -2,42 +2,39 @@ import { globalOnKeyUp } from '@roleypoly/design-system/atoms/key-events'; import * as React from 'react'; import { IoMdClose } from 'react-icons/io'; import { - DefocusHandler, - PopoverBase, - PopoverContent, - PopoverHead, - PopoverHeadCloser, + DefocusHandler, + PopoverBase, + PopoverContent, + PopoverHead, + PopoverHeadCloser, } from './Popover.styled'; type PopoverProps = { - children: () => React.ReactNode; - position: 'top left' | 'top right' | 'bottom left' | 'bottom right'; - active: boolean; - canDefocus?: boolean; - onExit?: (type: 'escape' | 'defocus' | 'explicit') => void; - headContent: React.ReactNode; - preferredWidth?: number; + children: () => React.ReactNode; + position: 'top left' | 'top right' | 'bottom left' | 'bottom right'; + active: boolean; + canDefocus?: boolean; + onExit?: (type: 'escape' | 'defocus' | 'explicit') => void; + headContent: React.ReactNode; + preferredWidth?: number; }; export const Popover = (props: PopoverProps) => { - globalOnKeyUp(['Escape'], () => props.onExit?.('escape'), props.active); - return ( - <> - - - props.onExit?.('explicit')}> - - -
{props.headContent}
-
- {props.children()} -
- {props.canDefocus && ( - props.onExit?.('defocus')} - /> - )} - - ); + globalOnKeyUp(['Escape'], () => props.onExit?.('escape'), props.active); + return ( + <> + + + props.onExit?.('explicit')}> + + +
{props.headContent}
+
+ {props.children()} +
+ {props.canDefocus && ( + props.onExit?.('defocus')} /> + )} + + ); }; diff --git a/packages/design-system/atoms/role/Role.spec.tsx b/packages/design-system/atoms/role/Role.spec.tsx index f039507..0169086 100644 --- a/packages/design-system/atoms/role/Role.spec.tsx +++ b/packages/design-system/atoms/role/Role.spec.tsx @@ -4,10 +4,10 @@ import { roleCategory } from '../../fixtures/storyData'; import { Role } from './Role'; it('fires an OnClick handler when clicked', () => { - const onClickMock = jest.fn(); - const view = shallow( - - ); - view.simulate('click'); - expect(onClickMock).toBeCalledWith(false); + const onClickMock = jest.fn(); + const view = shallow( + + ); + view.simulate('click'); + expect(onClickMock).toBeCalledWith(false); }); diff --git a/packages/design-system/atoms/role/Role.stories.tsx b/packages/design-system/atoms/role/Role.stories.tsx index ab9b9fe..1d52282 100644 --- a/packages/design-system/atoms/role/Role.stories.tsx +++ b/packages/design-system/atoms/role/Role.stories.tsx @@ -5,75 +5,65 @@ import { roleCategory } from '../../fixtures/storyData'; import { Role as RoleComponent } from './Role'; export default { - title: 'Atoms/Role', - component: RoleComponent, - decorators: [withColors], + title: 'Atoms/Role', + component: RoleComponent, + decorators: [withColors], }; const Demo = styled.div` - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; `; const RoleWithState = (props: any) => { - const [selected, updateSelected] = React.useState(false); - return ( -
- updateSelected(next)} - /> -
- ); + const [selected, updateSelected] = React.useState(false); + return ( +
+ updateSelected(next)} + /> +
+ ); }; export const Role = () => ( - - {roleCategory.map((c, idx) => ( - - ))} - + + {roleCategory.map((c, idx) => ( + + ))} + ); export const Selected = () => ( - - {roleCategory.map((c, idx) => ( - - ))} - + + {roleCategory.map((c, idx) => ( + + ))} + ); export const Unselected = () => ( - - {roleCategory.map((c, idx) => ( - - ))} - + + {roleCategory.map((c, idx) => ( + + ))} + ); export const DisabledByPosition = () => ( - - {roleCategory.map((c, idx) => ( - - ))} - + + {roleCategory.map((c, idx) => ( + + ))} + ); export const DisabledByDanger = () => ( - - {roleCategory.map((c, idx) => ( - - ))} - + + {roleCategory.map((c, idx) => ( + + ))} + ); diff --git a/packages/design-system/atoms/role/Role.styled.tsx b/packages/design-system/atoms/role/Role.styled.tsx index dee7ef2..cc00b95 100644 --- a/packages/design-system/atoms/role/Role.styled.tsx +++ b/packages/design-system/atoms/role/Role.styled.tsx @@ -3,80 +3,80 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled, { css } from 'styled-components'; export type StyledProps = { - selected: boolean; - defaultColor: boolean; - disabled: boolean; - type?: 'delete'; + selected: boolean; + defaultColor: boolean; + disabled: boolean; + type?: 'delete'; }; export const Circle = styled.div` - width: 24px; - height: 24px; - border-radius: 25px; - background-color: ${(props) => - props.defaultColor && !props.selected ? 'transparent' : 'var(--role-color)'}; - border: 1px solid - ${(props) => - props.defaultColor - ? 'var(--role-color)' - : props.selected - ? 'var(--role-accent)' - : 'transparent'}; - display: flex; - justify-content: center; - align-items: center; - transition: border ${transitions.in2in}s ease-in-out, - background-color ${transitions.in2in}s ease-in-out; - flex-shrink: 0; + width: 24px; + height: 24px; + border-radius: 25px; + background-color: ${(props) => + props.defaultColor && !props.selected ? 'transparent' : 'var(--role-color)'}; + border: 1px solid + ${(props) => + props.defaultColor + ? 'var(--role-color)' + : props.selected + ? 'var(--role-accent)' + : 'transparent'}; + display: flex; + justify-content: center; + align-items: center; + transition: border ${transitions.in2in}s ease-in-out, + background-color ${transitions.in2in}s ease-in-out; + flex-shrink: 0; - svg { - width: 10px; - height: 10px; - fill-opacity: ${(props) => (props.selected || props.disabled ? 1 : 0)}; - transition: fill-opacity ${transitions.in2in}s ease-in-out; - fill: ${(props) => - props.disabled && props.defaultColor - ? 'var(--role-color)' - : 'var(--role-contrast)'}; - } + svg { + width: 10px; + height: 10px; + fill-opacity: ${(props) => (props.selected || props.disabled ? 1 : 0)}; + transition: fill-opacity ${transitions.in2in}s ease-in-out; + fill: ${(props) => + props.disabled && props.defaultColor + ? 'var(--role-color)' + : 'var(--role-contrast)'}; + } `; export const Outer = styled.div` - border-radius: 24px; - background-color: ${(props) => - props.selected && !props.defaultColor ? 'var(--role-color)' : palette.taupe100}; - color: ${(props) => (props.selected ? 'var(--role-contrast)' : palette.grey600)}; - transition: color ${transitions.in2in}s ease-in-out, - background-color ${transitions.in2in}s ease-in-out, - transform ${transitions.actionable}s ease-in-out, - box-shadow ${transitions.actionable}s ease-in-out; - display: flex; - padding: 4px; - user-select: none; - overflow: hidden; - cursor: pointer; - ${(props) => - !props.disabled - ? css` - &:hover { - transform: translateY(-2px); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); - ${Circle} svg { - fill-opacity: 1; - } - } + border-radius: 24px; + background-color: ${(props) => + props.selected && !props.defaultColor ? 'var(--role-color)' : palette.taupe100}; + color: ${(props) => (props.selected ? 'var(--role-contrast)' : palette.grey600)}; + transition: color ${transitions.in2in}s ease-in-out, + background-color ${transitions.in2in}s ease-in-out, + transform ${transitions.actionable}s ease-in-out, + box-shadow ${transitions.actionable}s ease-in-out; + display: flex; + padding: 4px; + user-select: none; + overflow: hidden; + cursor: pointer; + ${(props) => + !props.disabled + ? css` + &:hover { + transform: translateY(-2px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); + ${Circle} svg { + fill-opacity: 1; + } + } - &:active { - transform: translateY(0); - box-shadow: 0 0 0 transparent; - } - ` - : null}; + &:active { + transform: translateY(0); + box-shadow: 0 0 0 transparent; + } + ` + : null}; `; export const Text = styled.div` - padding: 0 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + padding: 0 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; diff --git a/packages/design-system/atoms/role/Role.tsx b/packages/design-system/atoms/role/Role.tsx index 24074ce..2ac29c8 100644 --- a/packages/design-system/atoms/role/Role.tsx +++ b/packages/design-system/atoms/role/Role.tsx @@ -7,94 +7,94 @@ import { FaCheck, FaTimes } from 'react-icons/fa'; import * as styled from './Role.styled'; type Props = { - role: RPCRole; - selected: boolean; - disabled?: boolean; - onClick?: (newState: boolean) => void; - tooltipId?: string; - type?: 'delete'; + role: RPCRole; + selected: boolean; + disabled?: boolean; + onClick?: (newState: boolean) => void; + tooltipId?: string; + type?: 'delete'; }; const getColorsFromBase = (baseColor: chroma.Color, contrastCheckThrow: number = 5) => { - // Which has more contrast? Stepping up or stepping down? - const contrastColorUp = baseColor.brighten(contrastCheckThrow); - const contrastColorDown = baseColor.darken(contrastCheckThrow); + // Which has more contrast? Stepping up or stepping down? + const contrastColorUp = baseColor.brighten(contrastCheckThrow); + const contrastColorDown = baseColor.darken(contrastCheckThrow); - if ( - chroma.contrast(baseColor, contrastColorUp) > - chroma.contrast(baseColor, contrastColorDown) - ) { - return { - contrastColor: baseColor.brighten(3), - accentColor: baseColor.brighten(2), - }; - } else { - return { - contrastColor: baseColor.darken(3), - accentColor: baseColor.darken(2), - }; - } + if ( + chroma.contrast(baseColor, contrastColorUp) > + chroma.contrast(baseColor, contrastColorDown) + ) { + return { + contrastColor: baseColor.brighten(3), + accentColor: baseColor.brighten(2), + }; + } else { + return { + contrastColor: baseColor.darken(3), + accentColor: baseColor.darken(2), + }; + } }; export const Role = (props: Props) => { - const colorVars = { - '--role-color': 'white', - '--role-contrast': 'hsl(0,0,0%)', - '--role-accent': 'hsl(0,0,70%)', - }; + const colorVars = { + '--role-color': 'white', + '--role-contrast': 'hsl(0,0,0%)', + '--role-accent': 'hsl(0,0,70%)', + }; - if (props.role.color !== 0) { - const baseColor = numberToChroma(props.role.color); - const { accentColor, contrastColor } = getColorsFromBase(baseColor, 5); - colorVars['--role-color'] = baseColor.css(); - colorVars['--role-accent'] = accentColor.css(); - colorVars['--role-contrast'] = contrastColor.css(); - } + if (props.role.color !== 0) { + const baseColor = numberToChroma(props.role.color); + const { accentColor, contrastColor } = getColorsFromBase(baseColor, 5); + colorVars['--role-color'] = baseColor.css(); + colorVars['--role-accent'] = accentColor.css(); + colorVars['--role-contrast'] = contrastColor.css(); + } - const styledProps: styled.StyledProps = { - selected: props.selected, - defaultColor: props.role.color === 0, - disabled: !!props.disabled, - type: props.type, - }; + const styledProps: styled.StyledProps = { + selected: props.selected, + defaultColor: props.role.color === 0, + disabled: !!props.disabled, + type: props.type, + }; - const extra = !props.disabled - ? {} - : { - 'data-tip': disabledReason(props.role), - 'data-for': props.tooltipId, - }; + const extra = !props.disabled + ? {} + : { + 'data-tip': disabledReason(props.role), + 'data-for': props.tooltipId, + }; - return ( - !props.disabled && props.onClick?.(!props.selected)} - {...extra} - > - - {!props.disabled && props.type !== 'delete' ? : } - - {props.role.name} - - ); + return ( + !props.disabled && props.onClick?.(!props.selected)} + {...extra} + > + + {!props.disabled && props.type !== 'delete' ? : } + + {props.role.name} + + ); }; const disabledReason = (role: RPCRole) => { - switch (role.safety) { - case RoleSafety.HigherThanBot: - return `This role is above Roleypoly's own role.`; - case RoleSafety.DangerousPermissions: - const rolePermissions = BigInt(role.permissions); - let permissionHits: string[] = []; + switch (role.safety) { + case RoleSafety.HigherThanBot: + return `This role is above Roleypoly's own role.`; + case RoleSafety.DangerousPermissions: + const rolePermissions = BigInt(role.permissions); + let permissionHits: string[] = []; - evaluatePermission(rolePermissions, permissions.ADMINISTRATOR) && - permissionHits.push('Administrator'); - evaluatePermission(rolePermissions, permissions.MANAGE_ROLES) && - permissionHits.push('Manage Roles'); + evaluatePermission(rolePermissions, permissions.ADMINISTRATOR) && + permissionHits.push('Administrator'); + evaluatePermission(rolePermissions, permissions.MANAGE_ROLES) && + permissionHits.push('Manage Roles'); - return `This role has unsafe permissions: ${permissionHits.join(', ')}`; - default: - return `This role is disabled.`; - } + return `This role has unsafe permissions: ${permissionHits.join(', ')}`; + default: + return `This role is disabled.`; + } }; diff --git a/packages/design-system/atoms/space/Space.stories.tsx b/packages/design-system/atoms/space/Space.stories.tsx index ba95f37..ac0e768 100644 --- a/packages/design-system/atoms/space/Space.stories.tsx +++ b/packages/design-system/atoms/space/Space.stories.tsx @@ -2,13 +2,13 @@ import * as React from 'react'; import { Space as SpaceComponent } from './Space'; export default { - title: 'Atoms', + title: 'Atoms', }; export const Space = () => ( - <> - hello world - - but im over here - + <> + hello world + + but im over here + ); diff --git a/packages/design-system/atoms/space/Space.tsx b/packages/design-system/atoms/space/Space.tsx index 7c7df4a..84b8d7e 100644 --- a/packages/design-system/atoms/space/Space.tsx +++ b/packages/design-system/atoms/space/Space.tsx @@ -1,5 +1,5 @@ import styled from 'styled-components'; export const Space = styled.div` - height: 15px; + height: 15px; `; diff --git a/packages/design-system/atoms/sparkle/Shapes.tsx b/packages/design-system/atoms/sparkle/Shapes.tsx index be77b89..13446ba 100644 --- a/packages/design-system/atoms/sparkle/Shapes.tsx +++ b/packages/design-system/atoms/sparkle/Shapes.tsx @@ -2,203 +2,203 @@ import * as React from 'react'; import { CSSProperties } from 'styled-components'; type SparkleProps = { - height: string; - strokeColor: string; - repeatCount?: number; - style?: CSSProperties; - delay?: number; + height: string; + strokeColor: string; + repeatCount?: number; + style?: CSSProperties; + delay?: number; }; const Animation = (props: SparkleProps) => ( - <> - - + <> + + - - + + ); const SparkleCircle = (props: SparkleProps) => ( - - - - - - - + + + + + + + ); const SparkleStar = (props: SparkleProps) => ( - - - - - - - - + + + + + + + + ); const SparkleCross = (props: SparkleProps) => ( - - - - - - - + + + + + + + ); const patternBase: CSSProperties = { - position: 'relative', + position: 'relative', }; const shapeMixin: CSSProperties = { - position: 'absolute', + position: 'absolute', }; export const SparklePatternAlpha = ({ style, ...props }: SparkleProps) => ( -
- + - + - + - + -
+ bottom: '0%', + right: '10%', + }} + delay={0.75} + /> +
); export const SparklePatternBeta = ({ style, ...props }: SparkleProps) => ( -
- + - + - + - + -
+ top: '20%', + left: '30%', + transform: 'rotate(30deg)', + }} + delay={0.6} + /> + ); diff --git a/packages/design-system/atoms/sparkle/Sparkle.stories.tsx b/packages/design-system/atoms/sparkle/Sparkle.stories.tsx index 6eede0d..7aaee09 100644 --- a/packages/design-system/atoms/sparkle/Sparkle.stories.tsx +++ b/packages/design-system/atoms/sparkle/Sparkle.stories.tsx @@ -4,19 +4,19 @@ import * as React from 'react'; import { SparkleOverlay } from './Sparkle'; export default { - title: 'Atoms/Sparkle', - component: SparkleOverlay, - args: { - size: -10, - opacity: 1, - repeatCount: 3, - }, + title: 'Atoms/Sparkle', + component: SparkleOverlay, + args: { + size: -10, + opacity: 1, + repeatCount: 3, + }, }; export const ExampleButton = (args) => ( - - - - - + + + + + ); diff --git a/packages/design-system/atoms/sparkle/Sparkle.tsx b/packages/design-system/atoms/sparkle/Sparkle.tsx index a262005..6ec2bd2 100644 --- a/packages/design-system/atoms/sparkle/Sparkle.tsx +++ b/packages/design-system/atoms/sparkle/Sparkle.tsx @@ -4,49 +4,49 @@ import styled from 'styled-components'; import { SparklePatternAlpha, SparklePatternBeta } from './Shapes'; type Props = { - children: React.ReactNode; - size?: number; - opacity?: number; - repeatCount?: number; - strokeColor?: string; + children: React.ReactNode; + size?: number; + opacity?: number; + repeatCount?: number; + strokeColor?: string; }; const SparkleContainer = styled.div` - position: relative; + position: relative; `; type EffectProps = { - effectSize: Props['size']; - effectOpacity: Props['opacity']; + effectSize: Props['size']; + effectOpacity: Props['opacity']; }; const SparkleEffect = styled.div` - position: absolute; - top: ${(props) => props.effectSize}px; - bottom: ${(props) => props.effectSize}px; - left: ${(props) => props.effectSize}px; - right: ${(props) => props.effectSize}px; - display: flex; - justify-content: space-between; - z-index: 5; - opacity: ${(props) => props.effectOpacity}; - pointer-events: none; + position: absolute; + top: ${(props) => props.effectSize}px; + bottom: ${(props) => props.effectSize}px; + left: ${(props) => props.effectSize}px; + right: ${(props) => props.effectSize}px; + display: flex; + justify-content: space-between; + z-index: 5; + opacity: ${(props) => props.effectOpacity}; + pointer-events: none; `; export const SparkleOverlay = ({ strokeColor = palette.gold400, ...props }: Props) => ( - - - - - - {props.children} - + + + + + + {props.children} + ); diff --git a/packages/design-system/atoms/tab-view/TabView.spec.tsx b/packages/design-system/atoms/tab-view/TabView.spec.tsx index f6b7fe0..d5669d8 100644 --- a/packages/design-system/atoms/tab-view/TabView.spec.tsx +++ b/packages/design-system/atoms/tab-view/TabView.spec.tsx @@ -4,36 +4,36 @@ import { Tab, TabView, TabViewProps } from './TabView'; import { TabContent, TabTitle } from './TabView.styled'; const makeView = (props: Partial = {}) => - shallow( - - {() =>
tab 1
}
- {() =>
tab 2
}
, -
- ); + shallow( + + {() =>
tab 1
}
+ {() =>
tab 2
}
, +
+ ); it('renders tab content correctly', () => { - const view = makeView(); + const view = makeView(); - expect(view.find(Tab).renderProp('children')().text()).toBe('tab 1'); + expect(view.find(Tab).renderProp('children')().text()).toBe('tab 1'); }); it('automatically picks preselected tab content', () => { - const view = makeView({ initialTab: 1 }); + const view = makeView({ initialTab: 1 }); - expect(view.find(Tab).renderProp('children')().text()).toBe('tab 2'); + expect(view.find(Tab).renderProp('children')().text()).toBe('tab 2'); }); it('automatically uses the first tab when preselected tab is not present', () => { - const view = makeView({ initialTab: -1 }); + const view = makeView({ initialTab: -1 }); - view.find(TabContent).find('i').simulate('load'); - expect(view.find(Tab).renderProp('children')().text()).toBe('tab 1'); + view.find(TabContent).find('i').simulate('load'); + expect(view.find(Tab).renderProp('children')().text()).toBe('tab 1'); }); it('changes between tabs when tab is clicked', () => { - const view = makeView(); + const view = makeView(); - view.find(TabTitle).at(1).simulate('click'); + view.find(TabTitle).at(1).simulate('click'); - expect(view.find(Tab).renderProp('children')().text()).toBe('tab 2'); + expect(view.find(Tab).renderProp('children')().text()).toBe('tab 2'); }); diff --git a/packages/design-system/atoms/tab-view/TabView.stories.tsx b/packages/design-system/atoms/tab-view/TabView.stories.tsx index cf37bd9..272ca11 100644 --- a/packages/design-system/atoms/tab-view/TabView.stories.tsx +++ b/packages/design-system/atoms/tab-view/TabView.stories.tsx @@ -2,26 +2,26 @@ import * as React from 'react'; import { Tab, TabView } from './TabView'; export default { - title: 'Atoms/Tab View', - argTypes: { - tabCount: { control: 'range', min: 1, max: 100 }, - }, - args: { - tabCount: 10, - }, + title: 'Atoms/Tab View', + argTypes: { + tabCount: { control: 'range', min: 1, max: 100 }, + }, + args: { + tabCount: 10, + }, }; export const ManyTabs = ({ tabCount }) => { - const tabs = [...'0'.repeat(tabCount)].map((_, i) => ( - - {() => ( - <> -

tab {i}

-

hello!!!!!

- - )} -
- )); + const tabs = [...'0'.repeat(tabCount)].map((_, i) => ( + + {() => ( + <> +

tab {i}

+

hello!!!!!

+ + )} +
+ )); - return {tabs}; + return {tabs}; }; diff --git a/packages/design-system/atoms/tab-view/TabView.styled.ts b/packages/design-system/atoms/tab-view/TabView.styled.ts index ffc9f3b..a156c13 100644 --- a/packages/design-system/atoms/tab-view/TabView.styled.ts +++ b/packages/design-system/atoms/tab-view/TabView.styled.ts @@ -6,37 +6,37 @@ import styled, { css } from 'styled-components'; export const TabViewStyled = styled.div``; export const TabTitleRow = styled.div` - display: flex; - border-bottom: 1px solid ${palette.taupe100}; - overflow-x: auto; - overflow-y: hidden; - white-space: nowrap; + display: flex; + border-bottom: 1px solid ${palette.taupe100}; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; `; export const TabTitle = styled.div<{ selected: boolean }>` - flex: 1; - text-align: center; - padding: 0.7em 1em; - border-bottom: 3px solid transparent; - transition: border-color ${transitions.in2out}s ease-in-out, - color ${transitions.in2out}s ease-in-out; - cursor: pointer; - color: ${palette.taupe500}; - ${(props) => - props.selected - ? css` - color: unset; - border-bottom-color: ${palette.taupe500}; - ` - : css` - &:hover { - border-bottom-color: ${palette.taupe300}; - color: unset; - } - `}; - ${onTablet(css` - padding: 0.45em 1em; - `)} + flex: 1; + text-align: center; + padding: 0.7em 1em; + border-bottom: 3px solid transparent; + transition: border-color ${transitions.in2out}s ease-in-out, + color ${transitions.in2out}s ease-in-out; + cursor: pointer; + color: ${palette.taupe500}; + ${(props) => + props.selected + ? css` + color: unset; + border-bottom-color: ${palette.taupe500}; + ` + : css` + &:hover { + border-bottom-color: ${palette.taupe300}; + color: unset; + } + `}; + ${onTablet(css` + padding: 0.45em 1em; + `)} `; export const TabContent = styled.div``; diff --git a/packages/design-system/atoms/tab-view/TabView.tsx b/packages/design-system/atoms/tab-view/TabView.tsx index d8dd3e5..9b73364 100644 --- a/packages/design-system/atoms/tab-view/TabView.tsx +++ b/packages/design-system/atoms/tab-view/TabView.tsx @@ -2,52 +2,52 @@ import * as React from 'react'; import { TabContent, TabTitle, TabTitleRow, TabViewStyled } from './TabView.styled'; export type TabViewProps = { - children: React.ReactNode[]; - initialTab?: number; + children: React.ReactNode[]; + initialTab?: number; }; type TabProps = { - title: string; - children: () => React.ReactNode; + title: string; + children: () => React.ReactNode; }; export const TabView = (props: TabViewProps) => { - const tabNames = React.Children.map(props.children, (child) => { - if (!React.isValidElement(child)) { - return '(Oops)'; - } - - return child.props.title; - }) as string[]; - - if (tabNames.length === 0) { - return null; + const tabNames = React.Children.map(props.children, (child) => { + if (!React.isValidElement(child)) { + return '(Oops)'; } - const [currentTab, setCurrentTab] = React.useState(props.initialTab ?? 0); + return child.props.title; + }) as string[]; - return ( - - - {tabNames.map((tabName, idx) => ( - setCurrentTab(idx)} - key={`tab${tabName}${idx}`} - > - {tabName} - - ))} - - - {props.children[currentTab] || ( - setCurrentTab(0)}> - Tabs were misconfigured, resetting to zero. - - )} - - - ); + if (tabNames.length === 0) { + return null; + } + + const [currentTab, setCurrentTab] = React.useState(props.initialTab ?? 0); + + return ( + + + {tabNames.map((tabName, idx) => ( + setCurrentTab(idx)} + key={`tab${tabName}${idx}`} + > + {tabName} + + ))} + + + {props.children[currentTab] || ( + setCurrentTab(0)}> + Tabs were misconfigured, resetting to zero. + + )} + + + ); }; export const Tab = (props: TabProps) =>
{props.children()}
; diff --git a/packages/design-system/atoms/text-input/TextInput.stories.tsx b/packages/design-system/atoms/text-input/TextInput.stories.tsx index 15e7404..3c9a521 100644 --- a/packages/design-system/atoms/text-input/TextInput.stories.tsx +++ b/packages/design-system/atoms/text-input/TextInput.stories.tsx @@ -4,33 +4,33 @@ import { FiKey } from 'react-icons/fi'; import { TextInput, TextInputWithIcon } from './TextInput'; export default { - title: 'Atoms/Text Input', - argTypes: { - placeholder: { control: 'text' }, - }, - args: { - placeholder: 'Fill me in!', - }, + title: 'Atoms/Text Input', + argTypes: { + placeholder: { control: 'text' }, + }, + args: { + placeholder: 'Fill me in!', + }, }; export const Common = (args) => ( +
+ TextInput
- TextInput -
- -
-
- -
- TextInputWithIcon -
- } {...args} /> -
-
- } {...args} disabled /> -
-
- } {...args} type="password" /> -
+
+
+ +
+ TextInputWithIcon +
+ } {...args} /> +
+
+ } {...args} disabled /> +
+
+ } {...args} type="password" /> +
+
); diff --git a/packages/design-system/atoms/text-input/TextInput.tsx b/packages/design-system/atoms/text-input/TextInput.tsx index 2271081..49d1498 100644 --- a/packages/design-system/atoms/text-input/TextInput.tsx +++ b/packages/design-system/atoms/text-input/TextInput.tsx @@ -3,81 +3,81 @@ import * as React from 'react'; import styled from 'styled-components'; const StyledTextInput = styled.input` - appearance: none; - border: 1px solid ${palette.taupe200}; - border-radius: 3px; - line-height: 163%; - padding: 12px 16px; - font-size: 1.2rem; - background-color: ${palette.taupe300}; - color: ${palette.grey600}; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; - position: relative; - width: 100%; - box-sizing: border-box; - max-width: 97vw; + appearance: none; + border: 1px solid ${palette.taupe200}; + border-radius: 3px; + line-height: 163%; + padding: 12px 16px; + font-size: 1.2rem; + background-color: ${palette.taupe300}; + color: ${palette.grey600}; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + position: relative; + width: 100%; + box-sizing: border-box; + max-width: 97vw; - :focus { - outline: none; - border-color: ${palette.grey100}; - box-shadow: 1px 0 3px rgba(0, 0, 0, 0.25); - } + :focus { + outline: none; + border-color: ${palette.grey100}; + box-shadow: 1px 0 3px rgba(0, 0, 0, 0.25); + } - [disabled], - :disabled { - cursor: not-allowed; - color: rgba(255, 255, 255, 0.75); - font-style: italic; - } + [disabled], + :disabled { + cursor: not-allowed; + color: rgba(255, 255, 255, 0.75); + font-style: italic; + } - :hover:not([disabled]) { - border-color: ${palette.grey100}; - } + :hover:not([disabled]) { + border-color: ${palette.grey100}; + } - ::placeholder { - color: ${palette.taupe500}; - } + ::placeholder { + color: ${palette.taupe500}; + } `; type TextInputProps = React.InputHTMLAttributes & { - _override?: React.Component; + _override?: React.Component; }; export const TextInput = (props: TextInputProps) => { - const { ...rest } = props; - return ; + const { ...rest } = props; + return ; }; const StyledTextInputWithIcon = styled(StyledTextInput)` - padding-left: 36px; + padding-left: 36px; `; const IconContainer = styled.div` - position: absolute; - left: 12px; - top: 0; - bottom: 0; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; `; const IconInputContainer = styled.div` - position: relative; - width: 100%; + position: relative; + width: 100%; `; type TextInputWithIconProps = TextInputProps & { - icon: React.ReactNode; + icon: React.ReactNode; }; export const TextInputWithIcon = (props: TextInputWithIconProps) => { - const { icon, ...rest } = props; - return ( - - {icon} - - - ); + const { icon, ...rest } = props; + return ( + + {icon} + + + ); }; diff --git a/packages/design-system/atoms/timings/timings.ts b/packages/design-system/atoms/timings/timings.ts index 08322d9..374a8ac 100644 --- a/packages/design-system/atoms/timings/timings.ts +++ b/packages/design-system/atoms/timings/timings.ts @@ -1,6 +1,6 @@ export const transitions = { - in2in: 0.3, - out2in: 0.15, - in2out: 0.25, - actionable: 0.07, + in2in: 0.3, + out2in: 0.15, + in2out: 0.25, + actionable: 0.07, }; diff --git a/packages/design-system/atoms/typist/Typist.spec.tsx b/packages/design-system/atoms/typist/Typist.spec.tsx index 96aaa76..a6cd7de 100644 --- a/packages/design-system/atoms/typist/Typist.spec.tsx +++ b/packages/design-system/atoms/typist/Typist.spec.tsx @@ -5,22 +5,22 @@ import { Typist } from './Typist'; jest.useFakeTimers(); it('correctly cycles through provided lines', () => { - const lines = ['abcdef', 'ghijkl']; - let view = shallow(); + const lines = ['abcdef', 'ghijkl']; + let view = shallow(); - jest.advanceTimersByTime(100 * lines[0].length); - view = view.update(); - expect(view.text()).toBe(lines[0]); + jest.advanceTimersByTime(100 * lines[0].length); + view = view.update(); + expect(view.text()).toBe(lines[0]); }); it('correctly cycles through provided characters in a line', () => { - const lines = ['abcdef']; + const lines = ['abcdef']; - let view = shallow(); + let view = shallow(); - Array(...lines[0]).forEach((_, idx) => { - view = view.update(); - expect(view.text()).toBe(lines[0].slice(0, idx)); - jest.advanceTimersByTime(1); - }); + Array(...lines[0]).forEach((_, idx) => { + view = view.update(); + expect(view.text()).toBe(lines[0].slice(0, idx)); + jest.advanceTimersByTime(1); + }); }); diff --git a/packages/design-system/atoms/typist/Typist.stories.tsx b/packages/design-system/atoms/typist/Typist.stories.tsx index a1442af..e1be38c 100644 --- a/packages/design-system/atoms/typist/Typist.stories.tsx +++ b/packages/design-system/atoms/typist/Typist.stories.tsx @@ -2,15 +2,15 @@ import * as React from 'react'; import { Typist } from './Typist'; export default { - title: 'Atoms/Typist', - component: Typist, - args: { - charTimeout: 75, - resetTimeout: 2000, - lines: ['hello world', 'and again', 'a third', 'story time!'], - }, + title: 'Atoms/Typist', + component: Typist, + args: { + charTimeout: 75, + resetTimeout: 2000, + lines: ['hello world', 'and again', 'a third', 'story time!'], + }, }; export const Looping = (args) => { - return ; + return ; }; diff --git a/packages/design-system/atoms/typist/Typist.tsx b/packages/design-system/atoms/typist/Typist.tsx index e40eecd..97d0585 100644 --- a/packages/design-system/atoms/typist/Typist.tsx +++ b/packages/design-system/atoms/typist/Typist.tsx @@ -1,37 +1,37 @@ import * as React from 'react'; type TypistProps = { - resetTimeout: number; - charTimeout: number; - lines: string[]; + resetTimeout: number; + charTimeout: number; + lines: string[]; }; export const Typist = (props: TypistProps) => { - const [outputText, setOutputText] = React.useState(''); - const [currentLine, setCurrentLine] = React.useState(0); + const [outputText, setOutputText] = React.useState(''); + const [currentLine, setCurrentLine] = React.useState(0); - React.useEffect(() => { - const fullLine = props.lines[currentLine]; + React.useEffect(() => { + const fullLine = props.lines[currentLine]; - if (outputText === fullLine) { - const timeout = setTimeout(() => { - setOutputText(''); - setCurrentLine((currentLine + 1) % props.lines.length); - }, props.resetTimeout); + if (outputText === fullLine) { + const timeout = setTimeout(() => { + setOutputText(''); + setCurrentLine((currentLine + 1) % props.lines.length); + }, props.resetTimeout); - return () => { - clearTimeout(timeout); - }; - } + return () => { + clearTimeout(timeout); + }; + } - const timeout = setTimeout(() => { - setOutputText(fullLine.slice(0, outputText.length + 1)); - }, props.charTimeout); + const timeout = setTimeout(() => { + setOutputText(fullLine.slice(0, outputText.length + 1)); + }, props.charTimeout); - return () => { - clearTimeout(timeout); - }; - }, [currentLine, outputText]); + return () => { + clearTimeout(timeout); + }; + }, [currentLine, outputText]); - return <>{outputText}; + return <>{outputText}; }; diff --git a/packages/design-system/atoms/typography/mdx.tsx b/packages/design-system/atoms/typography/mdx.tsx index 4d2f3a2..d7c6a12 100644 --- a/packages/design-system/atoms/typography/mdx.tsx +++ b/packages/design-system/atoms/typography/mdx.tsx @@ -2,20 +2,20 @@ import styled from 'styled-components'; import { Link, Text, text600, text700, text800, text900 } from './typography'; export const mdxComponents = { - h1: styled.h1` - ${text900} - `, - h2: styled.h2` - ${text800} - `, - h3: styled.h3` - ${text700} - `, - h4: styled.h4` - ${text600} - `, - p: styled.p` - ${Text} - `, - a: Link, + h1: styled.h1` + ${text900} + `, + h2: styled.h2` + ${text800} + `, + h3: styled.h3` + ${text700} + `, + h4: styled.h4` + ${text600} + `, + p: styled.p` + ${Text} + `, + a: Link, }; diff --git a/packages/design-system/atoms/typography/typography.stories.tsx b/packages/design-system/atoms/typography/typography.stories.tsx index ebc555a..4e24d64 100644 --- a/packages/design-system/atoms/typography/typography.stories.tsx +++ b/packages/design-system/atoms/typography/typography.stories.tsx @@ -3,116 +3,114 @@ import styled from 'styled-components'; import * as typography from './typography'; export default { - title: 'Atoms/Typography', + title: 'Atoms/Typography', }; const Text = () => ( - <> -

- Lorem ipsum dolor sit, amet consectetur adipisicing elit. Et facilis alias - placeat cumque sapiente ad delectus omnis quae. Reiciendis quibusdam deserunt - repellat. Exercitationem modi incidunt autem nemo tempore eaque soluta. -

-

- 帯カノ需混モイ一録43旧百12共ドレ能生ホクユ禁度ヨ材図クほはそ護関ラト郵張エノヨ議件クめざ県読れみとぶ論税クょンど慎転リつぎみ松期ほへド. - 縦投記ふで覧速っだせあ過先課フ演無ぎぱべ習併相ーす気6元ゆる領気希ぎ投代ラ我関レ森郎由系堂ず. - 読ケリ夜指ーっトせ認平引ウシ間花ヱクム年6台ぐ山婦ラスエ子著コア掲中ロ像属戸メソユ職諏ルど詐児題たに書希ク幕値長ラそめド. -

-

- 🔸🐕🔺💱🎊👽🐛 👨📼🕦📞 👱👆🍗👚🌈 🔝🔟🍉🔰🍲🏁🕗 🎡🐉🍲📻🔢🔄 💟💲🍻💜💩🔼 - 🎱🌸📛👫🌻 🗽🕜🐥👕🍈. 🐒🍚🔓📱🏦 🎦🌑🔛💙👣🔚 🔆🗻🌿🎳📲🍯 🌞💟🎌🍌 🔪📯🐎💮 - 👌👭🎋🏉🏰 📓🕃🎂💉🔩 🐟🌇👺🌊🌒 📪👅🍂🍁 🌖🐮🔽🌒📊. 🔤🍍🌸📷🎴 💏🍌📎👥👉👒 - 👝💜🔶🍣 💨🗼👈💉💉💰 🍐🕖🌰👝🕓🏊🐕 🏀📅📼📒 🐕🌈👋 -

- + <> +

+ Lorem ipsum dolor sit, amet consectetur adipisicing elit. Et facilis alias placeat + cumque sapiente ad delectus omnis quae. Reiciendis quibusdam deserunt repellat. + Exercitationem modi incidunt autem nemo tempore eaque soluta. +

+

+ 帯カノ需混モイ一録43旧百12共ドレ能生ホクユ禁度ヨ材図クほはそ護関ラト郵張エノヨ議件クめざ県読れみとぶ論税クょンど慎転リつぎみ松期ほへド. + 縦投記ふで覧速っだせあ過先課フ演無ぎぱべ習併相ーす気6元ゆる領気希ぎ投代ラ我関レ森郎由系堂ず. + 読ケリ夜指ーっトせ認平引ウシ間花ヱクム年6台ぐ山婦ラスエ子著コア掲中ロ像属戸メソユ職諏ルど詐児題たに書希ク幕値長ラそめド. +

+

+ 🔸🐕🔺💱🎊👽🐛 👨📼🕦📞 👱👆🍗👚🌈 🔝🔟🍉🔰🍲🏁🕗 🎡🐉🍲📻🔢🔄 💟💲🍻💜💩🔼 + 🎱🌸📛👫🌻 🗽🕜🐥👕🍈. 🐒🍚🔓📱🏦 🎦🌑🔛💙👣🔚 🔆🗻🌿🎳📲🍯 🌞💟🎌🍌 🔪📯🐎💮 + 👌👭🎋🏉🏰 📓🕃🎂💉🔩 🐟🌇👺🌊🌒 📪👅🍂🍁 🌖🐮🔽🌒📊. 🔤🍍🌸📷🎴 💏🍌📎👥👉👒 + 👝💜🔶🍣 💨🗼👈💉💉💰 🍐🕖🌰👝🕓🏊🐕 🏀📅📼📒 🐕🌈👋 +

+ ); const swatches: [string, string | undefined, string][] = [ - ['text900', 'LargeTitle', 'Used for large titles.'], - ['text800', 'MediumTitle', 'Used for medium titles.'], - ['text700', 'SmallTitle', 'Used for small titles.'], - ['text600', 'AccentTitle', 'Used for accenting titles.'], - ['text500', 'LargeText', 'Used for general large font text blocks.'], - ['text400', 'Text', 'Used for less important font text blocks.'], - ['text300', undefined, 'Used for smaller UI elements.'], - ['text200', 'AmbientLarge', 'Used for ambient text.'], - ['text100', 'AmbientSmall', 'Used for ambient text.'], + ['text900', 'LargeTitle', 'Used for large titles.'], + ['text800', 'MediumTitle', 'Used for medium titles.'], + ['text700', 'SmallTitle', 'Used for small titles.'], + ['text600', 'AccentTitle', 'Used for accenting titles.'], + ['text500', 'LargeText', 'Used for general large font text blocks.'], + ['text400', 'Text', 'Used for less important font text blocks.'], + ['text300', undefined, 'Used for smaller UI elements.'], + ['text200', 'AmbientLarge', 'Used for ambient text.'], + ['text100', 'AmbientSmall', 'Used for ambient text.'], ]; const Section = styled.section` - margin: 3.26rem; + margin: 3.26rem; `; const swatch = (mixin: typeof typography.text900) => - styled.p` - ${mixin} - `; + styled.p` + ${mixin} + `; const Usage = styled.code` - ${typography.text300} + ${typography.text300} `; const Description = styled.i` - ${typography.text200} + ${typography.text200} `; export const Sizes = () => ( -
- {swatches.map(([mixin, componentName, usage], i) => { - const Component = swatch((typography as any)[mixin]); - return ( -
-
- - The quick brown fox jumped over the lazy dog. - -
-
- - - @{mixin} {componentName && `<${componentName} />`} - - -
-
- {usage} -
-
- ); - })} -
+
+ {swatches.map(([mixin, componentName, usage], i) => { + const Component = swatch((typography as any)[mixin]); + return ( +
+
+ The quick brown fox jumped over the lazy dog. +
+
+ + + @{mixin} {componentName && `<${componentName} />`} + + +
+
+ {usage} +
+
+ ); + })} +
); const SpacingHead = styled.p` - ${typography.text700} + ${typography.text700} `; const SpacingSection = styled(Section)` - max-width: 50vw; - border-bottom: 1px solid rgba(0, 0, 0, 0.25); + max-width: 50vw; + border-bottom: 1px solid rgba(0, 0, 0, 0.25); `; export const Spacing = () => ( -
- {swatches.map(([mixin], i) => { - const Component = swatch((typography as any)[mixin]); - return ( - - @{mixin} - - - - - ); - })} -
+
+ {swatches.map(([mixin], i) => { + const Component = swatch((typography as any)[mixin]); + return ( + + @{mixin} + + + + + ); + })} +
); export const Link = () => ( - - Here is a link <3 - + + Here is a link <3 + ); diff --git a/packages/design-system/atoms/typography/typography.tsx b/packages/design-system/atoms/typography/typography.tsx index 83322e7..b44354b 100644 --- a/packages/design-system/atoms/typography/typography.tsx +++ b/packages/design-system/atoms/typography/typography.tsx @@ -4,115 +4,115 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled, { css } from 'styled-components'; const reset = css` - margin: 0; - line-height: 163%; - padding: 0; - font-weight: 400; - text-decoration: none; - font-size-adjust: 0.75; + margin: 0; + line-height: 163%; + padding: 0; + font-weight: 400; + text-decoration: none; + font-size-adjust: 0.75; `; export const text900 = css` - ${reset} + ${reset} - font-size: 2.3rem; + font-size: 2.3rem; `; export const text800 = css` - ${reset} + ${reset} - font-size: 2rem; + font-size: 2rem; `; export const text700 = css` - ${reset} + ${reset} - font-size: 1.7rem; + font-size: 1.7rem; `; export const text600 = css` - ${reset} + ${reset} - font-size: 1.4rem; + font-size: 1.4rem; `; export const text500 = css` - ${reset} + ${reset} - font-size: 1.2rem; + font-size: 1.2rem; `; export const text400 = css` - ${reset} + ${reset} - font-size: 1rem; + font-size: 1rem; `; export const text300 = css` - ${reset} + ${reset} - font-size: 0.9rem; + font-size: 0.9rem; `; export const text200 = css` - ${reset} + ${reset} - font-size: 0.7rem; + font-size: 0.7rem; `; export const text100 = css` - ${reset} + ${reset} - font-size: 0.5rem; + font-size: 0.5rem; `; export const LargeTitle = styled.span` - ${text900} + ${text900} `; export const MediumTitle = styled.span` - ${text800} + ${text800} `; export const SmallTitle = styled.span` - ${text700} + ${text700} `; export const AccentTitle = styled.span` - ${text600} + ${text600} `; export const LargeText = styled.span` - ${text500} + ${text500} `; export const Text = styled.span` - ${text400} + ${text400} `; export const AmbientLarge = styled.span` - ${text200} + ${text200} `; export const AmbientSmall = styled.span` - ${text100} + ${text100} `; export const Link = styled.a` - color: ${palette.taupe500}; - text-decoration: none; - transition: color ${transitions.actionable}s ease-in-out; - &:hover { - color: ${palette.taupe600}; - } + color: ${palette.taupe500}; + text-decoration: none; + transition: color ${transitions.actionable}s ease-in-out; + &:hover { + color: ${palette.taupe600}; + } `; export const CompletelyStylelessLink = styled(RouterLink)` + color: inherit; + text-decoration: none; + :visited, + :active, + :hover { color: inherit; - text-decoration: none; - :visited, - :active, - :hover { - color: inherit; - } + } `; diff --git a/packages/design-system/fixtures/storyData.ts b/packages/design-system/fixtures/storyData.ts index 6684a17..da32bba 100644 --- a/packages/design-system/fixtures/storyData.ts +++ b/packages/design-system/fixtures/storyData.ts @@ -1,241 +1,241 @@ import { - Category, - CategoryType, - DiscordUser, - Features, - Guild, - GuildData, - GuildEnumeration, - GuildSlug, - Member, - Role, - RoleSafety, - RoleypolyUser, + Category, + CategoryType, + DiscordUser, + Features, + Guild, + GuildData, + GuildEnumeration, + GuildSlug, + Member, + Role, + RoleSafety, + RoleypolyUser, } from '@roleypoly/types'; export const roleCategory: Role[] = [ - { - id: 'aaa', - permissions: '0', - name: 'She/Her', - color: 0xffc0cb, - position: 1, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'bbb', - permissions: '0', - name: 'He/Him', - color: 0xc0ebff, - position: 2, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'ccc', - permissions: '0', - name: 'They/Them', - color: 0xc0ffd5, - position: 3, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'ddd', - permissions: '0', - name: 'Reee', - color: 0xff0000, - position: 4, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'eee', - permissions: '0', - name: 'black but actually bravely default', - color: 0x000000, - position: 5, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'fff', - permissions: '0', - name: 'b̻͌̆̽ͣ̃ͭ̊l͚̥͙̔ͨ̊aͥć͕k͎̟͍͕ͥ̋ͯ̓̈̉̋i͛̄̔͂̚̚҉̳͈͔̖̼̮ṣ̤̗̝͊̌͆h͈̭̰͔̥̯ͅ', - color: 0x1, - position: 6, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'unsafe1', - permissions: '0', - name: 'too high', - color: 0xff0088, - position: 7, - managed: false, - safety: RoleSafety.HigherThanBot, - }, - { - id: 'unsafe2', - permissions: String(0x00000008 | 0x10000000), - name: 'too strong', - color: 0x00ff88, - position: 8, - managed: false, - safety: RoleSafety.DangerousPermissions, - }, + { + id: 'aaa', + permissions: '0', + name: 'She/Her', + color: 0xffc0cb, + position: 1, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'bbb', + permissions: '0', + name: 'He/Him', + color: 0xc0ebff, + position: 2, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'ccc', + permissions: '0', + name: 'They/Them', + color: 0xc0ffd5, + position: 3, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'ddd', + permissions: '0', + name: 'Reee', + color: 0xff0000, + position: 4, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'eee', + permissions: '0', + name: 'black but actually bravely default', + color: 0x000000, + position: 5, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'fff', + permissions: '0', + name: 'b̻͌̆̽ͣ̃ͭ̊l͚̥͙̔ͨ̊aͥć͕k͎̟͍͕ͥ̋ͯ̓̈̉̋i͛̄̔͂̚̚҉̳͈͔̖̼̮ṣ̤̗̝͊̌͆h͈̭̰͔̥̯ͅ', + color: 0x1, + position: 6, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'unsafe1', + permissions: '0', + name: 'too high', + color: 0xff0088, + position: 7, + managed: false, + safety: RoleSafety.HigherThanBot, + }, + { + id: 'unsafe2', + permissions: String(0x00000008 | 0x10000000), + name: 'too strong', + color: 0x00ff88, + position: 8, + managed: false, + safety: RoleSafety.DangerousPermissions, + }, ]; export const mockCategory: Category = { - id: 'aaa', - name: 'Mock', - roles: roleCategory.map((x) => x.id), - hidden: false, - type: CategoryType.Multi, - position: 0, + id: 'aaa', + name: 'Mock', + roles: roleCategory.map((x) => x.id), + hidden: false, + type: CategoryType.Multi, + position: 0, }; export const roleCategory2: Role[] = [ - { - id: 'ddd2', - permissions: '0', - name: 'red', - color: 0xff0000, - position: 9, - managed: false, - safety: RoleSafety.Safe, - }, - { - id: 'eee2', - permissions: '0', - name: 'green', - color: 0x00ff00, - position: 10, - managed: false, - safety: RoleSafety.Safe, - }, + { + id: 'ddd2', + permissions: '0', + name: 'red', + color: 0xff0000, + position: 9, + managed: false, + safety: RoleSafety.Safe, + }, + { + id: 'eee2', + permissions: '0', + name: 'green', + color: 0x00ff00, + position: 10, + managed: false, + safety: RoleSafety.Safe, + }, ]; export const mockCategorySingle: Category = { - id: 'bbb', - name: 'Mock Single 岡野', - roles: roleCategory2.map((x) => x.id), - hidden: false, - type: CategoryType.Single, - position: 0, + id: 'bbb', + name: 'Mock Single 岡野', + roles: roleCategory2.map((x) => x.id), + hidden: false, + type: CategoryType.Single, + position: 0, }; export const roleWikiData = { - aaa: 'Typically used by feminine-identifying people', - bbb: 'Typically used by masculine-identifying people', - ccc: 'Typically used to refer to all people as a singular neutral.', + aaa: 'Typically used by feminine-identifying people', + bbb: 'Typically used by masculine-identifying people', + ccc: 'Typically used to refer to all people as a singular neutral.', }; export const guild: Guild = { - name: 'emoji megaporium', - id: '421896162539470888', - icon: '3372fd895ed913b55616c5e49cd50e60', - roles: [], + name: 'emoji megaporium', + id: '421896162539470888', + icon: '3372fd895ed913b55616c5e49cd50e60', + roles: [], }; export const roleypolyGuild: GuildSlug = { - name: 'Roleypoly', - id: '386659935687147521', - permissionLevel: 0, - icon: 'ffee638c73ff9c972554f64ca34d67ee', + name: 'Roleypoly', + id: '386659935687147521', + permissionLevel: 0, + icon: 'ffee638c73ff9c972554f64ca34d67ee', }; export const guildMap: { [x: string]: GuildSlug } = { - 'emoji megaporium': { - name: guild.name, - id: guild.id, - permissionLevel: 0, - icon: guild.icon, - }, - Roleypoly: roleypolyGuild, - 'chamber of secrets': { - name: 'chamber of secrets', - id: 'aaa', - permissionLevel: 0, - icon: '', - }, - Eclipse: { - name: 'Eclipse', - id: '408821059161423873', - permissionLevel: 0, - icon: '49dfdd8b2456e2977e80a8b577b19c0d', - }, + 'emoji megaporium': { + name: guild.name, + id: guild.id, + permissionLevel: 0, + icon: guild.icon, + }, + Roleypoly: roleypolyGuild, + 'chamber of secrets': { + name: 'chamber of secrets', + id: 'aaa', + permissionLevel: 0, + icon: '', + }, + Eclipse: { + name: 'Eclipse', + id: '408821059161423873', + permissionLevel: 0, + icon: '49dfdd8b2456e2977e80a8b577b19c0d', + }, }; export const guildData: GuildData = { - id: 'aaa', - message: 'henlo worl!!', - categories: [mockCategory, mockCategorySingle], - features: Features.None, + id: 'aaa', + message: 'henlo worl!!', + categories: [mockCategory, mockCategorySingle], + features: Features.None, }; export const user: DiscordUser = { - id: '62601275618889728', - username: 'okano', - discriminator: '0001', - avatar: 'ca2028bab0fe30e1af4392f3fa3576e2', - bot: false, + id: '62601275618889728', + username: 'okano', + discriminator: '0001', + avatar: 'ca2028bab0fe30e1af4392f3fa3576e2', + bot: false, }; export const member: Member = { - guildid: 'aaa', - roles: ['aaa', 'eee', 'unsafe2', 'ddd2'], - nick: 'okano cat', - user: user, + guildid: 'aaa', + roles: ['aaa', 'eee', 'unsafe2', 'ddd2'], + nick: 'okano cat', + user: user, }; export const rpUser: RoleypolyUser = { - discorduser: user, + discorduser: user, }; export const guildEnum: GuildEnumeration = { - guilds: [ - { - id: 'aaa', - guild: guildMap['emoji megaporium'], - member, - data: guildData, - roles: [...roleCategory, ...roleCategory2], - }, - { - id: 'bbb', - guild: guildMap['Roleypoly'], - member: { - ...member, - roles: ['unsafe2'], - }, - data: guildData, - roles: [...roleCategory, ...roleCategory2], - }, - { - id: 'ccc', - guild: guildMap['chamber of secrets'], - member, - data: guildData, - roles: [...roleCategory, ...roleCategory2], - }, - { - id: 'ddd', - guild: guildMap['Eclipse'], - member, - data: guildData, - roles: [...roleCategory, ...roleCategory2], - }, - ], + guilds: [ + { + id: 'aaa', + guild: guildMap['emoji megaporium'], + member, + data: guildData, + roles: [...roleCategory, ...roleCategory2], + }, + { + id: 'bbb', + guild: guildMap['Roleypoly'], + member: { + ...member, + roles: ['unsafe2'], + }, + data: guildData, + roles: [...roleCategory, ...roleCategory2], + }, + { + id: 'ccc', + guild: guildMap['chamber of secrets'], + member, + data: guildData, + roles: [...roleCategory, ...roleCategory2], + }, + { + id: 'ddd', + guild: guildMap['Eclipse'], + member, + data: guildData, + roles: [...roleCategory, ...roleCategory2], + }, + ], }; export const mastheadSlugs: GuildSlug[] = guildEnum.guilds.map( - (guild, idx) => ({ - id: guild.guild.id, - name: guild.guild.name, - icon: guild.guild.icon, - permissionLevel: 1 << idx % 3, - }) + (guild, idx) => ({ + id: guild.guild.id, + name: guild.guild.name, + icon: guild.guild.icon, + permissionLevel: 1 << idx % 3, + }) ); diff --git a/packages/design-system/molecules/demo-discord/DemoDiscord.stories.tsx b/packages/design-system/molecules/demo-discord/DemoDiscord.stories.tsx index 593c352..8bc8fd6 100644 --- a/packages/design-system/molecules/demo-discord/DemoDiscord.stories.tsx +++ b/packages/design-system/molecules/demo-discord/DemoDiscord.stories.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { DemoDiscord } from './DemoDiscord'; export default { - title: 'Molecules/Role Demos', + title: 'Molecules/Role Demos', }; export const Discord = () => ; diff --git a/packages/design-system/molecules/demo-discord/DemoDiscord.styled.ts b/packages/design-system/molecules/demo-discord/DemoDiscord.styled.ts index 552f009..c68e3c0 100644 --- a/packages/design-system/molecules/demo-discord/DemoDiscord.styled.ts +++ b/packages/design-system/molecules/demo-discord/DemoDiscord.styled.ts @@ -2,35 +2,35 @@ import { palette } from '@roleypoly/design-system/atoms/colors'; import styled, { keyframes } from 'styled-components'; export const Base = styled.div` - background-color: ${palette.discord100}; - border: solid 1px rgba(0, 0, 0, 0.15); - border-radius: 3px; - padding: 10px; - user-select: none; + background-color: ${palette.discord100}; + border: solid 1px rgba(0, 0, 0, 0.15); + border-radius: 3px; + padding: 10px; + user-select: none; `; export const Timestamp = styled.span` - padding: 0 5px; - font-size: 0.7em; - opacity: 0.3; + padding: 0 5px; + font-size: 0.7em; + opacity: 0.3; `; export const TextParts = styled.span` - padding: 0 5px; + padding: 0 5px; `; export const Username = styled(TextParts)` - &:hover { - text-decoration: underline; - cursor: pointer; - } + &:hover { + text-decoration: underline; + cursor: pointer; + } `; export const InputBox = styled.div` - margin-top: 10px; - background-color: ${palette.discord200}; - padding: 7px 10px; - border-radius: 3px; + margin-top: 10px; + background-color: ${palette.discord200}; + padding: 7px 10px; + border-radius: 3px; `; const lineBlink = keyframes` @@ -52,16 +52,16 @@ const lineBlink = keyframes` `; export const Line = styled.div` - background-color: ${palette.grey600}; - width: 1px; - height: 1.5em; - display: inline-block; - position: absolute; - right: -5px; - animation: ${lineBlink} 0.5s ease-in-out infinite alternate-reverse; + background-color: ${palette.grey600}; + width: 1px; + height: 1.5em; + display: inline-block; + position: absolute; + right: -5px; + animation: ${lineBlink} 0.5s ease-in-out infinite alternate-reverse; `; export const InputTextAlignment = styled.div` - position: relative; - display: inline-block; + position: relative; + display: inline-block; `; diff --git a/packages/design-system/molecules/demo-discord/DemoDiscord.tsx b/packages/design-system/molecules/demo-discord/DemoDiscord.tsx index b1f9699..80b0c6b 100644 --- a/packages/design-system/molecules/demo-discord/DemoDiscord.tsx +++ b/packages/design-system/molecules/demo-discord/DemoDiscord.tsx @@ -2,52 +2,52 @@ import { Typist } from '@roleypoly/design-system/atoms/typist'; import { demoData } from '@roleypoly/types/demoData'; import * as React from 'react'; import { - Base, - InputBox, - InputTextAlignment, - Line, - TextParts, - Timestamp, - Username, + Base, + InputBox, + InputTextAlignment, + Line, + TextParts, + Timestamp, + Username, } from './DemoDiscord.styled'; export const DemoDiscord = () => { - const time = new Date(); - const timeString = time.toTimeString(); + const time = new Date(); + const timeString = time.toTimeString(); - const [easterEggCount, setEasterEggCount] = React.useState(0); + const [easterEggCount, setEasterEggCount] = React.useState(0); - return ( - - - {time.getHours() % 12}:{timeString.slice(3, 5)}  - {time.getHours() <= 12 ? 'AM' : 'PM'} - - setEasterEggCount(easterEggCount + 1)}> - okano cat - - - {easterEggCount >= 15 - ? `NYAAAAAAA${'A'.repeat(easterEggCount - 15)}` - : easterEggCount >= 11 - ? `I'm.. I'm gonna...` - : easterEggCount >= 10 - ? `S-senpai... Be careful...` - : easterEggCount >= 5 - ? `H-hey... Stop that..` - : `Hey, I'd like some roles!`} - - - -   - `.iam ${role.name}`)} - /> - - - - - ); + return ( + + + {time.getHours() % 12}:{timeString.slice(3, 5)}  + {time.getHours() <= 12 ? 'AM' : 'PM'} + + setEasterEggCount(easterEggCount + 1)}> + okano cat + + + {easterEggCount >= 15 + ? `NYAAAAAAA${'A'.repeat(easterEggCount - 15)}` + : easterEggCount >= 11 + ? `I'm.. I'm gonna...` + : easterEggCount >= 10 + ? `S-senpai... Be careful...` + : easterEggCount >= 5 + ? `H-hey... Stop that..` + : `Hey, I'd like some roles!`} + + + +   + `.iam ${role.name}`)} + /> + + + + + ); }; diff --git a/packages/design-system/molecules/demo-picker/DemoPicker.stories.tsx b/packages/design-system/molecules/demo-picker/DemoPicker.stories.tsx index 9d1260f..a7ce2a7 100644 --- a/packages/design-system/molecules/demo-picker/DemoPicker.stories.tsx +++ b/packages/design-system/molecules/demo-picker/DemoPicker.stories.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { DemoPicker } from './DemoPicker'; export default { - title: 'Molecules/Role Demos', + title: 'Molecules/Role Demos', }; export const Picker = () => ; diff --git a/packages/design-system/molecules/demo-picker/DemoPicker.tsx b/packages/design-system/molecules/demo-picker/DemoPicker.tsx index fbc1f15..3415ddd 100644 --- a/packages/design-system/molecules/demo-picker/DemoPicker.tsx +++ b/packages/design-system/molecules/demo-picker/DemoPicker.tsx @@ -5,42 +5,42 @@ import * as React from 'react'; import styled from 'styled-components'; const Container = styled.div` - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: center; - align-content: center; - height: 95px; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + align-content: center; + height: 95px; `; const RoleWrap = styled.div` - padding: 2.5px; - display: inline-block; + padding: 2.5px; + display: inline-block; `; export const DemoPicker = () => { - const [selectedStates, setSelectedStates] = React.useState< - { - [key in RPCRole['id']]: boolean; - } - >(demoData.reduce((acc, role) => ({ ...acc, [role.id]: false }), {})); + const [selectedStates, setSelectedStates] = React.useState< + { + [key in RPCRole['id']]: boolean; + } + >(demoData.reduce((acc, role) => ({ ...acc, [role.id]: false }), {})); - return ( - - {demoData.map((role) => ( - - { - setSelectedStates({ - ...selectedStates, - [role.id]: !selectedStates[role.id], - }); - }} - /> - - ))} - - ); + return ( + + {demoData.map((role) => ( + + { + setSelectedStates({ + ...selectedStates, + [role.id]: !selectedStates[role.id], + }); + }} + /> + + ))} + + ); }; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx b/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx index 9cb0133..332c913 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx +++ b/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx @@ -3,17 +3,17 @@ import { mockCategory, roleCategory, roleCategory2 } from '../../fixtures/storyD import { EditorCategory } from './EditorCategory'; export default { - title: 'Molecules/Editor/Category', + title: 'Molecules/Editor/Category', }; export const CategoryEditor = () => { - const [categoryData, setCategoryData] = React.useState(mockCategory); - return ( - setCategoryData(category)} - uncategorizedRoles={roleCategory} - guildRoles={[...roleCategory, ...roleCategory2]} - /> - ); + const [categoryData, setCategoryData] = React.useState(mockCategory); + return ( + setCategoryData(category)} + uncategorizedRoles={roleCategory} + guildRoles={[...roleCategory, ...roleCategory2]} + /> + ); }; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.styled.ts b/packages/design-system/molecules/editor-category/EditorCategory.styled.ts index 196dc48..57bed7d 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.styled.ts +++ b/packages/design-system/molecules/editor-category/EditorCategory.styled.ts @@ -1,6 +1,6 @@ import styled from 'styled-components'; export const RoleContainer = styled.div` - display: flex; - margin: 10px; + display: flex; + margin: 10px; `; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.tsx b/packages/design-system/molecules/editor-category/EditorCategory.tsx index 262bb94..12daf3f 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.tsx +++ b/packages/design-system/molecules/editor-category/EditorCategory.tsx @@ -12,134 +12,133 @@ import { GoSearch } from 'react-icons/go'; import { RoleContainer } from './EditorCategory.styled'; type Props = { - category: Category; - uncategorizedRoles: RoleType[]; - guildRoles: RoleType[]; - onChange: (category: Category) => void; + category: Category; + uncategorizedRoles: RoleType[]; + guildRoles: RoleType[]; + onChange: (category: Category) => void; }; const typeEnumToSwitch = (typeData: CategoryType) => { - if (typeData === CategoryType.Single) { - return 'Single'; - } else { - return 'Multiple'; - } + if (typeData === CategoryType.Single) { + return 'Single'; + } else { + return 'Multiple'; + } }; const switchToTypeEnum = (typeData: 'Single' | 'Multiple') => { - if (typeData === 'Single') { - return CategoryType.Single; - } else { - return CategoryType.Multi; - } + if (typeData === 'Single') { + return CategoryType.Single; + } else { + return CategoryType.Multi; + } }; export const EditorCategory = (props: Props) => { - const [roleSearchPopoverActive, setRoleSearchPopoverActive] = React.useState(false); - const [roleSearchTerm, updateSearchTerm] = React.useState(''); + const [roleSearchPopoverActive, setRoleSearchPopoverActive] = React.useState(false); + const [roleSearchTerm, updateSearchTerm] = React.useState(''); - const onUpdate = ( - key: keyof typeof props.category, - pred?: (newValue: any) => any - ) => (newValue: any) => { - props.onChange({ - ...props.category, - [key]: pred ? pred(newValue) : newValue, - }); - }; + const onUpdate = (key: keyof typeof props.category, pred?: (newValue: any) => any) => ( + newValue: any + ) => { + props.onChange({ + ...props.category, + [key]: pred ? pred(newValue) : newValue, + }); + }; - const handleRoleSelect = (role: RoleType) => { - setRoleSearchPopoverActive(false); - updateSearchTerm(''); - props.onChange({ - ...props.category, - roles: [...props.category.roles, role.id], - }); - }; + const handleRoleSelect = (role: RoleType) => { + setRoleSearchPopoverActive(false); + updateSearchTerm(''); + props.onChange({ + ...props.category, + roles: [...props.category.roles, role.id], + }); + }; - const handleRoleDeselect = (role: RoleType) => () => { - props.onChange({ - ...props.category, - roles: props.category.roles.filter((x) => x !== role.id), - }); - }; + const handleRoleDeselect = (role: RoleType) => () => { + props.onChange({ + ...props.category, + roles: props.category.roles.filter((x) => x !== role.id), + }); + }; - return ( -
- Category Name - x.target.value)} - /> + return ( +
+ Category Name + x.target.value)} + /> - + - Selection Type -
- -
+ Selection Type +
+ +
- + - Visiblity -
- a === 'Hidden')} - /> -
+ Visiblity +
+ a === 'Hidden')} + /> +
- - Roles - setRoleSearchPopoverActive(false)} - > - {() => ( - updateSearchTerm(newTerm)} - /> - )} - - - } - placeholder={'Type or drag a role...'} - onFocus={() => setRoleSearchPopoverActive(true)} - value={roleSearchTerm} - onChange={(x) => updateSearchTerm(x.target.value)} - /> - - {props.category.roles.map((id) => { - const role = props.guildRoles.find((x) => x.id === id); - if (!role) { - return <>; - } + + Roles + setRoleSearchPopoverActive(false)} + > + {() => ( + updateSearchTerm(newTerm)} + /> + )} + + + } + placeholder={'Type or drag a role...'} + onFocus={() => setRoleSearchPopoverActive(true)} + value={roleSearchTerm} + onChange={(x) => updateSearchTerm(x.target.value)} + /> + + {props.category.roles.map((id) => { + const role = props.guildRoles.find((x) => x.id === id); + if (!role) { + return <>; + } - return ( - - ); - })} - - -
- ); + return ( + + ); + })} + + +
+ ); }; diff --git a/packages/design-system/molecules/error-banner/ErrorBanner.stories.tsx b/packages/design-system/molecules/error-banner/ErrorBanner.stories.tsx index b4b60c7..ff56b74 100644 --- a/packages/design-system/molecules/error-banner/ErrorBanner.stories.tsx +++ b/packages/design-system/molecules/error-banner/ErrorBanner.stories.tsx @@ -2,25 +2,25 @@ import * as React from 'react'; import { ErrorBanner } from './ErrorBanner'; export default { - title: 'Molecules/Error Banner', - argTypes: { - english: { control: 'text' }, - japanese: { control: 'text' }, - friendlyCode: { control: 'text' }, - }, - args: { - english: 'Oh no! I lost it!', - japanese: 'ちょっとにんげんだよ', - friendlyCode: '404', - }, + title: 'Molecules/Error Banner', + argTypes: { + english: { control: 'text' }, + japanese: { control: 'text' }, + friendlyCode: { control: 'text' }, + }, + args: { + english: 'Oh no! I lost it!', + japanese: 'ちょっとにんげんだよ', + friendlyCode: '404', + }, }; export const ErrorBanner_ = ({ english, japanese, friendlyCode }) => ( - + ); diff --git a/packages/design-system/molecules/error-banner/ErrorBanner.styled.ts b/packages/design-system/molecules/error-banner/ErrorBanner.styled.ts index ec41058..eba7021 100644 --- a/packages/design-system/molecules/error-banner/ErrorBanner.styled.ts +++ b/packages/design-system/molecules/error-banner/ErrorBanner.styled.ts @@ -4,38 +4,38 @@ import { text300, text500, text700 } from '@roleypoly/design-system/atoms/typogr import styled, { css } from 'styled-components'; export const ErrorWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - ${onSmallScreen(css` - display: block; - text-align: center; - `)} + display: flex; + align-items: center; + justify-content: center; + ${onSmallScreen(css` + display: block; + text-align: center; + `)} `; export const ErrorDivider = styled.div` - width: 1px; - height: 3em; - background: ${palette.grey600}; - margin: 0 1em; - ${onSmallScreen(css` - display: none; - `)} + width: 1px; + height: 3em; + background: ${palette.grey600}; + margin: 0 1em; + ${onSmallScreen(css` + display: none; + `)} `; export const ErrorSideCode = styled.div` - ${text700} - ${onSmallScreen(css` - margin-bottom: 0.4em; - `)} + ${text700} + ${onSmallScreen(css` + margin-bottom: 0.4em; + `)} `; export const ErrorText = styled.div` - ${text500} + ${text500} `; export const ErrorTextLower = styled.div` - ${text300} + ${text300} - color: ${palette.taupe500}; + color: ${palette.taupe500}; `; diff --git a/packages/design-system/molecules/error-banner/ErrorBanner.tsx b/packages/design-system/molecules/error-banner/ErrorBanner.tsx index bec79ff..af46262 100644 --- a/packages/design-system/molecules/error-banner/ErrorBanner.tsx +++ b/packages/design-system/molecules/error-banner/ErrorBanner.tsx @@ -1,29 +1,29 @@ import * as React from 'react'; import { - ErrorDivider, - ErrorSideCode, - ErrorText, - ErrorTextLower, - ErrorWrapper, + ErrorDivider, + ErrorSideCode, + ErrorText, + ErrorTextLower, + ErrorWrapper, } from './ErrorBanner.styled'; export type ErrorMessage = { - english: string; - japanese?: string; - friendlyCode?: string; + english: string; + japanese?: string; + friendlyCode?: string; }; type ErrorBannerProps = { - message: Required; + message: Required; }; export const ErrorBanner = (props: ErrorBannerProps) => ( - - {props.message.friendlyCode} - -
- {props.message.english} - {props.message.japanese} -
-
+ + {props.message.friendlyCode} + +
+ {props.message.english} + {props.message.japanese} +
+
); diff --git a/packages/design-system/molecules/footer/Flags.tsx b/packages/design-system/molecules/footer/Flags.tsx index 855506c..f19e9e2 100644 --- a/packages/design-system/molecules/footer/Flags.tsx +++ b/packages/design-system/molecules/footer/Flags.tsx @@ -1,94 +1,70 @@ import * as React from 'react'; type FlagsProps = { - width?: number | string; - height?: number | string; + width?: number | string; + height?: number | string; }; export const Flags = (props: FlagsProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + + + ); diff --git a/packages/design-system/molecules/footer/Footer.stories.tsx b/packages/design-system/molecules/footer/Footer.stories.tsx index a7904bb..3f2749c 100644 --- a/packages/design-system/molecules/footer/Footer.stories.tsx +++ b/packages/design-system/molecules/footer/Footer.stories.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import { Footer as FooterComponent } from './Footer'; export default { - title: 'Molecules', - component: FooterComponent, + title: 'Molecules', + component: FooterComponent, }; export const Footer = (args) => ; diff --git a/packages/design-system/molecules/footer/Footer.styled.ts b/packages/design-system/molecules/footer/Footer.styled.ts index 7a9cef0..9410789 100644 --- a/packages/design-system/molecules/footer/Footer.styled.ts +++ b/packages/design-system/molecules/footer/Footer.styled.ts @@ -3,28 +3,28 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled from 'styled-components'; export const FooterWrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - text-align: center; + display: flex; + align-items: center; + justify-content: center; + text-align: center; - a { - color: ${palette.taupe500}; - text-decoration: none; - transition: color ${transitions.actionable}s ease-in-out; - &:hover { - color: ${palette.taupe600}; - } + a { + color: ${palette.taupe500}; + text-decoration: none; + transition: color ${transitions.actionable}s ease-in-out; + &:hover { + color: ${palette.taupe600}; } + } `; export const HoverColor = styled.div` - opacity: 0.3; - filter: saturate(0); - transition: all ${transitions.in2in}s ease-in-out; + opacity: 0.3; + filter: saturate(0); + transition: all ${transitions.in2in}s ease-in-out; - &:hover { - opacity: 1; - filter: none; - } + &:hover { + opacity: 1; + filter: none; + } `; diff --git a/packages/design-system/molecules/footer/Footer.tsx b/packages/design-system/molecules/footer/Footer.tsx index eaf82e2..b89b7a3 100644 --- a/packages/design-system/molecules/footer/Footer.tsx +++ b/packages/design-system/molecules/footer/Footer.tsx @@ -7,21 +7,21 @@ import { FooterWrapper, HoverColor } from './Footer.styled'; const year = new Date().getFullYear(); export const Footer = () => ( - - -
- © {year} Roleypoly – Made with{' '} - -  in Raleigh, NC -
-
- Discord/Support –  - Patreon –  - GitHub -
- - - -
-
+ + +
+ © {year} Roleypoly – Made with{' '} + +  in Raleigh, NC +
+
+ Discord/Support –  + Patreon –  + GitHub +
+ + + +
+
); diff --git a/packages/design-system/molecules/guild-nav/GuildNav.stories.tsx b/packages/design-system/molecules/guild-nav/GuildNav.stories.tsx index 03a1748..994b8ce 100644 --- a/packages/design-system/molecules/guild-nav/GuildNav.stories.tsx +++ b/packages/design-system/molecules/guild-nav/GuildNav.stories.tsx @@ -4,18 +4,18 @@ import { mastheadSlugs } from '../../fixtures/storyData'; import { GuildNav } from './GuildNav'; export default { - title: 'Molecules/Guild Nav', - component: GuildNav, + title: 'Molecules/Guild Nav', + component: GuildNav, }; export const HasGuilds = () => ( - - - + + + ); export const NoGuilds = () => ( - - - + + + ); diff --git a/packages/design-system/molecules/guild-nav/GuildNav.styled.ts b/packages/design-system/molecules/guild-nav/GuildNav.styled.ts index 3a39576..6c08585 100644 --- a/packages/design-system/molecules/guild-nav/GuildNav.styled.ts +++ b/packages/design-system/molecules/guild-nav/GuildNav.styled.ts @@ -3,19 +3,19 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled from 'styled-components'; export const GuildNavItem = styled.a` - display: flex; - align-items: center; - transition: border ${transitions.in2in}s ease-in-out; - border: 1px solid transparent; - border-radius: 3px; - box-sizing: border-box; - margin: 5px; - user-select: none; - color: unset; - text-decoration: none; + display: flex; + align-items: center; + transition: border ${transitions.in2in}s ease-in-out; + border: 1px solid transparent; + border-radius: 3px; + box-sizing: border-box; + margin: 5px; + user-select: none; + color: unset; + text-decoration: none; - &:hover { - border-color: ${palette.taupe300}; - cursor: pointer; - } + &:hover { + border-color: ${palette.taupe300}; + cursor: pointer; + } `; diff --git a/packages/design-system/molecules/guild-nav/GuildNav.tsx b/packages/design-system/molecules/guild-nav/GuildNav.tsx index 0e18460..fbe4526 100644 --- a/packages/design-system/molecules/guild-nav/GuildNav.tsx +++ b/packages/design-system/molecules/guild-nav/GuildNav.tsx @@ -8,61 +8,61 @@ import ReactTooltip from 'react-tooltip'; import { GuildNavItem } from './GuildNav.styled'; type Props = { - guilds: GuildSlug[]; - recentGuilds: string[]; + guilds: GuildSlug[]; + recentGuilds: string[]; }; const tooltipId = 'guildnav'; const Badges = (props: { guild: GuildSlug }) => { - return React.useMemo(() => { - if (props.guild.permissionLevel === UserGuildPermissions.Admin) { - return ; - } + return React.useMemo(() => { + if (props.guild.permissionLevel === UserGuildPermissions.Admin) { + return ; + } - if (props.guild.permissionLevel === UserGuildPermissions.Manager) { - return ; - } + if (props.guild.permissionLevel === UserGuildPermissions.Manager) { + return ; + } - return null; - }, [props.guild.permissionLevel]); + return null; + }, [props.guild.permissionLevel]); }; const NavList = (props: { guilds: Props['guilds'] }) => ( - <> - {props.guilds.map((guild) => ( - - - - - ))} - + <> + {props.guilds.map((guild) => ( + + + + + ))} + ); export const GuildNav = (props: Props) => { - const { sortedGuildSlugs, recentGuildSlugs } = getRecentAndSortedGuilds( - props.guilds, - props.recentGuilds - ); + const { sortedGuildSlugs, recentGuildSlugs } = getRecentAndSortedGuilds( + props.guilds, + props.recentGuilds + ); - return ( -
- - {recentGuildSlugs && ( - <> -
Recents
- -
All Guilds
- - )} - - -
-
- ); + return ( +
+ + {recentGuildSlugs && ( + <> +
Recents
+ +
All Guilds
+ + )} + + +
+
+ ); }; diff --git a/packages/design-system/molecules/help-page-base/HelpPageBase.stories.tsx b/packages/design-system/molecules/help-page-base/HelpPageBase.stories.tsx index 2969c8f..f506032 100644 --- a/packages/design-system/molecules/help-page-base/HelpPageBase.stories.tsx +++ b/packages/design-system/molecules/help-page-base/HelpPageBase.stories.tsx @@ -2,13 +2,13 @@ import * as React from 'react'; import { HelpStoryWrapper } from './storyDecorator'; export default { - title: 'Molecules/Help Page', - decorators: [HelpStoryWrapper], + title: 'Molecules/Help Page', + decorators: [HelpStoryWrapper], }; export const Base = () => ( - <> -

What is the world but vibrations?

-

Vibrations that synchronize and tie it together, running free forever.

- + <> +

What is the world but vibrations?

+

Vibrations that synchronize and tie it together, running free forever.

+ ); diff --git a/packages/design-system/molecules/help-page-base/HelpPageBase.tsx b/packages/design-system/molecules/help-page-base/HelpPageBase.tsx index d0de4c5..e0851d6 100644 --- a/packages/design-system/molecules/help-page-base/HelpPageBase.tsx +++ b/packages/design-system/molecules/help-page-base/HelpPageBase.tsx @@ -3,19 +3,19 @@ import * as React from 'react'; import styled from 'styled-components'; export type HelpPageProps = { - children: React.ReactNode; + children: React.ReactNode; }; const Container = styled.div` - background: ${palette.taupe300}; - padding: 2em 3em; - width: 1024px; - max-width: 98vw; - margin: 0 auto; - margin-top: 75px; - box-sizing: border-box; + background: ${palette.taupe300}; + padding: 2em 3em; + width: 1024px; + max-width: 98vw; + margin: 0 auto; + margin-top: 75px; + box-sizing: border-box; `; export const HelpPageBase = (props: HelpPageProps) => ( - {props.children} + {props.children} ); diff --git a/packages/design-system/molecules/help-page-base/storyDecorator.tsx b/packages/design-system/molecules/help-page-base/storyDecorator.tsx index 9be79c8..ca6c35a 100644 --- a/packages/design-system/molecules/help-page-base/storyDecorator.tsx +++ b/packages/design-system/molecules/help-page-base/storyDecorator.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { HelpPageBase } from './HelpPageBase'; export const HelpStoryWrapper = (storyFn: any): React.ReactNode => ( - - {storyFn()} - + + {storyFn()} + ); diff --git a/packages/design-system/molecules/nav-slug/NavSlug.stories.tsx b/packages/design-system/molecules/nav-slug/NavSlug.stories.tsx index 95ef929..b82b344 100644 --- a/packages/design-system/molecules/nav-slug/NavSlug.stories.tsx +++ b/packages/design-system/molecules/nav-slug/NavSlug.stories.tsx @@ -3,8 +3,8 @@ import { guild } from '../../fixtures/storyData'; import { NavSlug } from './NavSlug'; export default { - title: 'Molecules/Server Slug', - component: NavSlug, + title: 'Molecules/Server Slug', + component: NavSlug, }; export const Empty = () => ; diff --git a/packages/design-system/molecules/nav-slug/NavSlug.styled.ts b/packages/design-system/molecules/nav-slug/NavSlug.styled.ts index d1908c0..bb484a7 100644 --- a/packages/design-system/molecules/nav-slug/NavSlug.styled.ts +++ b/packages/design-system/molecules/nav-slug/NavSlug.styled.ts @@ -1,16 +1,16 @@ import styled from 'styled-components'; export const SlugContainer = styled.div` - display: flex; - align-items: center; - justify-content: flex-start; - padding: 5px; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 5px; `; export const SlugName = styled.div` - padding: 0 10px; - position: relative; - top: -1px; - white-space: nowrap; - text-overflow: ellipsis; + padding: 0 10px; + position: relative; + top: -1px; + white-space: nowrap; + text-overflow: ellipsis; `; diff --git a/packages/design-system/molecules/nav-slug/NavSlug.tsx b/packages/design-system/molecules/nav-slug/NavSlug.tsx index b1b74fb..19cf96e 100644 --- a/packages/design-system/molecules/nav-slug/NavSlug.tsx +++ b/packages/design-system/molecules/nav-slug/NavSlug.tsx @@ -5,23 +5,19 @@ import { GoOrganization } from 'react-icons/go'; import { SlugContainer, SlugName } from './NavSlug.styled'; type Props = { - guild: GuildSlug | null; + guild: GuildSlug | null; }; export const NavSlug = (props: Props) => ( - - - {props.guild ? utils.initialsFromName(props.guild.name) : } - - {props.guild?.name || <>Your Guilds} - + + + {props.guild ? utils.initialsFromName(props.guild.name) : } + + {props.guild?.name || <>Your Guilds} + ); diff --git a/packages/design-system/molecules/picker-category/PickerCategory.stories.tsx b/packages/design-system/molecules/picker-category/PickerCategory.stories.tsx index 43766dd..ae018ef 100644 --- a/packages/design-system/molecules/picker-category/PickerCategory.stories.tsx +++ b/packages/design-system/molecules/picker-category/PickerCategory.stories.tsx @@ -3,32 +3,32 @@ import { mockCategory, roleCategory, roleWikiData } from '../../fixtures/storyDa import { PickerCategory } from './PickerCategory'; export default { - title: 'Molecules/Picker Category', - component: PickerCategory, - args: { - title: 'Pronouns', - roles: roleCategory, - category: mockCategory, - selectedRoles: [], - }, + title: 'Molecules/Picker Category', + component: PickerCategory, + args: { + title: 'Pronouns', + roles: roleCategory, + category: mockCategory, + selectedRoles: [], + }, }; export const Default = (args) => { - return ; + return ; }; export const Single = (args) => { - return ; + return ; }; Single.args = { - type: 'single', + type: 'single', }; export const Multi = (args) => { - return ; + return ; }; Multi.args = { - type: 'multi', + type: 'multi', }; export const Wiki = (args) => { - return ; + return ; }; diff --git a/packages/design-system/molecules/picker-category/PickerCategory.styled.tsx b/packages/design-system/molecules/picker-category/PickerCategory.styled.tsx index 6c006d5..95fcee5 100644 --- a/packages/design-system/molecules/picker-category/PickerCategory.styled.tsx +++ b/packages/design-system/molecules/picker-category/PickerCategory.styled.tsx @@ -1,20 +1,20 @@ import styled from 'styled-components'; export const Head = styled.div` - margin: 7px 5px; - line-height: 200%; - display: flex; - align-items: center; - justify-content: space-between; + margin: 7px 5px; + line-height: 200%; + display: flex; + align-items: center; + justify-content: space-between; `; export const HeadTitle = styled.div` - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; `; export const HeadSub = styled.div` - flex-shrink: 0; - margin-top: -4px; + flex-shrink: 0; + margin-top: -4px; `; diff --git a/packages/design-system/molecules/picker-category/PickerCategory.tsx b/packages/design-system/molecules/picker-category/PickerCategory.tsx index 942a157..f8aada8 100644 --- a/packages/design-system/molecules/picker-category/PickerCategory.tsx +++ b/packages/design-system/molecules/picker-category/PickerCategory.tsx @@ -8,57 +8,57 @@ import styled from 'styled-components'; import { Head, HeadSub, HeadTitle } from './PickerCategory.styled'; export type CategoryProps = { - title: string; - roles: RPCRole[]; - category: RPCCategory; - selectedRoles: string[]; - onChange: (role: RPCRole) => (newState: boolean) => void; - type: 'single' | 'multi'; + title: string; + roles: RPCRole[]; + category: RPCCategory; + selectedRoles: string[]; + onChange: (role: RPCRole) => (newState: boolean) => void; + type: 'single' | 'multi'; } & ( - | { - wikiMode: true; - roleWikiData: { [roleId: string]: string }; - } - | { - wikiMode: false; - } + | { + wikiMode: true; + roleWikiData: { [roleId: string]: string }; + } + | { + wikiMode: false; + } ); const Category = styled.div` - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; `; const Container = styled.div` - overflow: hidden; - padding: 5px; + overflow: hidden; + padding: 5px; `; export const PickerCategory = (props: CategoryProps) => ( -
- - - {props.title} - - {props.type === 'single' && ( - - Pick one - - )} - - - {sortBy(props.roles, 'position').map((role, idx) => ( - - - - ))} - - -
+
+ + + {props.title} + + {props.type === 'single' && ( + + Pick one + + )} + + + {sortBy(props.roles, 'position').map((role, idx) => ( + + + + ))} + + +
); diff --git a/packages/design-system/molecules/preauth-greeting/PreauthGreeting.stories.tsx b/packages/design-system/molecules/preauth-greeting/PreauthGreeting.stories.tsx index 9ac97f7..2209b70 100644 --- a/packages/design-system/molecules/preauth-greeting/PreauthGreeting.stories.tsx +++ b/packages/design-system/molecules/preauth-greeting/PreauthGreeting.stories.tsx @@ -3,11 +3,11 @@ import { mastheadSlugs } from '../../fixtures/storyData'; import { PreauthGreeting } from './PreauthGreeting'; export default { - title: 'Molecules/Preauth/Greeting', - component: PreauthGreeting, - args: { - guildSlug: mastheadSlugs[0], - }, + title: 'Molecules/Preauth/Greeting', + component: PreauthGreeting, + args: { + guildSlug: mastheadSlugs[0], + }, }; export const Greeting = (args) => ; diff --git a/packages/design-system/molecules/preauth-greeting/PreauthGreeting.tsx b/packages/design-system/molecules/preauth-greeting/PreauthGreeting.tsx index ef6da19..e03b184 100644 --- a/packages/design-system/molecules/preauth-greeting/PreauthGreeting.tsx +++ b/packages/design-system/molecules/preauth-greeting/PreauthGreeting.tsx @@ -6,36 +6,30 @@ import * as React from 'react'; import styled from 'styled-components'; type GreetingProps = { - guildSlug: GuildSlug; + guildSlug: GuildSlug; }; const Center = styled.div` - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - text-align: center; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + text-align: center; `; export const PreauthGreeting = (props: GreetingProps) => ( -
- - {avatarUtils.initialsFromName(props.guildSlug.name)} - - - Hi there. {props.guildSlug.name} uses Roleypoly to help assign you - roles. - - - -
+
+ + {avatarUtils.initialsFromName(props.guildSlug.name)} + + + Hi there. {props.guildSlug.name} uses Roleypoly to help assign you roles. + + + +
); diff --git a/packages/design-system/molecules/reset-submit/ResetSubmit.spec.tsx b/packages/design-system/molecules/reset-submit/ResetSubmit.spec.tsx index 3a2c27e..2cfc74a 100644 --- a/packages/design-system/molecules/reset-submit/ResetSubmit.spec.tsx +++ b/packages/design-system/molecules/reset-submit/ResetSubmit.spec.tsx @@ -7,17 +7,17 @@ const onReset = jest.fn(); const onSubmit = jest.fn(); it('calls onReset when reset is clicked', () => { - const view = shallow(); + const view = shallow(); - view.find(Button).at(0).simulate('click'); + view.find(Button).at(0).simulate('click'); - expect(onReset).toBeCalled(); + expect(onReset).toBeCalled(); }); it('calls onSubmit when submit is clicked', () => { - const view = shallow(); + const view = shallow(); - view.find(Button).at(1).simulate('click'); + view.find(Button).at(1).simulate('click'); - expect(onSubmit).toBeCalled(); + expect(onSubmit).toBeCalled(); }); diff --git a/packages/design-system/molecules/reset-submit/ResetSubmit.stories.tsx b/packages/design-system/molecules/reset-submit/ResetSubmit.stories.tsx index 85825f6..3879f5b 100644 --- a/packages/design-system/molecules/reset-submit/ResetSubmit.stories.tsx +++ b/packages/design-system/molecules/reset-submit/ResetSubmit.stories.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import { ResetSubmit } from './ResetSubmit'; export default { - title: 'Molecules', - component: ResetSubmit, + title: 'Molecules', + component: ResetSubmit, }; export const ResetAndSubmit = (args) => ; diff --git a/packages/design-system/molecules/reset-submit/ResetSubmit.styled.ts b/packages/design-system/molecules/reset-submit/ResetSubmit.styled.ts index fb626ce..215ca73 100644 --- a/packages/design-system/molecules/reset-submit/ResetSubmit.styled.ts +++ b/packages/design-system/molecules/reset-submit/ResetSubmit.styled.ts @@ -2,18 +2,18 @@ import { onSmallScreen } from '@roleypoly/design-system/atoms/breakpoints'; import styled from 'styled-components'; export const Buttons = styled.div` - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; `; export const Left = styled.div` - flex: 0; - ${onSmallScreen` + flex: 0; + ${onSmallScreen` flex: 1 1 100%; order: 2; `} `; export const Right = styled.div` - flex: 1; + flex: 1; `; diff --git a/packages/design-system/molecules/reset-submit/ResetSubmit.tsx b/packages/design-system/molecules/reset-submit/ResetSubmit.tsx index 0fe35b3..d044efa 100644 --- a/packages/design-system/molecules/reset-submit/ResetSubmit.tsx +++ b/packages/design-system/molecules/reset-submit/ResetSubmit.tsx @@ -5,38 +5,38 @@ import { MdRestore } from 'react-icons/md'; import styled from 'styled-components'; type Props = { - onSubmit: () => void; - onReset: () => void; + onSubmit: () => void; + onReset: () => void; }; const Buttons = styled.div` - display: flex; - flex-wrap: wrap; + display: flex; + flex-wrap: wrap; `; const Left = styled.div` - flex: 0; - ${onSmallScreen` + flex: 0; + ${onSmallScreen` flex: 1 1 100%; order: 2; `} `; const Right = styled.div` - flex: 1; + flex: 1; `; export const ResetSubmit = (props: Props) => { - return ( - - - - - - - - - ); + return ( + + + + + + + + + ); }; diff --git a/packages/design-system/molecules/role-search/RoleSearch.stories.tsx b/packages/design-system/molecules/role-search/RoleSearch.stories.tsx index 39d30d7..2cf3379 100644 --- a/packages/design-system/molecules/role-search/RoleSearch.stories.tsx +++ b/packages/design-system/molecules/role-search/RoleSearch.stories.tsx @@ -3,12 +3,12 @@ import { roleCategory } from '../../fixtures/storyData'; import { RoleSearch } from './RoleSearch'; export default { - title: 'Molecules/Role Search', - component: RoleSearch, - args: { - roles: roleCategory, - searchTerm: '', - }, + title: 'Molecules/Role Search', + component: RoleSearch, + args: { + roles: roleCategory, + searchTerm: '', + }, }; export const Search = (args) => ; diff --git a/packages/design-system/molecules/role-search/RoleSearch.tsx b/packages/design-system/molecules/role-search/RoleSearch.tsx index 397f8fb..a5a714e 100644 --- a/packages/design-system/molecules/role-search/RoleSearch.tsx +++ b/packages/design-system/molecules/role-search/RoleSearch.tsx @@ -8,50 +8,50 @@ import { GoSearch } from 'react-icons/go'; import styled from 'styled-components'; type Props = { - roles: RoleType[]; - placeholder?: string; - onSelect: (role: RoleType) => void; - onSearchUpdate: (newTerm: string) => void; - searchTerm: string; + roles: RoleType[]; + placeholder?: string; + onSelect: (role: RoleType) => void; + onSearchUpdate: (newTerm: string) => void; + searchTerm: string; }; export const RoleSearch = (props: Props) => { - const fuse = new Fuse(props.roles, { includeScore: true, keys: ['name'] }); - const results = - props.searchTerm !== '' - ? fuse.search(props.searchTerm) - : props.roles.map((role) => ({ - item: role, - })); + const fuse = new Fuse(props.roles, { includeScore: true, keys: ['name'] }); + const results = + props.searchTerm !== '' + ? fuse.search(props.searchTerm) + : props.roles.map((role) => ({ + item: role, + })); - const handleClick = (role: RoleType) => () => { - props.onSelect(role); - }; + const handleClick = (role: RoleType) => () => { + props.onSelect(role); + }; - return ( -
- } - placeholder={props.placeholder || 'Search or drag a role...'} - value={props.searchTerm} - onChange={(x) => props.onSearchUpdate(x.target.value)} - /> - - {results.map((resultRole, idx) => ( - - - - ))} -
- ); + return ( +
+ } + placeholder={props.placeholder || 'Search or drag a role...'} + value={props.searchTerm} + onChange={(x) => props.onSearchUpdate(x.target.value)} + /> + + {results.map((resultRole, idx) => ( + + + + ))} +
+ ); }; const RoleInliner = styled.div` - display: flex; - margin: 5px 0; + display: flex; + margin: 5px 0; `; diff --git a/packages/design-system/molecules/server-listing-card/ServerListingCard.stories.tsx b/packages/design-system/molecules/server-listing-card/ServerListingCard.stories.tsx index 1ef9c86..37e0253 100644 --- a/packages/design-system/molecules/server-listing-card/ServerListingCard.stories.tsx +++ b/packages/design-system/molecules/server-listing-card/ServerListingCard.stories.tsx @@ -2,11 +2,11 @@ import { roleypolyGuild } from '../../fixtures/storyData'; import { ServerListingCard } from './ServerListingCard'; export default { - title: 'Molecules/Server Listing Card', - component: ServerListingCard, - args: { - guild: { ...roleypolyGuild, permissionLevel: 4 }, - }, + title: 'Molecules/Server Listing Card', + component: ServerListingCard, + args: { + guild: { ...roleypolyGuild, permissionLevel: 4 }, + }, }; export const serverListingCard = (args) => ; diff --git a/packages/design-system/molecules/server-listing-card/ServerListingCard.styled.ts b/packages/design-system/molecules/server-listing-card/ServerListingCard.styled.ts index 4d76752..31b69bd 100644 --- a/packages/design-system/molecules/server-listing-card/ServerListingCard.styled.ts +++ b/packages/design-system/molecules/server-listing-card/ServerListingCard.styled.ts @@ -5,86 +5,86 @@ import { text200, text500 } from '@roleypoly/design-system/atoms/typography'; import styled, { css } from 'styled-components'; export const CardLine = styled.div<{ left?: boolean }>` - justify-content: center; - align-items: center; - display: flex; - padding: 5px; - box-sizing: border-box; - ${(props) => - props.left && - css` - flex: 1; - justify-content: flex-end; - align-items: flex-end; - `} + justify-content: center; + align-items: center; + display: flex; + padding: 5px; + box-sizing: border-box; + ${(props) => + props.left && + css` + flex: 1; + justify-content: flex-end; + align-items: flex-end; + `} `; export const MaxWidthTitle = styled.div` - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; export const PermissionTagStyled = styled.div<{ hiddenOnSmall?: boolean }>` - ${text200} + ${text200} - display: inline-block; - background-color: ${palette.taupe200}; - padding: 4px 6px; - border-radius: 2px; + display: inline-block; + background-color: ${palette.taupe200}; + padding: 4px 6px; + border-radius: 2px; - svg { - position: relative; - top: 1px; - ${onTablet( - css` - margin-right: 2px; - ` - )} - } + svg { + position: relative; + top: 1px; + ${onTablet( + css` + margin-right: 2px; + ` + )} + } - ${(props) => - props.hiddenOnSmall && - onSmallScreen( - css` - display: none; - ` - )} + ${(props) => + props.hiddenOnSmall && + onSmallScreen( + css` + display: none; + ` + )} `; export const CardBase = styled.div` - ${text500} + ${text500} - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - background-color: ${palette.taupe300}; - overflow-x: hidden; - text-align: center; - display: flex; - align-items: center; - padding: 10px; - border-radius: 3px; - cursor: pointer; - user-select: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + background-color: ${palette.taupe300}; + overflow-x: hidden; + text-align: center; + display: flex; + align-items: center; + padding: 10px; + border-radius: 3px; + cursor: pointer; + user-select: none; + transform: translate(0); + transition: transform ease-in-out ${transitions.actionable}s, + box-shadow ease-in-out ${transitions.actionable}s, + border-color ease-in-out ${transitions.out2in}s; + box-sizing: border-box; + max-width: 98vw; + :hover { + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); + transform: translate(0, -1px); + } + :active { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); transform: translate(0); - transition: transform ease-in-out ${transitions.actionable}s, - box-shadow ease-in-out ${transitions.actionable}s, - border-color ease-in-out ${transitions.out2in}s; - box-sizing: border-box; - max-width: 98vw; - :hover { - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); - transform: translate(0, -1px); - } - :active { - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25); - transform: translate(0); - } + } - ${onTablet(css` - flex-direction: column; - justify-content: left; - `)} + ${onTablet(css` + flex-direction: column; + justify-content: left; + `)} `; diff --git a/packages/design-system/molecules/server-listing-card/ServerListingCard.tsx b/packages/design-system/molecules/server-listing-card/ServerListingCard.tsx index f3ea336..2233cad 100644 --- a/packages/design-system/molecules/server-listing-card/ServerListingCard.tsx +++ b/packages/design-system/molecules/server-listing-card/ServerListingCard.tsx @@ -4,55 +4,55 @@ import { GuildSlug, UserGuildPermissions } from '@roleypoly/types'; import * as React from 'react'; import { GoPerson, GoStar, GoZap } from 'react-icons/go'; import { - CardBase, - CardLine, - MaxWidthTitle, - PermissionTagStyled, + CardBase, + CardLine, + MaxWidthTitle, + PermissionTagStyled, } from './ServerListingCard.styled'; type ServerListingProps = { - guild: GuildSlug; + guild: GuildSlug; }; export const ServerListingCard = (props: ServerListingProps) => ( - - - - {utils.initialsFromName(props.guild.name)} - - - {props.guild.name} - - - - + + + + {utils.initialsFromName(props.guild.name)} + + + {props.guild.name} + + + + ); const PermissionTag = (props: { permissionLevel: UserGuildPermissions }) => { - switch (props.permissionLevel) { - case UserGuildPermissions.Admin: - return ( - - - Administrator - - ); - case UserGuildPermissions.Manager: - return ( - - - Role Manager - - ); - default: - return ( - - - Member - - ); - } + switch (props.permissionLevel) { + case UserGuildPermissions.Admin: + return ( + + + Administrator + + ); + case UserGuildPermissions.Manager: + return ( + + + Role Manager + + ); + default: + return ( + + + Member + + ); + } }; diff --git a/packages/design-system/molecules/server-masthead/ServerMasthead.spec.tsx b/packages/design-system/molecules/server-masthead/ServerMasthead.spec.tsx index 9b88e05..589ad53 100644 --- a/packages/design-system/molecules/server-masthead/ServerMasthead.spec.tsx +++ b/packages/design-system/molecules/server-masthead/ServerMasthead.spec.tsx @@ -7,13 +7,13 @@ import { ServerMasthead } from './ServerMasthead'; import { Editable } from './ServerMasthead.styled'; it('shows Edit Server when editable is true', () => { - const view = shallow(); + const view = shallow(); - expect(view.find(Editable).length).not.toBe(0); + expect(view.find(Editable).length).not.toBe(0); }); it('hides Edit Server when editable is true', () => { - const view = shallow(); + const view = shallow(); - expect(view.find(Editable).length).toBe(0); + expect(view.find(Editable).length).toBe(0); }); diff --git a/packages/design-system/molecules/server-masthead/ServerMasthead.stories.tsx b/packages/design-system/molecules/server-masthead/ServerMasthead.stories.tsx index 251c28f..d4f3d8d 100644 --- a/packages/design-system/molecules/server-masthead/ServerMasthead.stories.tsx +++ b/packages/design-system/molecules/server-masthead/ServerMasthead.stories.tsx @@ -3,15 +3,15 @@ import { guild } from '../../fixtures/storyData'; import { ServerMasthead } from './ServerMasthead'; export default { - title: 'Molecules/Server Masthead', - args: { - editable: false, - guild, - }, + title: 'Molecules/Server Masthead', + args: { + editable: false, + guild, + }, }; export const Default = (args) => ; export const Editable = (args) => ; Editable.args = { - editable: true, + editable: true, }; diff --git a/packages/design-system/molecules/server-masthead/ServerMasthead.styled.ts b/packages/design-system/molecules/server-masthead/ServerMasthead.styled.ts index db04146..27be99b 100644 --- a/packages/design-system/molecules/server-masthead/ServerMasthead.styled.ts +++ b/packages/design-system/molecules/server-masthead/ServerMasthead.styled.ts @@ -3,34 +3,34 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled from 'styled-components'; export const Wrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; `; export const Name = styled.div` - margin: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - display: flex; - flex-wrap: wrap; - flex-direction: column; - justify-content: flex-start; + margin: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: flex; + flex-wrap: wrap; + flex-direction: column; + justify-content: flex-start; `; export const Icon = styled.div` - flex-shrink: 0; + flex-shrink: 0; `; export const Editable = styled.div` - color: ${palette.taupe500}; - display: flex; - align-items: center; - user-select: none; - transition: color ${transitions.actionable}s ease-in-out; - cursor: pointer; - &:hover { - color: ${palette.taupe600}; - } + color: ${palette.taupe500}; + display: flex; + align-items: center; + user-select: none; + transition: color ${transitions.actionable}s ease-in-out; + cursor: pointer; + &:hover { + color: ${palette.taupe600}; + } `; diff --git a/packages/design-system/molecules/server-masthead/ServerMasthead.tsx b/packages/design-system/molecules/server-masthead/ServerMasthead.tsx index af72c82..b05465b 100644 --- a/packages/design-system/molecules/server-masthead/ServerMasthead.tsx +++ b/packages/design-system/molecules/server-masthead/ServerMasthead.tsx @@ -1,8 +1,8 @@ import { Avatar, utils } from '@roleypoly/design-system/atoms/avatar'; import { - AccentTitle, - AmbientLarge, - CompletelyStylelessLink, + AccentTitle, + AmbientLarge, + CompletelyStylelessLink, } from '@roleypoly/design-system/atoms/typography'; import { GuildSlug } from '@roleypoly/types'; import * as React from 'react'; @@ -10,33 +10,33 @@ import { GoPencil } from 'react-icons/go'; import { Editable, Icon, Name, Wrapper } from './ServerMasthead.styled'; export type ServerMastheadProps = { - guild: GuildSlug; - editable: boolean; + guild: GuildSlug; + editable: boolean; }; export const ServerMasthead = (props: ServerMastheadProps) => { - return ( - - - - {utils.initialsFromName(props.guild.name)} - - - - {props.guild.name} - {props.editable && ( - - - -   Edit Server - - - )} - - - ); + return ( + + + + {utils.initialsFromName(props.guild.name)} + + + + {props.guild.name} + {props.editable && ( + + + +   Edit Server + + + )} + + + ); }; diff --git a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx index 973c986..bf60186 100644 --- a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx +++ b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.stories.tsx @@ -4,16 +4,16 @@ import { user } from '../../fixtures/storyData'; import { UserAvatarGroup } from './UserAvatarGroup'; export default { - title: 'Molecules/User Avatar Group', - component: UserAvatarGroup, - args: { - user, - preventCollapse: true, - }, + title: 'Molecules/User Avatar Group', + component: UserAvatarGroup, + args: { + user, + preventCollapse: true, + }, }; export const Default = (args) => ( - - - + + + ); diff --git a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.styled.ts b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.styled.ts index 3ed5614..cb567ac 100644 --- a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.styled.ts +++ b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.styled.ts @@ -3,27 +3,27 @@ import { palette } from '@roleypoly/design-system/atoms/colors'; import styled, { css } from 'styled-components'; export const Collapse = styled.div<{ preventCollapse: boolean }>` - ${(props) => - !props.preventCollapse && - onSmallScreen(css` - display: none; - `)} + ${(props) => + !props.preventCollapse && + onSmallScreen(css` + display: none; + `)} `; export const Group = styled.div` - display: flex; - align-items: center; - justify-content: flex-end; - white-space: nowrap; + display: flex; + align-items: center; + justify-content: flex-end; + white-space: nowrap; `; export const Discriminator = styled.span` - color: ${palette.taupe500}; - font-size: 75%; - padding: 0 5px; + color: ${palette.taupe500}; + font-size: 75%; + padding: 0 5px; `; export const GroupText = styled.span` - position: relative; - top: -2px; + position: relative; + top: -2px; `; diff --git a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.tsx b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.tsx index 9f7e60d..763a837 100644 --- a/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.tsx +++ b/packages/design-system/molecules/user-avatar-group/UserAvatarGroup.tsx @@ -4,25 +4,25 @@ import * as React from 'react'; import { Collapse, Discriminator, Group, GroupText } from './UserAvatarGroup.styled'; type Props = { - user: DiscordUser; - preventCollapse?: boolean; + user: DiscordUser; + preventCollapse?: boolean; }; export const UserAvatarGroup = (props: Props) => ( - - - - {props.user.username} - #{props.user.discriminator} - -   - - - {utils.initialsFromName(props.user.username)} - - + + + + {props.user.username} + #{props.user.discriminator} + +   + + + {utils.initialsFromName(props.user.username)} + + ); diff --git a/packages/design-system/molecules/user-popover/UserPopover.stories.tsx b/packages/design-system/molecules/user-popover/UserPopover.stories.tsx index a8eb7a5..0a5b10b 100644 --- a/packages/design-system/molecules/user-popover/UserPopover.stories.tsx +++ b/packages/design-system/molecules/user-popover/UserPopover.stories.tsx @@ -4,15 +4,15 @@ import { user } from '../../fixtures/storyData'; import { UserPopover as UserPopoverComponent } from './UserPopover'; export default { - title: 'Molecules/User Popover', - component: UserPopoverComponent, - args: { - user, - }, + title: 'Molecules/User Popover', + component: UserPopoverComponent, + args: { + user, + }, }; export const UserPopover = (args) => ( - - - + + + ); diff --git a/packages/design-system/molecules/user-popover/UserPopover.styled.ts b/packages/design-system/molecules/user-popover/UserPopover.styled.ts index ede63ff..c0a010b 100644 --- a/packages/design-system/molecules/user-popover/UserPopover.styled.ts +++ b/packages/design-system/molecules/user-popover/UserPopover.styled.ts @@ -3,31 +3,31 @@ import { transitions } from '@roleypoly/design-system/atoms/timings'; import styled from 'styled-components'; export const Base = styled.div` - text-align: right; - display: flex; - flex-direction: column; - user-select: none; + text-align: right; + display: flex; + flex-direction: column; + user-select: none; `; export const NavAction = styled.div` - height: 2.25em; - display: flex; - align-items: center; - justify-content: flex-end; - transition: color ${transitions.actionable}s ease-in-out; - color: ${palette.taupe500}; - box-sizing: border-box; + height: 2.25em; + display: flex; + align-items: center; + justify-content: flex-end; + transition: color ${transitions.actionable}s ease-in-out; + color: ${palette.taupe500}; + box-sizing: border-box; - &:hover { - cursor: pointer; - color: ${palette.taupe600}; - } + &:hover { + cursor: pointer; + color: ${palette.taupe600}; + } - svg { - font-size: 120%; - box-sizing: content-box; - padding: 5px 8px; - position: relative; - top: 0.1em; - } + svg { + font-size: 120%; + box-sizing: content-box; + padding: 5px 8px; + position: relative; + top: 0.1em; + } `; diff --git a/packages/design-system/molecules/user-popover/UserPopover.tsx b/packages/design-system/molecules/user-popover/UserPopover.tsx index 1225022..27a4454 100644 --- a/packages/design-system/molecules/user-popover/UserPopover.tsx +++ b/packages/design-system/molecules/user-popover/UserPopover.tsx @@ -6,21 +6,21 @@ import { GoGear, GoSignOut } from 'react-icons/go'; import { Base, NavAction } from './UserPopover.styled'; type UserPopoverProps = { - user: DiscordUser; + user: DiscordUser; }; export const UserPopover = (props: UserPopoverProps) => ( - - - - - Settings - - - - - Log Out - - - + + + + + Settings + + + + + Log Out + + + ); diff --git a/packages/design-system/organisms/app-shell/AppShell.stories.tsx b/packages/design-system/organisms/app-shell/AppShell.stories.tsx index 4b3fca6..e657755 100644 --- a/packages/design-system/organisms/app-shell/AppShell.stories.tsx +++ b/packages/design-system/organisms/app-shell/AppShell.stories.tsx @@ -3,18 +3,18 @@ import { mastheadSlugs, user } from '../../fixtures/storyData'; import { AppShell } from './AppShell'; export default { - title: 'Organisms/App Shell', - component: AppShell, + title: 'Organisms/App Shell', + component: AppShell, }; export const Guest = () => ( - -

Hello World

-
+ +

Hello World

+
); export const LoggedIn = () => ( - -

Hello World

-
+ +

Hello World

+
); diff --git a/packages/design-system/organisms/app-shell/AppShell.styled.tsx b/packages/design-system/organisms/app-shell/AppShell.styled.tsx index 0ad40d9..9ff0c68 100644 --- a/packages/design-system/organisms/app-shell/AppShell.styled.tsx +++ b/packages/design-system/organisms/app-shell/AppShell.styled.tsx @@ -3,11 +3,11 @@ import { fontCSS } from '@roleypoly/design-system/atoms/fonts'; import styled, { createGlobalStyle } from 'styled-components'; export const Content = styled.div<{ small?: boolean }>` - margin: 0 auto; - margin-top: 50px; - width: ${(props) => (props.small ? '960px' : '1024px')}; - max-width: 98vw; - max-height: calc(100vh - 50px); + margin: 0 auto; + margin-top: 50px; + width: ${(props) => (props.small ? '960px' : '1024px')}; + max-width: 98vw; + max-height: calc(100vh - 50px); `; export const GlobalStyles = createGlobalStyle` diff --git a/packages/design-system/organisms/app-shell/AppShell.tsx b/packages/design-system/organisms/app-shell/AppShell.tsx index 7154d57..ab8b771 100644 --- a/packages/design-system/organisms/app-shell/AppShell.tsx +++ b/packages/design-system/organisms/app-shell/AppShell.tsx @@ -7,38 +7,38 @@ import { Scrollbars } from 'react-custom-scrollbars'; import { Content, GlobalStyles } from './AppShell.styled'; export type AppShellProps = { - children: React.ReactNode; - user?: DiscordUser; - showFooter?: boolean; - small?: boolean; - activeGuildId?: string | null; - guilds?: GuildSlug[]; - recentGuilds?: string[]; - disableGuildPicker?: boolean; + children: React.ReactNode; + user?: DiscordUser; + showFooter?: boolean; + small?: boolean; + activeGuildId?: string | null; + guilds?: GuildSlug[]; + recentGuilds?: string[]; + disableGuildPicker?: boolean; }; export const AppShell = (props: AppShellProps) => ( - <> - - - {props.user ? ( - - ) : ( - - )} - - {props.children} - {props.showFooter &&