diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..ca0889094 --- /dev/null +++ b/.babelrc @@ -0,0 +1,28 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "browsers": [ + "last 2 Chrome versions", + "last 2 Firefox versions", + "Firefox ESR", + "last 3 Safari versions", + "last 2 Edge versions", + "last 3 iOS versions", + "last 2 ChromeAndroid versions", + "last 2 FirefoxAndroid versions" + ] + } + } + ], + "@babel/preset-flow", + "@babel/preset-react", + "@babel/preset-typescript" + ], + "plugins": [ + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-flow-strip-types" + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..65a8997cb --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,154 @@ +version: 2.1 +executors: + node: + docker: + - image: cimg/node:18.16.1 + +defaults: &defaults + working_directory: ~/repo + parallelism: 1 + executor: + name: node + +aliases: + - &restore_npm_cache + restore_cache: + keys: + - npm-v7-dependency-{{ checksum "package-lock.json" }} + - npm-v7-dependency- + +jobs: + build: + <<: *defaults + + steps: + - checkout + + - persist_to_workspace: + root: ~/ + paths: + - repo + + - *restore_npm_cache + + - run: + name: install node dependencies + command: | + npm install + + - run: + name: build package + command: | + npm run build + + - save_cache: + paths: + - ./node_modules + key: npm-v7-dependency-{{ checksum "package-lock.json" }} + + eslint_flow: + <<: *defaults + + steps: + - attach_workspace: + at: ~/ + + - *restore_npm_cache + + - run: + name: lint check for javascript + command: | + npm run lint --max-warnings=0 + + - store_test_results: + path: ./tmp/eslint + + stylelint: + <<: *defaults + + steps: + - attach_workspace: + at: ~/ + + - *restore_npm_cache + + - run: + name: stylelint + command: | + npm run stylelint + + - store_test_results: + path: ./tmp/stylelint + + build_storybook: + <<: *defaults + resource_class: medium+ + + steps: + - attach_workspace: + at: ~/ + + - *restore_npm_cache + + - run: + name: build + command: | + mkdir -p /tmp/storybook + npm run build-storybook -- -o /tmp/storybook + + - store_artifacts: + path: /tmp/storybook + + - run: + name: comment to pr + command: | + # PR トリガーではない場合はスキップ + if [ -z "$CIRCLE_PR_NUMBER" ]; then exit 0; fi + + # https://support.circleci.com/hc/en-us/articles/5034956515355-How-to-Programmatically-Construct-the-URLs-for-Artifacts + index_html_url="https://output.circle-artifacts.com/output/job/${CIRCLE_WORKFLOW_JOB_ID}/artifacts/${CIRCLE_NODE_INDEX}/tmp/storybook/index.html" + + # https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment + curl \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/issues/${CIRCLE_PR_NUMBER}/comments \ + -d '{"body":"### Storybook\n'${index_html_url}'"}' + + test: + <<: *defaults + + steps: + - attach_workspace: + at: ~/ + + - *restore_npm_cache + + - run: + name: test + command: npm test + + - store_test_results: + path: ./tmp/test + +workflows: + version: 2 + integration: + jobs: + - build + - eslint_flow: + requires: + - build + - stylelint: + requires: + - build + - build_storybook: + context: + - github_token_sushi_bot + requires: + - build + - test: + requires: + - build diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d5700888a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules/ + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..73bf5d8c1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..178135c2b --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/dist/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..332ccf574 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,79 @@ +module.exports = { + parser: '@babel/eslint-parser', + env: { + browser: true, + jest: true, + node: true + }, + plugins: ['react-hooks'], + globals: {}, + extends: ['eslint:recommended', 'plugin:compat/recommended', 'plugin:import/recommended', 'plugin:jsx-a11y/recommended', 'plugin:react/recommended', 'plugin:storybook/recommended'], + settings: { + react: { + version: 'detect' + }, + 'import/resolver': { + node: { + extensions: ['.js', '.ts', '.tsx'] + } + }, + // TODO: browserslist を設定するか各 polyfill に対応 + polyfills: ['window.scrollX', 'window.scrollY', 'Array.from'] + }, + rules: { + 'no-unused-vars': ['error', { + vars: 'all', + args: 'all', + argsIgnorePattern: '^_' + }], + // TODO: ログインなどの要素に使えるようオフにしているが、要検討 + 'jsx-a11y/no-autofocus': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error' + }, + overrides: [{ + plugins: ['ft-flow'], + files: ['*.js', '*.js.flow'], + extends: ['plugin:ft-flow/recommended'], + rules: { + 'ft-flow/generic-spacing': 'off', + 'ft-flow/space-after-type-colon': 'off' + } + }, { + files: ['*.ts', '*.tsx'], + extends: ['eslint-config-freee-typescript'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + 'jsx-a11y/no-autofocus': 'off', + '@typescript-eslint/naming-convention': ['error', { + selector: 'default', + format: ['camelCase', 'PascalCase'] + }, { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE', 'PascalCase'] + }, { + selector: 'parameter', + format: ['camelCase', 'snake_case', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allow' + }, { + selector: 'property', + format: null + }, { + selector: 'memberLike', + modifiers: ['private'], + format: ['camelCase'], + leadingUnderscore: 'require' + }, { + selector: 'typeLike', + format: ['PascalCase'] + }] + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.ts', '.tsx'] + } + } + } + }] +}; \ No newline at end of file diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..b9432db08 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,20 @@ +[ignore] +.*/__tests__/types/.* +lv1/.* +lv2/.* +utilities/.* +hooks/.* + + +[include] + +[libs] + +[lints] + +[options] +server.max_workers=4 +module.name_mapper='^vibes\(.*\)$' -> '\1' +sharedmemory.hash_table_pow=21 + +[strict] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..7e0d136b7 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ + + +## :memo: 概要 + + +## :stuck_out_tongue: やってないこと + + +## :heavy_check_mark: 動作確認 + +- [ ] Storybook で 〇〇 が XX できるのを確認 + diff --git a/.github/workflows/publish_storybook.yml b/.github/workflows/publish_storybook.yml new file mode 100644 index 000000000..88b94596f --- /dev/null +++ b/.github/workflows/publish_storybook.yml @@ -0,0 +1,31 @@ +name: publish Storybook +on: + push: + branches: + - 'main' +jobs: + publish: + if: github.repository == 'freee/vibes' + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: 'ap-northeast-1' + role-to-assume: ${{ secrets.PROD_AWS_ROLE_TO_ASSUME }} + # - uses: actions/setup-node@v3 + # with: + # node-version-file: .node-version + # cache: 'npm' + # - name: Install Dependencies + # run: npm install + # - name: Build Storybook + # run: npm run build-storybook + - name: Publish to S3 + run: aws s3 sync --delete storybook-static ${{ secrets.PROD_AWS_S3_BUCKET }} + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8173db1b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +node_modules +*.log +.DS_Store +.idea/ +.cache +.vscode +storybook-static/ +dist/ + +# docker +docker/.build_timestamp + +# direnv / dotenv +.envrc +.env + +# eslint cache +.eslintcache diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..36af21989 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx lint-staged diff --git a/.jest/Mock.js b/.jest/Mock.js new file mode 100644 index 000000000..77d604170 --- /dev/null +++ b/.jest/Mock.js @@ -0,0 +1,18 @@ +export default { + use: () => {}, + unuse: () => {} +}; + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); \ No newline at end of file diff --git a/.jest/setup.js b/.jest/setup.js new file mode 100644 index 000000000..5d72226ba --- /dev/null +++ b/.jest/setup.js @@ -0,0 +1 @@ +import 'jest-canvas-mock'; diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..3876fd498 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18.16.1 diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..1dab4ed4c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact = true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..178135c2b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +/dist/ diff --git a/.storybook/logo-vibes.svg b/.storybook/logo-vibes.svg new file mode 100644 index 000000000..e2bdeaf08 --- /dev/null +++ b/.storybook/logo-vibes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 000000000..1754a05ce --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,65 @@ +module.exports = { + addons: [ + '@storybook/addon-knobs', + '@storybook/addon-essentials', + '@storybook/addon-links', + '@storybook/addon-a11y', + '@storybook/addon-storysource', + '@kemuridama/storybook-addon-github', + ], + stories: [ + '../docs/**/*.stories.mdx', + '../src/**/*.stories.tsx', + '../examples/**/*.stories.tsx', + ], + typescript: { + check: true, + }, + webpackFinal: (config) => ({ + ...config, + module: { + ...config.module, + rules: [ + ...config.module.rules, + { + test: /\.stories\.tsx?$/, + use: [require.resolve('@storybook/source-loader')], + enforce: 'pre', + }, + { + test: /\.scss$/, + use: [ + { + loader: 'style-loader', + }, + { + loader: 'css-loader', + }, + { + loader: 'sass-loader', + options: { + implementation: require('sass'), + }, + }, + ], + }, + { + test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/, + use: { + loader: 'file-loader', + options: { + name: './fonts/[name].[ext]', + }, + }, + }, + ], + }, + }), + framework: { + name: '@storybook/react-webpack5', + options: {}, + }, + docs: { + autodocs: true, + }, +}; diff --git a/.storybook/manager.js b/.storybook/manager.js new file mode 100644 index 000000000..182ce3dc7 --- /dev/null +++ b/.storybook/manager.js @@ -0,0 +1,11 @@ +import { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import logo from '../.storybook/logo-vibes.svg'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'vibes', + brandImage: logo, + }), +}) diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 000000000..22b1b1646 --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,75 @@ +import * as React from 'react'; +import { DocsContainer } from '@storybook/addon-docs'; +import '../stylesheets/freee.scss'; +import '../stylesheets/vibes_2021.scss'; +import { VibesProvider, useLang } from '../src/utilities/VibesProvider'; +import ReactDOM from 'react-dom'; + +const LanguagePortal = React.forwardRef(({ lang }, ref) => + ReactDOM.createPortal(
, document.body) +); + +export const decorators = [ + (Story, context) => { + const portalParentRef = React.useRef(); + return ( + +
+ +
+ +
+ ); + }, +]; + +export const parameters = { + viewMode: 'docs', + docs: { + container: ({ children, context }) => ( +
+ {children} +
+ ), + }, + options: { + storySort: { + order: ['doc', ['Readme'], 'examples', 'lv2', 'lv1', 'deprecated'], + }, + }, + github: { + repository: 'freee/vibes', + branch: 'master', + }, +}; + +export const globalTypes = { + responsive: { + description: 'Responsive', + defaultValue: 'Off', + toolbar: { + title: 'Responsive', + icon: 'circlehollow', + items: [ + { value: true, title: 'On' }, + { value: false, title: 'Off' }, + ], + }, + }, + lang: { + description: 'Language', + defaultValue: 'ja', + toolbar: { + title: 'Language', + icon: 'globe', + items: [ + { value: 'ja', title: '日本語 (ja)' }, + { value: 'en', title: 'English (en)' }, + ], + }, + }, +}; diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..02438a537 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM node:14.15.4 +ARG github_username +ARG github_token + +ENV GITHUB_USERNAME $github_username +ENV GITHUB_TOKEN $github_token + +WORKDIR /usr/src/app + +COPY docker/git-credential-github-token /usr/local/bin +RUN apt-get update && apt-get install -y curl git +RUN mkdir /root/.ssh/ && \ + git config --global url."https://github.com".insteadOf ssh://git@github.com && \ + git config --global --add url."https://github.com".insteadOf git://git@github.com && \ + git config --global --add url."https://github.com/".insteadOf git@github.com: && \ + git config --global credential.helper github-token + +COPY package.json ./ +COPY package-lock.json ./ +RUN npm install + +COPY . . + +EXPOSE 6006 + +CMD ["npm", "run", "storybook"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..5dc03f475 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 freee K.K. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..c6a678203 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +APP_NAME := vibes +VERSION_TAG ?= latest +IMAGE_NAME := $(APP_NAME):$(VERSION_TAG) + +.DEFAULT_GOAL = help # display usage when there is no argument. + +DOCKER_DIR := docker +DOCKER_BUILD_TIMESTAMP := docker/.build_timestamp + +$(DOCKER_BUILD_TIMESTAMP): $(shell find ./docker -not -name .build_timestamp) + @docker build -t $(IMAGE_NAME) --build-arg github_username=$(GITHUB_USERNAME) --build-arg github_token=$(GITHUB_TOKEN) . + touch $(DOCKER_BUILD_TIMESTAMP) + +.PHONY: help +help: ## show options + @grep -E '^[a-zA-Z_-{\.}]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' + +.PHONY: docker.build +docker.build: $(DOCKER_BUILD_TIMESTAMP) ## build docker image + +.PHONY: docker.rebuild +docker.rebuild: ## rebuild docker image + -rm $(DOCKER_BUILD_TIMESTAMP) + make docker.build + +.PHONY: docker.run +docker.run: $(DOCKER_BUILD_TIMESTAMP) ## run storybook on docker + docker run -it -p 6006:6006 --rm --name $(APP_NAME) $(IMAGE_NAME) diff --git a/README.md b/README.md new file mode 100644 index 000000000..875a949aa --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +[世界を変えるためのデザインシステム](https://speakerdeck.com/ymrl/shi-jie-wobian-erutamefalsedezainsisutemu)です + +# Getting started + +## Install + +TBD + +## Usage + +スタイルを適用するため node_modules/vibes/vibes.css を読み込んでください。 + +from Sass: + +``` +@import 'node_modules/vibes/vibes_2021'; +``` + +from JavaScript with CSS Modules: + +```js +import '@freee/vibes/css'; +``` + +スタイルを読み込んだら、Vibes の React Component を次のように使うだけです。 + +```js +import * as React from 'react'; +import { Breadcrumbs } from '@freee/vibes'; +import { CompanyLogoT } from 'somewhere'; + +export default function App() { + return ( +
+ + +
+ ); +} +``` + +# Contribution + +[こちらのガイドライン](https://github.com/freee/vibes/blob/master/docs/Contribution.stories.mdx)を参照してください。 diff --git a/__tests__/types/flow/.flowconfig b/__tests__/types/flow/.flowconfig new file mode 100644 index 000000000..a55f4d9c6 --- /dev/null +++ b/__tests__/types/flow/.flowconfig @@ -0,0 +1,26 @@ +[ignore] + +[include] +./../../../index.js +./../../../lv1.js +./../../../lv2.js +./../../../utilities.js +./../../../node_modules/lottie-web/.* +./../../../node_modules/react/.* +./../../../node_modules/react-dom/.* +./../../../node_modules/react-icons/.* +./../../../node_modules/react-modal/.* +./../../../node_modules/react-dnd/.* +./../../../node_modules/react-dnd-html5-backend/.* + +[libs] + +[lints] + +[options] +server.max_workers=4 +module.name_mapper='^vibes\(.*\)$' -> '/../../..\1' +include_warnings=true +suppress_comment= \\(.\\|\n\\)*\\$FlowExpectedError + +[strict] diff --git a/__tests__/types/flow/lv1.js b/__tests__/types/flow/lv1.js new file mode 100644 index 000000000..bd29f9661 --- /dev/null +++ b/__tests__/types/flow/lv1.js @@ -0,0 +1,896 @@ +// @flow + +import * as React from 'react'; +import { + FocusHighlight, + VisuallyHidden, + Balloon, + ColumnBase, + Container, + ContentsBase, + DialogBase, + FloatingBase, + MarginBase, + NegativeMarginBase, + PopupBase, + CardBase, + ScrimBase, + ScrollableBase, + ZebraBase, + Button, + GlobalNaviButton, + IconOnlyButton, + IconOnlyJumpButton, + IconOnlyBackwardButton, + InlineLink, + JumpButton, + BackwardButton, + LeftIconButton, + ListButton, + PagerButton, + RightIconButton, + TabButton, + TextButton, + CheckBox, + RadioButton, + SearchField, + SelectBox, + TextArea, + TextField, + ToggleButton, + OptionButton, + FormControlLabel, + GridBlock, + GridWrapper, + Note, + PageTitle, + Paragraph, + SectionTitle, + SubSectionTitle, + Text, + Avatar, + MaterialIcon, + RequiredIcon, + StatusIcon, + AlertSwallow, + AppStoreBadge, + CloudSkeletonIllust, + CloudUploadIllust, + CsvUploadIllust, + FileUploadIllust, + GooglePlayBadge, + ImageUploadIllust, + NotFoundSwallow, + CalendarDate, + SegmentControlButton, + StepNumber, + StepBlock, + StepBorder, + Tab, + WithDescriptionContent, + WithSideContent, + Stack, + BorderTableListCell, + CheckBoxCell, + DescriptionListCell, + DescriptionListHeadCell, + TableListCell, + TableListHead, + TableListHeadCell, + TableListRow, + Message, + ProgressBar, + Loading, + InlineSpinner, + // eslint-disable-next-line import/no-unresolved +} from 'vibes/index'; + +const marginProps = { + marginLeft: true, + marginRight: true, + marginBottom: true, + marginTop: true, + marginSize: 'large', +}; + +const commonProps = { + 'data-guide': 'guide', + 'data-test': 'test', + 'data-tracking': 'tracking', + 'data-masking': true, + ma: 1, + mt: 1, + mb: 1, + mr: 1, + ml: 1, +}; + +const inputHandlers = { + onChange: (_e: SyntheticInputEvent) => {}, + onInput: (_e: SyntheticInputEvent) => {}, + onFocus: (_e: SyntheticFocusEvent) => {}, + onBlur: (_e: SyntheticFocusEvent) => {}, + onKeyDown: (_e: SyntheticKeyboardEvent) => {}, + onKeyUp: (_e: SyntheticKeyboardEvent) => {}, +}; + +const buttonAriaProps = { + 'aria-expanded': true, + 'aria-pressed': true, + 'aria-controls': 'hoge', + 'aria-owns': 'hoge', + 'aria-haspopup': true, + 'aria-describedby': 'hoge', +}; +const linkAriaProps = { + 'aria-expanded': true, + 'aria-controls': 'hoge', + 'aria-owns': 'hoge', + 'aria-haspopup': true, + 'aria-describedby': 'hoge', +}; +const numberInputAriaProps = { + 'aria-valuemin': 0, + 'aria-valuemax': 100, + 'aria-valuenow': 10, +}; +const numberInputProps = { + min: 0, + max: 100, + step: 10, +}; + +const tableRowAriaProps = { + 'aria-level': 1, + 'aria-setsize': 1, + 'aria-posinset': 1, + 'aria-expanded': false, +}; + +// a11y +// $FlowExpectedError +; + +
+; + +
+; + +
+; + +export default function App() { + return ( +
+ {/* bases */} + + balloon + + +
+ + +
+
+ + +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + ) => {}} + onSelfWindowNavigation={() => {}} + {...commonProps} + {...marginProps} + > +
+ + +
+ + + +
+ + {/* buttons */} + + {}} + {...marginProps} + {...commonProps} + > +
+ + {}} + onClick={( + _e: SyntheticEvent + ) => {}} + onKeyDown={( + _e: SyntheticKeyboardEvent + ) => {}} + {...marginProps} + {...commonProps} + {...buttonAriaProps} + {...linkAriaProps} + /> + ) => {}} + onSelfWindowNavigation={() => {}} + rel="test" + {...commonProps} + /> + ) => {}} + onSelfWindowNavigation={() => {}} + rel="test" + {...commonProps} + /> + {}} + onClick={( + _e: SyntheticEvent + ) => {}} + {...buttonAriaProps} + {...linkAriaProps} + {...commonProps} + > +
+ + ) => {}} + onSelfWindowNavigation={() => {}} + rel="test" + {...marginProps} + {...commonProps} + > +
+ + ) => {}} + onSelfWindowNavigation={() => {}} + rel="test" + {...marginProps} + {...commonProps} + > +
+ + ) => {}} + {...marginProps} + {...commonProps} + > +
+ + + ) => {}} + onKeyDown={( + _e: SyntheticKeyboardEvent + ) => {}} + selectableItemRef={React.createRef< + HTMLAnchorElement | HTMLButtonElement + >()} + {...marginProps} + {...commonProps} + > +
+ + ) => {}} + small + {...marginProps} + {...commonProps} + > +
+ + ) => {}} + {...marginProps} + {...commonProps} + > +
+ + +
+ + + ) => {}} + {...buttonAriaProps} + {...marginProps} + {...commonProps} + > +
+ + {/* forms */} + +
+ + +
+ + + ) => {}} + onInput={(_e: SyntheticInputEvent) => {}} + onFocus={(_e: SyntheticFocusEvent) => {}} + onBlur={(_e: SyntheticFocusEvent) => {}} + onKeyDown={(_e: SyntheticKeyboardEvent) => {}} + onKeyUp={(_e: SyntheticKeyboardEvent) => {}} + {...marginProps} + {...commonProps} + /> + + ); +}; + +const TextArea = React.forwardRef(TextAreaInner); +export default TextArea; diff --git a/src/lv1/forms/TextField.stories.tsx b/src/lv1/forms/TextField.stories.tsx new file mode 100644 index 000000000..1b515385c --- /dev/null +++ b/src/lv1/forms/TextField.stories.tsx @@ -0,0 +1,281 @@ +import * as React from 'react'; + +import { MdDateRange, MdExpandMore, MdFavorite } from 'react-icons/md'; +import { action, actions } from '@storybook/addon-actions'; +import { boolean, number, select, text } from '@storybook/addon-knobs'; +import { commonKnobs } from '../../../stories'; +import TextField from './TextField'; +import ColumnBase from '../bases/ColumnBase'; +import { FocusHighlight } from '../a11y/FocusHighlight'; +import CheckBox from './CheckBox'; +import Paragraph from '../typography/Paragraph'; + +const handlers = actions({ + onChange: 'change', + onInput: 'input', + onFocus: 'focus', + onBlur: 'blur', + onKeyDown: 'keydown', + onKeyUp: 'keyup', +}); + +export default { + component: TextField, +}; + +export const TextFieldComponent = () => { + const ref = React.createRef(); + return ( + + ); +}; + +export const Widths = () => ( + <> + + + + + + +); + +export const Default = () => ( + <> + + + + +); + +export const Small = () => ( + <> + + + + +); + +export const Large = () => ( + <> + + + + +); + +export const MaxWidth = () => ( +
+ + + +
+); + +export const Suffix = () => ( + <> + + + + +); + +export const Borderless = () => ( + <> + + + + + +); + +export const WithIcon = () => ( + <> + + テキストフィールドの右脇にアイコンを表示できますが、これはコンボボックス等の動作を示すためのものです。 + 通常のフィールドには使用しないでください。 + + + + + + + + +); + +export const WithLoading = () => { + const [loading, setLoading] = React.useState(true); + return ( + <> + + setLoading(e.target.checked)} + checked={loading} + > + Loading + + + ); +}; + +export const DeprecatedMarginProps = () => ( + +); diff --git a/src/lv1/forms/TextField.tsx b/src/lv1/forms/TextField.tsx new file mode 100644 index 000000000..221d08551 --- /dev/null +++ b/src/lv1/forms/TextField.tsx @@ -0,0 +1,409 @@ +import * as React from 'react'; +import { MarginClassProps } from '../../utilities/marginClasses'; +import commonProps, { + CommonProps, + pickCommonDataProps, +} from '../../utilities/commonProps'; +import vbClassNames from '../../utilities/vbClassNames'; +import functionalMarginClasses from '../../utilities/functionalMarginClasses'; +import { FormHandlers, AutocompleteAttribute } from './types'; +import { + filterTextBoxAriaProps, + TextBoxAriaProps, + filterNumberInputAriaProps, + NumberInputAriaProps, + ButtonAriaProps, +} from '../../utilities/AriaProps'; +import InlineSpinner from '../InlineSpinner'; +import { useResponsive } from '../../utilities/VibesProvider'; + +export type TextFieldType = 'text' | 'email' | 'password' | 'number' | 'tel'; +export type TextFieldWidth = 'xSmall' | 'small' | 'medium' | 'large' | 'full'; + +type NumberInputProps = { + min?: number; + max?: number; + step?: number; + /** + * type="number" 時に右側に表示される spinner を非表示にします + * @default false + */ + hideSpinner?: boolean; +}; + +type Props = { + /** + * input id を指定します + */ + id?: string; + /** + * input type を指定します。 + * + * `tel` は電話番号の入力欄以外に使用しないでください(IMEをオフにするために使用しないでください) + */ + type?: TextFieldType; + /** + * input要素のaria-label を指定します。 `