From d56c4a857bfcebb16398259cdf7bf68d3dfd707d Mon Sep 17 00:00:00 2001 From: hirotomoyamada Date: Wed, 9 Oct 2024 10:45:53 +0900 Subject: [PATCH] feat(.eslint): added new eslint configurations and rules --- .env.example | 1 - .eslint/base.ts | 65 + .eslint/cspell.ts | 18 + .eslint/import.ts | 47 + .eslint/index.ts | 10 + .eslint/jsx-a11y.ts | 14 + .eslint/next.ts | 18 + .eslint/perfectionist.ts | 133 + .eslint/react-hooks.ts | 15 + .eslint/react.ts | 26 + .eslint/shared.ts | 11 + .eslint/typescript.ts | 75 + .prettierignore | 1 - @types/@clack-prompts.d.ts | 4 +- @types/@next-eslint-plugin-next.d.ts | 1 + @types/component.d.ts | 50 +- @types/eslint-config-prettier.d.ts | 1 + @types/eslint-plugin-import.d.ts | 1 + @types/eslint-plugin-jsx-a11y.d.ts | 1 + @types/eslint-plugin-react-hooks.d.ts | 1 + @types/eslint-plugin-react.d.ts | 1 + @types/eslint-plugin-spellcheck.d.ts | 1 + @types/next.d.ts | 13 +- @types/search.d.ts | 12 +- README.md | 4 +- components/data-display/authors.tsx | 18 +- components/forms/color-mode-button.tsx | 26 +- components/forms/copy-button.tsx | 66 +- components/forms/index.ts | 4 +- components/forms/search.tsx | 161 +- components/forms/theme-scheme-button.tsx | 40 +- components/layouts/footer.tsx | 14 +- components/layouts/header.tsx | 110 +- components/layouts/index.ts | 2 +- components/media-and-icons/discord.tsx | 4 +- components/media-and-icons/github.tsx | 6 +- components/media-and-icons/index.ts | 6 +- components/media-and-icons/layout.tsx | 16 +- components/media-and-icons/logo.tsx | 2 +- components/media-and-icons/seo.tsx | 8 +- components/media-and-icons/twitter.tsx | 6 +- components/media-and-icons/x.tsx | 4 +- components/media-and-icons/yamada-ui.tsx | 4 +- components/navigation/next-link.tsx | 20 +- components/navigation/tree.tsx | 135 +- components/overlay/mobile-menu.tsx | 44 +- constant/index.ts | 2 +- constant/menu.ts | 16 +- constant/seo.ts | 18 +- constant/sns.ts | 6 +- contexts/app-context.tsx | 18 +- contexts/i18n-context.tsx | 41 +- cspell.json | 13 +- eslint.config.mjs | 319 -- eslint.config.ts | 65 + hooks/use-download.ts | 8 +- hooks/use-event-listener.ts | 2 +- layouts/app-layout.tsx | 23 +- lefthook.yaml | 13 +- next.config.mjs | 8 +- package.json | 59 +- pages/404.page.tsx | 12 +- pages/_app.page.tsx | 24 +- pages/_document.page.tsx | 6 +- pages/index.page.tsx | 4 +- pnpm-lock.yaml | 5678 +++++++--------------- theme/index.ts | 12 +- tsconfig.json | 4 +- tsconfig.node.json | 11 - utils/array.ts | 4 +- utils/i18n.ts | 6 +- utils/next.ts | 2 +- utils/storage.ts | 10 +- vitest.config.mts | 21 - 74 files changed, 2900 insertions(+), 4725 deletions(-) delete mode 100644 .env.example create mode 100644 .eslint/base.ts create mode 100644 .eslint/cspell.ts create mode 100644 .eslint/import.ts create mode 100644 .eslint/index.ts create mode 100644 .eslint/jsx-a11y.ts create mode 100644 .eslint/next.ts create mode 100644 .eslint/perfectionist.ts create mode 100644 .eslint/react-hooks.ts create mode 100644 .eslint/react.ts create mode 100644 .eslint/shared.ts create mode 100644 .eslint/typescript.ts create mode 100644 @types/@next-eslint-plugin-next.d.ts create mode 100644 @types/eslint-config-prettier.d.ts create mode 100644 @types/eslint-plugin-import.d.ts create mode 100644 @types/eslint-plugin-jsx-a11y.d.ts create mode 100644 @types/eslint-plugin-react-hooks.d.ts create mode 100644 @types/eslint-plugin-react.d.ts create mode 100644 @types/eslint-plugin-spellcheck.d.ts delete mode 100644 eslint.config.mjs create mode 100644 eslint.config.ts delete mode 100644 tsconfig.node.json delete mode 100644 vitest.config.mts diff --git a/.env.example b/.env.example deleted file mode 100644 index 3b926cd..0000000 --- a/.env.example +++ /dev/null @@ -1 +0,0 @@ -GITHUB_TOKEN= diff --git a/.eslint/base.ts b/.eslint/base.ts new file mode 100644 index 0000000..83e691a --- /dev/null +++ b/.eslint/base.ts @@ -0,0 +1,65 @@ +import type { Linter } from "eslint" +import { sharedFiles } from "./shared" + +export const baseConfig: Linter.Config = { + name: "eslint/base", + files: sharedFiles, + rules: { + "constructor-super": "error", + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-import-assign": "error", + "no-invalid-regexp": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-nonoctal-decimal-escape": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-setter-return": "error", + "no-this-before-super": "error", + "no-undef": "error", + "no-unexpected-multiline": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-var": "error", + "no-with": "error", + "require-yield": "error", + "use-isnan": "error", + "valid-typeof": "error", + + "no-console": ["warn", { allow: ["warn", "error"] }], + }, +} diff --git a/.eslint/cspell.ts b/.eslint/cspell.ts new file mode 100644 index 0000000..25d531b --- /dev/null +++ b/.eslint/cspell.ts @@ -0,0 +1,18 @@ +import type { Linter } from "eslint" +import cspellPlugin from "@cspell/eslint-plugin" +import { sharedFiles } from "./shared" + +export const cspellConfig: Linter.Config = { + name: "eslint/cspell", + files: sharedFiles, + plugins: { "@cspell": cspellPlugin }, + rules: { + "@cspell/spellchecker": [ + "warn", + { + configFile: new URL("../cspell.json", import.meta.url).toString(), + cspell: {}, + }, + ], + }, +} diff --git a/.eslint/import.ts b/.eslint/import.ts new file mode 100644 index 0000000..5360f73 --- /dev/null +++ b/.eslint/import.ts @@ -0,0 +1,47 @@ +import type { Linter } from "eslint" +import { fixupPluginRules } from "@eslint/compat" +import { flatConfigs } from "eslint-plugin-import" +import unusedImportsPlugin from "eslint-plugin-unused-imports" +import { sharedFiles } from "./shared" + +export const importConfigArray: Linter.Config[] = [ + { + name: "eslint/import/order", + files: sharedFiles, + plugins: { + import: fixupPluginRules(flatConfigs.recommended.plugins.import), + }, + rules: { + "import/consistent-type-specifier-style": ["error", "prefer-top-level"], + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [ + ".js", + ".cjs", + ".mjs", + ".jsx", + ".ts", + ".cts", + ".mts", + ".tsx", + ".d.ts", + ], + }, + "import/resolver": { + node: true, + typescript: true, + }, + }, + }, + { + name: "eslint/import/unused", + files: sharedFiles, + plugins: { + "unused-imports": unusedImportsPlugin, + }, + rules: { + "unused-imports/no-unused-imports": "error", + }, + }, +] diff --git a/.eslint/index.ts b/.eslint/index.ts new file mode 100644 index 0000000..b60104d --- /dev/null +++ b/.eslint/index.ts @@ -0,0 +1,10 @@ +export * from "./base" +export * from "./cspell" +export * from "./import" +export * from "./jsx-a11y" +export * from "./next" +export * from "./perfectionist" +export * from "./react" +export * from "./react-hooks" +export * from "./shared" +export * from "./typescript" diff --git a/.eslint/jsx-a11y.ts b/.eslint/jsx-a11y.ts new file mode 100644 index 0000000..abd6c21 --- /dev/null +++ b/.eslint/jsx-a11y.ts @@ -0,0 +1,14 @@ +import type { Linter } from "eslint" +import jsxA11yPlugin from "eslint-plugin-jsx-a11y" +import { sharedFiles } from "./shared" + +export const jsxA11yConfig: Linter.Config = { + name: "eslint/jsx-a11y", + files: sharedFiles, + plugins: { + "jsx-a11y": jsxA11yPlugin, + }, + rules: { + ...jsxA11yPlugin.configs.recommended.rules, + }, +} diff --git a/.eslint/next.ts b/.eslint/next.ts new file mode 100644 index 0000000..ca79d54 --- /dev/null +++ b/.eslint/next.ts @@ -0,0 +1,18 @@ +import type { Linter } from "eslint" +import { fixupPluginRules } from "@eslint/compat" +import nextPlugin from "@next/eslint-plugin-next" +import { sharedFiles } from "./shared" + +export const nextConfig: Linter.Config = { + name: "eslint/next", + files: sharedFiles, + plugins: { + "@next/next": fixupPluginRules(nextPlugin), + }, + rules: { + ...nextPlugin.configs.recommended.rules, + ...nextPlugin.configs["core-web-vitals"].rules, + "@next/next/no-assign-module-variable": "off", + "@next/next/no-title-in-document-head": "off", + }, +} diff --git a/.eslint/perfectionist.ts b/.eslint/perfectionist.ts new file mode 100644 index 0000000..6bb567a --- /dev/null +++ b/.eslint/perfectionist.ts @@ -0,0 +1,133 @@ +import type { TSESLint } from "@typescript-eslint/utils" +import perfectionistPlugin from "eslint-plugin-perfectionist" +import { sharedFiles } from "./shared" + +const type = "natural" + +const semanticSizes = { + base: "base", + "9xs": "9xs", + "8xs": "8xs", + "7xs": "7xs", + "6xs": "6xs", + "5xs": "5xs", + "4xs": "4xs", + "3xs": "3xs", + "2xs": ["2xs", "hairline", "ultra-fast"], + xs: ["xs", "thin", "tighter", "faster", "shorter"], + sm: ["sm", "light", "tight", "fast", "short"], + md: ["md", "normal"], + lg: ["lg", "medium", "wide", "slow", "tall"], + xl: ["xl", "semibold", "wider", "slower", "taller"], + "2xl": ["2xl", "bold", "widest", "ultra-slow"], + "3xl": ["3xl", "extrabold"], + "4xl": ["4xl", "black"], + "5xl": "5xl", + "6xl": "6xl", + "7xl": "7xl", + "8xl": "8xl", + "9xl": "9xl", +} + +const sortObjectGroups = { + customGroups: { + aria: "aria-*", + callback: "on*", + data: "data-*", + internal: "__*", + primary: ["key", "ref", "id", "lang"], + props: "*Props", + pseudos: "_*", + quaternary: ["className", "alt"], + quinary: ["css", "sx", "style"], + secondary: ["as", "form", "type", "htmlFor"], + senary: ["layerStyle", "textStyle", "baseStyle", "apply"], + septenary: ["variant", "size", "colorScheme"], + tertiary: ["name", "src", "srcSet", "href", "target"], + ...semanticSizes, + }, + groups: [ + "primary", + "secondary", + "tertiary", + "quaternary", + "quinary", + "senary", + "septenary", + ...Object.keys(semanticSizes), + ["aria", "data"], + "unknown", + "pseudos", + "props", + "callback", + "internal", + ], +} + +export const perfectionistConfig: TSESLint.FlatConfig.Config = { + name: "eslint/perfectionist", + files: sharedFiles, + plugins: { perfectionist: perfectionistPlugin }, + rules: { + "perfectionist/sort-exports": ["error", { type }], + "perfectionist/sort-imports": [ + "error", + { + type, + groups: [ + "type", + ["external-type", "builtin-type", "internal-type"], + ["parent-type", "sibling-type", "index-type"], + ["builtin", "external"], + "internal", + ["parent", "sibling", "index"], + "object", + "unknown", + ["side-effect", "side-effect-style"], + ], + newlinesBetween: "never", + }, + ], + + "perfectionist/sort-array-includes": ["warn", { type }], + "perfectionist/sort-interfaces": [ + "warn", + { + type, + groupKind: "required-first", + partitionByNewLine: true, + ...sortObjectGroups, + }, + ], + "perfectionist/sort-intersection-types": ["warn", { type }], + "perfectionist/sort-jsx-props": [ + "warn", + { + type, + ...sortObjectGroups, + }, + ], + "perfectionist/sort-maps": ["warn", { type }], + "perfectionist/sort-named-exports": ["warn", { type }], + "perfectionist/sort-named-imports": ["warn", { type }], + "perfectionist/sort-object-types": [ + "warn", + { + type, + groupKind: "required-first", + partitionByNewLine: true, + ...sortObjectGroups, + }, + ], + "perfectionist/sort-objects": [ + "warn", + { + type, + partitionByNewLine: true, + ...sortObjectGroups, + }, + ], + "perfectionist/sort-sets": ["warn", { type }], + "perfectionist/sort-union-types": ["warn", { type }], + }, +} diff --git a/.eslint/react-hooks.ts b/.eslint/react-hooks.ts new file mode 100644 index 0000000..d4e4348 --- /dev/null +++ b/.eslint/react-hooks.ts @@ -0,0 +1,15 @@ +import type { Linter } from "eslint" +import { fixupPluginRules } from "@eslint/compat" +import reactHooksPlugin from "eslint-plugin-react-hooks" +import { sharedFiles } from "./shared" + +export const reactHooksConfig: Linter.Config = { + name: "eslint/react-hooks", + files: sharedFiles, + plugins: { "react-hooks": fixupPluginRules(reactHooksPlugin) }, + rules: { + ...reactHooksPlugin.configs.recommended.rules, + + "react-hooks/exhaustive-deps": "error", + }, +} diff --git a/.eslint/react.ts b/.eslint/react.ts new file mode 100644 index 0000000..9a4423d --- /dev/null +++ b/.eslint/react.ts @@ -0,0 +1,26 @@ +import type { Linter } from "eslint" +import reactPlugin from "eslint-plugin-react" +import { sharedFiles } from "./shared" + +export const reactConfig: Linter.Config = { + name: "eslint/react", + files: sharedFiles, + plugins: { react: reactPlugin }, + rules: { + ...reactPlugin.configs.recommended.rules, + + "react/no-unescaped-entities": "off", + "react/prop-types": "off", + + "react/forward-ref-uses-ref": "error", + "react/jsx-boolean-value": "error", + "react/jsx-curly-brace-presence": "error", + "react/jsx-fragments": "error", + "react/jsx-no-leaked-render": "error", + "react/jsx-no-useless-fragment": "error", + "react/jsx-pascal-case": "error", + "react/react-in-jsx-scope": "off", + "react/self-closing-comp": "error", + }, + settings: { react: { version: "detect" } }, +} diff --git a/.eslint/shared.ts b/.eslint/shared.ts new file mode 100644 index 0000000..656c775 --- /dev/null +++ b/.eslint/shared.ts @@ -0,0 +1,11 @@ +export const sharedFiles = [ + "**/*.js", + "**/*.cjs", + "**/*.mjs", + "**/*.jsx", + "**/*.ts", + "**/*.cts", + "**/*.mts", + "**/*.tsx", + "**/*.d.ts", +] diff --git a/.eslint/typescript.ts b/.eslint/typescript.ts new file mode 100644 index 0000000..6e37551 --- /dev/null +++ b/.eslint/typescript.ts @@ -0,0 +1,75 @@ +import type { TSESLint } from "@typescript-eslint/utils" +import { configs, plugin } from "typescript-eslint" +import { sharedFiles } from "./shared" + +const disabledRules = configs.recommended[1]?.rules +const stylisticRules = configs.stylistic[2]?.rules + +export const typescriptConfig: TSESLint.FlatConfig.Config = { + name: "eslint/typescript", + files: sharedFiles, + plugins: { + "@typescript-eslint": plugin, + }, + rules: { + ...disabledRules, + ...stylisticRules, + + "@typescript-eslint/prefer-function-type": "off", + "no-array-constructor": "off", + "no-unused-expressions": "off", + "no-unused-vars": "off", + "no-useless-constructor": "off", + "prefer-const": "off", + + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/consistent-indexed-object-style": [ + "error", + "index-signature", + ], + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/consistent-type-imports": "error", + // "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-extra-non-null-assertion": "error", + "@typescript-eslint/no-extraneous-class": "error", + "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-import-type-side-effects": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "error", + "@typescript-eslint/no-non-null-asserted-optional-chain": "error", + "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-unsafe-declaration-merging": "error", + "@typescript-eslint/no-unused-expressions": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/no-wrapper-object-types": "error", + "@typescript-eslint/prefer-optional-chain": "error", + "@typescript-eslint/require-await": "error", + "@typescript-eslint/triple-slash-reference": "error", + + "@typescript-eslint/dot-notation": "warn", + // "@typescript-eslint/no-deprecated": "warn", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/no-unnecessary-template-expression": "warn", + "@typescript-eslint/no-unnecessary-type-arguments": "warn", + "@typescript-eslint/no-unnecessary-type-constraint": "warn", + "@typescript-eslint/prefer-find": "warn", + "@typescript-eslint/prefer-includes": "warn", + "@typescript-eslint/prefer-literal-enum-member": "warn", + "@typescript-eslint/prefer-namespace-keyword": "warn", + "@typescript-eslint/prefer-reduce-type-parameter": "warn", + "@typescript-eslint/prefer-string-starts-ends-with": "warn", + "@typescript-eslint/promise-function-async": "warn", + }, +} diff --git a/.prettierignore b/.prettierignore index 890f2f3..4e20880 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ .next node_modules pnpm-lock.yaml -*.hbs diff --git a/@types/@clack-prompts.d.ts b/@types/@clack-prompts.d.ts index d57009a..b776190 100644 --- a/@types/@clack-prompts.d.ts +++ b/@types/@clack-prompts.d.ts @@ -5,11 +5,11 @@ declare module "@clack/prompts" { type Spinner = ReturnType - type Runner = ( + type Runner = ( ...args: T ) => (p?: Prompts, s?: Spinner) => K - type RequiredRunner = ( + type RequiredRunner = ( ...args: T ) => (p: Prompts, s: Spinner) => K } diff --git a/@types/@next-eslint-plugin-next.d.ts b/@types/@next-eslint-plugin-next.d.ts new file mode 100644 index 0000000..f953f08 --- /dev/null +++ b/@types/@next-eslint-plugin-next.d.ts @@ -0,0 +1 @@ +declare module "@next/eslint-plugin-next" diff --git a/@types/component.d.ts b/@types/component.d.ts index 00bce1e..69a46ea 100644 --- a/@types/component.d.ts +++ b/@types/component.d.ts @@ -9,65 +9,65 @@ type LocaleMetadata = { [key in DefaultLocale]: Y } & { } declare module "component" { - type ContentType = "categoryGroup" | "category" | "component" + type ContentType = "category" | "categoryGroup" | "component" - type Author = { + interface Author { id: number - login: string avatar_url: string html_url: string + login: string } - type ComponentCode = { + interface ComponentCode { name: string - path: string code: string + path: string } - type ComponentPaths = { + interface ComponentPaths { component: string - theme: string | null - config: string | null + config: null | string + theme: null | string } - type Component = { + interface Component { name: string - slug: string - paths: ComponentPaths components: ComponentCode[] metadata: ComponentMetadata | null + paths: ComponentPaths + slug: string } - type ComponentCategoryGroup = Partial & { + type ComponentCategoryGroup = { name: string - slug: string isExpanded: boolean - icon?: string | null + slug: string + icon?: null | string items?: ComponentCategoryGroup[] - } + } & Partial - type ComponentCategory = Omit & { + type ComponentCategory = { items?: Component[] - } + } & Omit - type ComponentContainerProps = HTMLUIProps<"div"> & { + type ComponentContainerProps = { centerContent?: boolean - } + } & HTMLUIProps - type MetadataOptions = { + interface MetadataOptions { container: ComponentContainerProps } - type SharedMetadata = { - icon?: string | null + interface SharedMetadata { authors?: Author[] | null - labels?: string[] | null + icon?: null | string + labels?: null | string[] options?: MetadataOptions | null } - type CommonMetadata = { - title: string + interface CommonMetadata { description: string + title: string } type OriginMetadata = LocaleMetadata & SharedMetadata diff --git a/@types/eslint-config-prettier.d.ts b/@types/eslint-config-prettier.d.ts new file mode 100644 index 0000000..33237f8 --- /dev/null +++ b/@types/eslint-config-prettier.d.ts @@ -0,0 +1 @@ +declare module "eslint-config-prettier" diff --git a/@types/eslint-plugin-import.d.ts b/@types/eslint-plugin-import.d.ts new file mode 100644 index 0000000..a9924d4 --- /dev/null +++ b/@types/eslint-plugin-import.d.ts @@ -0,0 +1 @@ +declare module "eslint-plugin-import" diff --git a/@types/eslint-plugin-jsx-a11y.d.ts b/@types/eslint-plugin-jsx-a11y.d.ts new file mode 100644 index 0000000..7ffd65f --- /dev/null +++ b/@types/eslint-plugin-jsx-a11y.d.ts @@ -0,0 +1 @@ +declare module "eslint-plugin-jsx-a11y" diff --git a/@types/eslint-plugin-react-hooks.d.ts b/@types/eslint-plugin-react-hooks.d.ts new file mode 100644 index 0000000..8713c9c --- /dev/null +++ b/@types/eslint-plugin-react-hooks.d.ts @@ -0,0 +1 @@ +declare module "eslint-plugin-react-hooks" diff --git a/@types/eslint-plugin-react.d.ts b/@types/eslint-plugin-react.d.ts new file mode 100644 index 0000000..342626f --- /dev/null +++ b/@types/eslint-plugin-react.d.ts @@ -0,0 +1 @@ +declare module "eslint-plugin-react" diff --git a/@types/eslint-plugin-spellcheck.d.ts b/@types/eslint-plugin-spellcheck.d.ts new file mode 100644 index 0000000..89ce39d --- /dev/null +++ b/@types/eslint-plugin-spellcheck.d.ts @@ -0,0 +1 @@ +declare module "eslint-plugin-spellcheck" diff --git a/@types/next.d.ts b/@types/next.d.ts index 59bb02f..84395f0 100644 --- a/@types/next.d.ts +++ b/@types/next.d.ts @@ -3,16 +3,13 @@ import type { NextPage, NextPageWithConfig } from "next" import type { AppProps } from "next/app" declare module "next" { - type NextPageWithConfig = NextPage< - Props, - InitialProps - > & { - config?: ThemeConfig | ((asPath: string) => ThemeConfig | undefined) - } + type NextPageWithConfig = { + config?: ((asPath: string) => ThemeConfig | undefined) | ThemeConfig + } & NextPage } declare module "next/app" { - type AppPropsWithConfig = Omit, "Component"> & { + type AppPropsWithConfig = { Component: NextPageWithConfig

- } + } & Omit, "Component"> } diff --git a/@types/search.d.ts b/@types/search.d.ts index 0f82509..36b498f 100644 --- a/@types/search.d.ts +++ b/@types/search.d.ts @@ -1,16 +1,16 @@ declare module "search" { - type ContentHierarchy = { + interface ContentHierarchy { categoryGroup: string category?: string component?: string } - type Content = { - title: string - description?: string + interface Content { type: ContentType - slug: string - labels: string[] hierarchy: ContentHierarchy + labels: string[] + slug: string + title: string + description?: string } } diff --git a/README.md b/README.md index 77bf37f..3feba67 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# yamada-theme +

+ English | 日本語 +

diff --git a/components/data-display/authors.tsx b/components/data-display/authors.tsx index fa737b4..6eae871 100644 --- a/components/data-display/authors.tsx +++ b/components/data-display/authors.tsx @@ -1,12 +1,12 @@ -import { Avatar, AvatarGroup, Box, forwardRef, Tooltip } from "@yamada-ui/react" import type { AvatarGroupProps, AvatarProps } from "@yamada-ui/react" -import { memo } from "react" import type { Author } from "component" +import { Avatar, AvatarGroup, Box, forwardRef, Tooltip } from "@yamada-ui/react" +import { memo } from "react" -export type AuthorsProps = AvatarGroupProps & { +export type AuthorsProps = { authors?: Author[] | null avatarSize?: AvatarProps["boxSize"] -} +} & AvatarGroupProps export const Authors = memo( forwardRef( @@ -21,21 +21,21 @@ export const Authors = memo( max={5} {...rest} > - {authors.map(({ login, avatar_url, html_url }) => ( + {authors.map(({ avatar_url, html_url, login }) => ( - + diff --git a/components/forms/color-mode-button.tsx b/components/forms/color-mode-button.tsx index 4d93861..0b3a5d4 100644 --- a/components/forms/color-mode-button.tsx +++ b/components/forms/color-mode-button.tsx @@ -1,5 +1,6 @@ +import type { ColorMode, IconButtonProps, MenuProps } from "@yamada-ui/react" +import type { FC } from "react" import { Moon, Sun } from "@yamada-ui/lucide" -import type { IconButtonProps, MenuProps, ColorMode } from "@yamada-ui/react" import { IconButton, Menu, @@ -10,41 +11,40 @@ import { useBreakpointValue, useColorMode, } from "@yamada-ui/react" -import type { FC } from "react" import { memo } from "react" -export type ColorModeButtonProps = IconButtonProps & { +export type ColorModeButtonProps = { menuProps?: MenuProps -} +} & IconButtonProps export const ColorModeButton: FC = memo( ({ menuProps, ...rest }) => { const padding = useBreakpointValue({ base: 32, md: 16 }) - const { colorMode, internalColorMode, changeColorMode } = useColorMode() + const { changeColorMode, colorMode, internalColorMode } = useColorMode() return ( = memo( /> - + + type="radio" value={internalColorMode} onChange={changeColorMode} - type="radio" > - + Light - + Dark - + System diff --git a/components/forms/copy-button.tsx b/components/forms/copy-button.tsx index 32ac065..8bd6243 100644 --- a/components/forms/copy-button.tsx +++ b/components/forms/copy-button.tsx @@ -1,46 +1,44 @@ -import { Check, Copy } from "@yamada-ui/lucide" import type { ButtonProps } from "@yamada-ui/react" -import { forwardRef, IconButton, useClipboard, Tooltip } from "@yamada-ui/react" +import { Check, Copy } from "@yamada-ui/lucide" +import { forwardRef, IconButton, Tooltip, useClipboard } from "@yamada-ui/react" import { memo } from "react" -export type CopyButtonProps = Omit & { value: string } +export type CopyButtonProps = { value: string } & Omit export const CopyButton = memo( forwardRef(({ value, ...rest }, ref) => { const { hasCopied, onCopy } = useClipboard(value) return ( - <> - - : } - {...rest} - onClick={onCopy} - /> - - + + : } + {...rest} + onClick={onCopy} + /> + ) }), ) diff --git a/components/forms/index.ts b/components/forms/index.ts index e036814..f00562d 100644 --- a/components/forms/index.ts +++ b/components/forms/index.ts @@ -1,4 +1,4 @@ -export * from "./search" -export * from "./copy-button" export * from "./color-mode-button" +export * from "./copy-button" +export * from "./search" export * from "./theme-scheme-button" diff --git a/components/forms/search.tsx b/components/forms/search.tsx index bce5b77..ed0a545 100644 --- a/components/forms/search.tsx +++ b/components/forms/search.tsx @@ -1,30 +1,33 @@ +import type { ButtonProps, ModalProps, StackProps } from "@yamada-ui/react" +import type { FC, KeyboardEvent, KeyboardEventHandler, RefObject } from "react" import { + ExternalLink, LayoutList, PanelsTopLeft, Search as SearchIcon, - ExternalLink, } from "@yamada-ui/lucide" import { - ui, + dataAttr, + Divider, + forwardRef, + handlerAll, + Highlight, HStack, + Icon, + IconButton, + isApple, Kbd, Modal, ModalBody, + ModalHeader, Text, - forwardRef, - handlerAll, + ui, useDisclosure, - isApple, - Divider, - VStack, - ModalHeader, - Highlight, - dataAttr, useUpdateEffect, - IconButton, - Icon, + VStack, } from "@yamada-ui/react" -import type { StackProps, ModalProps, ButtonProps } from "@yamada-ui/react" +import { useI18n } from "contexts/i18n-context" +import { useEventListener } from "hooks/use-event-listener" import { matchSorter } from "match-sorter" import NextLink from "next/link" import { useRouter } from "next/router" @@ -37,17 +40,14 @@ import { useRef, useState, } from "react" -import type { FC, KeyboardEvent, KeyboardEventHandler, RefObject } from "react" import scrollIntoView from "scroll-into-view-if-needed" -import { useI18n } from "contexts/i18n-context" -import { useEventListener } from "hooks/use-event-listener" const ACTION_DEFAULT_KEY = "Ctrl" const ACTION_APPLE_KEY = "⌘" const useSearch = () => { const { events } = useRouter() - const { isOpen, onOpen, onClose } = useDisclosure() + const { isOpen, onClose, onOpen } = useDisclosure() useEffect(() => { events.on("routeChangeComplete", onClose) @@ -57,15 +57,15 @@ const useSearch = () => { } }, [onClose, events]) - return { isOpen, onOpen, onClose } + return { isOpen, onClose, onOpen } } -export type SearchProps = StackProps & {} +export type SearchProps = {} & StackProps export const Search = memo( forwardRef(({ ...rest }, ref) => { const { tc } = useI18n() - const { isOpen, onOpen, onClose } = useSearch() + const { isOpen, onClose, onOpen } = useSearch() const [actionKey, setActionKey] = useState(ACTION_APPLE_KEY) useEffect(() => { @@ -91,22 +91,22 @@ export const Search = memo( return ( <> @@ -121,21 +121,21 @@ export const Search = memo( }), ) -export type SearchButtonProps = ButtonProps & {} +export type SearchButtonProps = {} & ButtonProps export const SearchButton = memo( forwardRef(({ ...rest }, ref) => { - const { isOpen, onOpen, onClose } = useSearch() + const { isOpen, onClose, onOpen } = useSearch() return ( <> } + _hover={{ bg: ["blackAlpha.100", "whiteAlpha.50"] }} {...rest} onClick={handlerAll(rest.onClick, onOpen)} /> @@ -152,10 +152,10 @@ const SearchModal: FC = memo( ({ isOpen, onClose, ...rest }) => { const [query, setQuery] = useState("") const [selectedIndex, setSelectedIndex] = useState(0) - const { t, contents } = useI18n() + const { contents, t } = useI18n() const router = useRouter() - const eventRef = useRef<"mouse" | "keyboard" | null>(null) - const directionRef = useRef<"up" | "down">("down") + const eventRef = useRef<"keyboard" | "mouse" | null>(null) + const directionRef = useRef<"down" | "up">("down") const compositionRef = useRef(false) const containerRef = useRef(null) const itemRefs = useRef>>( @@ -182,10 +182,9 @@ const SearchModal: FC = memo( eventRef.current = "keyboard" - const actions: Record< - string, - KeyboardEventHandler | undefined - > = { + const actions: { + [key: string]: KeyboardEventHandler | undefined + } = { ArrowDown: () => { if (selectedIndex + 1 === hits.length) return @@ -198,10 +197,14 @@ const SearchModal: FC = memo( directionRef.current = "up" setSelectedIndex(selectedIndex - 1) }, + End: () => { + directionRef.current = "down" + setSelectedIndex(hits.length - 1) + }, Enter: () => { if (!hits.length) return - const { type, slug } = hits[selectedIndex] + const { type, slug } = hits[selectedIndex] ?? {} if (type === "component") { window.open(slug, "_blank") @@ -214,10 +217,6 @@ const SearchModal: FC = memo( directionRef.current = "up" setSelectedIndex(0) }, - End: () => { - directionRef.current = "down" - setSelectedIndex(hits.length - 1) - }, } const action = actions[ev.key] @@ -258,51 +257,51 @@ const SearchModal: FC = memo( el.scrollTop = top - 17 } }), - scrollMode: "if-needed", block: "nearest", - inline: "nearest", boundary: containerRef.current, + inline: "nearest", + scrollMode: "if-needed", }) }, [selectedIndex]) return ( - + setQuery(ev.target.value)} - onKeyDown={onKeyDown} - onCompositionStart={() => { - compositionRef.current = true - }} onCompositionEnd={() => { compositionRef.current = false }} + onCompositionStart={() => { + compositionRef.current = true + }} + onKeyDown={onKeyDown} /> @@ -312,7 +311,7 @@ const SearchModal: FC = memo( - {hits.map(({ title, type, slug, hierarchy }, index) => { + {hits.map(({ type, hierarchy, slug, title }, index) => { const isSelected = index === selectedIndex const ref = createRef() @@ -320,27 +319,27 @@ const SearchModal: FC = memo( return ( { eventRef.current = "mouse" @@ -350,14 +349,14 @@ const SearchModal: FC = memo( {type === "component" ? ( ) : ( )} @@ -365,8 +364,8 @@ const SearchModal: FC = memo( {type === "category" || type === "component" ? ( = memo( {type === "component" ? ( <> / = memo( ) : null} {title} @@ -409,8 +408,8 @@ const SearchModal: FC = memo( {type === "component" ? ( ) : null} diff --git a/components/forms/theme-scheme-button.tsx b/components/forms/theme-scheme-button.tsx index d84e0ae..9e56b85 100644 --- a/components/forms/theme-scheme-button.tsx +++ b/components/forms/theme-scheme-button.tsx @@ -1,5 +1,6 @@ -import { Palette } from "@yamada-ui/lucide" import type { BoxProps, IconButtonProps, PopoverProps } from "@yamada-ui/react" +import type { FC } from "react" +import { Palette } from "@yamada-ui/lucide" import { Box, IconButton, @@ -12,33 +13,32 @@ import { useRipple, useTheme, } from "@yamada-ui/react" -import type { FC } from "react" import { memo } from "react" -export type ThemeSchemeButtonProps = IconButtonProps & { +export type ThemeSchemeButtonProps = { popoverProps?: PopoverProps -} +} & IconButtonProps export const ThemeSchemeButton: FC = memo( ({ popoverProps, ...rest }) => { - const { isOpen, onOpen, onClose } = useDisclosure() - const { theme, changeThemeScheme } = useTheme() + const { isOpen, onClose, onOpen } = useDisclosure() + const { changeThemeScheme, theme } = useTheme() const { colorSchemes = [] } = theme return ( } {...rest} @@ -69,9 +69,9 @@ export const ThemeSchemeButton: FC = memo( ThemeSchemeButton.displayName = "ThemeSchemeButton" -type ColorButtonProps = BoxProps & { +type ColorButtonProps = { colorScheme: string -} +} & BoxProps const ColorButton: FC = memo(({ colorScheme, ...rest }) => { const { onPointerDown, ...rippleProps } = useRipple({}) @@ -80,19 +80,19 @@ const ColorButton: FC = memo(({ colorScheme, ...rest }) => { diff --git a/components/layouts/footer.tsx b/components/layouts/footer.tsx index f2fc47a..9203755 100644 --- a/components/layouts/footer.tsx +++ b/components/layouts/footer.tsx @@ -1,18 +1,18 @@ import type { CenterProps } from "@yamada-ui/react" import { Center, + forwardRef, HStack, Link, Text, VStack, - forwardRef, } from "@yamada-ui/react" -import { memo } from "react" import { Github, X } from "components/media-and-icons" import { CONSTANT } from "constant" import { useI18n } from "contexts/i18n-context" +import { memo } from "react" -export type FooterProps = CenterProps & {} +export type FooterProps = {} & CenterProps export const Footer = memo( forwardRef(({ ...rest }, ref) => { @@ -29,10 +29,10 @@ export const Footer = memo( > diff --git a/components/layouts/header.tsx b/components/layouts/header.tsx index 939106d..ef704aa 100644 --- a/components/layouts/header.tsx +++ b/components/layouts/header.tsx @@ -1,32 +1,31 @@ -import { Languages, Menu as MenuIcon } from "@yamada-ui/lucide" import type { CenterProps, IconButtonProps, MenuProps, UseDisclosureReturn, } from "@yamada-ui/react" +import type { FC } from "react" +import type { Locale } from "utils/i18n" +import { Languages, Menu as MenuIcon } from "@yamada-ui/lucide" import { Box, Center, CloseButton, - HStack, + forwardRef, Heading, + HStack, IconButton, Menu, MenuButton, MenuList, MenuOptionGroup, MenuOptionItem, - forwardRef, mergeRefs, useBreakpointValue, useDisclosure, useMotionValueEvent, useScroll, } from "@yamada-ui/react" -import Link from "next/link" -import type { FC } from "react" -import { memo, useRef, useState } from "react" import { ColorModeButton, Search, @@ -38,16 +37,17 @@ import { NextLinkIconButton, Tree } from "components/navigation" import { MobileMenu } from "components/overlay" import { CONSTANT } from "constant" import { useI18n } from "contexts/i18n-context" -import type { Locale } from "utils/i18n" +import Link from "next/link" +import { memo, useRef, useState } from "react" -export type HeaderProps = CenterProps & {} +export type HeaderProps = {} & CenterProps export const Header = memo( forwardRef(({ ...rest }, ref) => { const headerRef = useRef() const { scrollY } = useScroll() const [y, setY] = useState(0) - const { isOpen, onOpen, onClose } = useDisclosure() + const { isOpen, onClose, onOpen } = useDisclosure() const { height = 0 } = headerRef.current?.getBoundingClientRect() ?? {} useMotionValueEvent(scrollY, "change", setY) @@ -59,33 +59,33 @@ export const Header = memo(
- + Yamada Theme @@ -93,16 +93,16 @@ export const Header = memo( @@ -110,9 +110,9 @@ export const Header = memo(
} isOpen={isOpen} onClose={onClose} - header={} > @@ -121,52 +121,50 @@ export const Header = memo( }), ) -type ButtonGroupProps = Partial & { isMobile?: boolean } +type ButtonGroupProps = { isMobile?: boolean } & Partial const ButtonGroup: FC = memo( - ({ isMobile, isOpen, onOpen, onClose }) => { + ({ isMobile, isOpen, onClose, onOpen }) => { return ( } + isExternal /> } + isExternal /> - {CONSTANT.I18N.LOCALES.length > 1 ? ( - - ) : null} + @@ -174,17 +172,17 @@ const ButtonGroup: FC = memo( } + onClick={onOpen} /> ) : ( )} @@ -195,37 +193,37 @@ const ButtonGroup: FC = memo( ButtonGroup.displayName = "ButtonGroup" -type I18nButtonProps = IconButtonProps & { +type I18nButtonProps = { menuProps?: MenuProps -} +} & IconButtonProps const I18nButton: FC = memo(({ menuProps, ...rest }) => { const padding = useBreakpointValue({ base: 32, md: 16 }) - const { locale, changeLocale } = useI18n() + const { changeLocale, locale } = useI18n() return ( } {...rest} @@ -233,12 +231,12 @@ const I18nButton: FC = memo(({ menuProps, ...rest }) => { + type="radio" value={locale} onChange={changeLocale} - type="radio" > {CONSTANT.I18N.LOCALES.map(({ label, value }) => ( - + {label} ))} diff --git a/components/layouts/index.ts b/components/layouts/index.ts index c69ed9b..c420961 100644 --- a/components/layouts/index.ts +++ b/components/layouts/index.ts @@ -1,2 +1,2 @@ -export * from "./header" export * from "./footer" +export * from "./header" diff --git a/components/media-and-icons/discord.tsx b/components/media-and-icons/discord.tsx index bc4f8f1..ecc9ff8 100644 --- a/components/media-and-icons/discord.tsx +++ b/components/media-and-icons/discord.tsx @@ -8,14 +8,14 @@ export const Discord = forwardRef( ) diff --git a/components/media-and-icons/github.tsx b/components/media-and-icons/github.tsx index 851f7fe..e834f6c 100644 --- a/components/media-and-icons/github.tsx +++ b/components/media-and-icons/github.tsx @@ -8,14 +8,14 @@ export const Github = forwardRef( ) diff --git a/components/media-and-icons/index.ts b/components/media-and-icons/index.ts index 6c453ba..3cff2fe 100644 --- a/components/media-and-icons/index.ts +++ b/components/media-and-icons/index.ts @@ -1,8 +1,8 @@ +export * from "./discord" export * from "./github" +export * from "./layout" export * from "./logo" -export * from "./discord" -export * from "./twitter" export * from "./seo" +export * from "./twitter" export * from "./x" -export * from "./layout" export * from "./yamada-ui" diff --git a/components/media-and-icons/layout.tsx b/components/media-and-icons/layout.tsx index b54fb1c..4e0d916 100644 --- a/components/media-and-icons/layout.tsx +++ b/components/media-and-icons/layout.tsx @@ -7,12 +7,12 @@ export const LayoutHorizontal = forwardRef( return ( ) @@ -38,12 +38,12 @@ export const LayoutVertical = forwardRef( return ( ) diff --git a/components/media-and-icons/logo.tsx b/components/media-and-icons/logo.tsx index 17a5c14..b8a505b 100644 --- a/components/media-and-icons/logo.tsx +++ b/components/media-and-icons/logo.tsx @@ -6,8 +6,8 @@ export const Logo = forwardRef(({ ...rest }, ref) => { return ( +export type SeoProps = Pick -export const SEO = ({ title, description }: SEOProps) => ( +export const Seo = ({ description, title }: SeoProps) => ( ) diff --git a/components/media-and-icons/twitter.tsx b/components/media-and-icons/twitter.tsx index 2b59fbe..011a20c 100644 --- a/components/media-and-icons/twitter.tsx +++ b/components/media-and-icons/twitter.tsx @@ -8,15 +8,15 @@ export const Twitter = forwardRef( - + ) }, diff --git a/components/media-and-icons/x.tsx b/components/media-and-icons/x.tsx index 16b50e5..35245d0 100644 --- a/components/media-and-icons/x.tsx +++ b/components/media-and-icons/x.tsx @@ -9,12 +9,12 @@ export const X = forwardRef( ref={ref} boxSize={boxSize} fill="currentColor" - viewBox="0 0 24 24" focusable="false" + viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...rest} > - + ) }, diff --git a/components/media-and-icons/yamada-ui.tsx b/components/media-and-icons/yamada-ui.tsx index 0d7ccfb..c713728 100644 --- a/components/media-and-icons/yamada-ui.tsx +++ b/components/media-and-icons/yamada-ui.tsx @@ -7,11 +7,11 @@ export const YamadaUI = forwardRef( return (