-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UI プロトタイプ実装 #13
Merged
UI プロトタイプ実装 #13
Changes from all commits
Commits
Show all changes
91 commits
Select commit
Hold shift + click to select a range
5709751
chore: add vscode example config
sushichan044 414edd2
fix: tanstack query ではなく fetch を使う
sushichan044 7feed7f
feat: generate zod schema
sushichan044 c7cd72e
chore(deps): add conform
sushichan044 abe9c1e
chore(deps): add ufo
sushichan044 02b5823
feat: 簡易検索が動く
sushichan044 ce43d25
wip: 詳細検索
sushichan044 2f0ad03
fix: モーダル内のフォームを submit した際新しい状態をページ側のフォームに反映する
sushichan044 e29d9e0
fix: 配列に単体を渡しても良い
sushichan044 4e030e2
chore: meta tag
sushichan044 cd0c0c0
fix: DateRange が空で input されたときの処理
sushichan044 18c8687
chore: tsconfig を厳しくする
sushichan044 a8dd657
ブラウザの言語を取得する hooks
sushichan044 2452d8e
Note の Topic を表示するコンポーネント
sushichan044 1deafb2
トピックを検索可能にする
sushichan044 35ecc68
Mantine MultiSelect と Conform を連携可能にする
sushichan044 aa16516
トピック検索を複数選択可能にする
sushichan044 a076215
検索遷移中は検索 UI を操作できない
sushichan044 d206c23
空の値を事前に判定する
sushichan044 bc4dfa2
refactor: フォームごとに hooks を分けて id 生成を任せる
sushichan044 b777914
refactor: 値の処理部分を切り出す
sushichan044 4e0c2d7
言語選択に Select を使う
sushichan044 92afc5a
feat: ノートとポストに対する文検索
sushichan044 5234b46
fix: 予期せぬ autoCompletion を無効化
sushichan044 a75880c
fix: Response を返さず data を呼び出す
sushichan044 032ff6f
chore: Mantine と Tailwind の乖離を減らす
sushichan044 e1aa14a
refactor: 自動で data-1p-ignore してくれる TextInput を自作する
sushichan044 f639ff0
chore: Note Status の定数を作っておく
sushichan044 1f1b318
chore(deps): setup tailwindcss
sushichan044 5874c5f
fix: loader return value structure
sushichan044 a9726bd
検索条件追加、Modal を Advanced Search 側に寄せる
sushichan044 7fd35b6
fix: value coercing
sushichan044 8df6c6a
fix: 詳細検索モーダルをフルスクリーンにしない
sushichan044 dfc1131
feat: FieldSet を使ってまとめる
sushichan044 933b32e
fix: Mantine をラップしたコンポーネントの置き場
sushichan044 35a0e27
chore: sort props
sushichan044 933b033
fix: Modal を分離する
sushichan044 b3188c8
wip: set meta tag
sushichan044 bed95b0
fix: typo
sushichan044 9f40165
chore: スタイル調整
sushichan044 ec8b304
feat: Note, Post を整形して表示
sushichan044 0c78283
chore: InputControl を spread しない
sushichan044 ad8b002
fix: rename arrayContainsNonNullItem to containsNonNullValues
sushichan044 ee92d82
chore(deps): install prettier
sushichan044 f087177
chore: run prettier
sushichan044 14427a1
refactor: DateRangePicker を切り出す
sushichan044 21e5d3a
refactor: LanguageSelect を切り出す
sushichan044 28b68a8
fix: 配列の処理
sushichan044 8609cf7
fix: action でも手で作ったスキーマを使う
sushichan044 29a1f88
chore(deps): update ESLint
sushichan044 e407092
refactor: TopicSelect を切り出す
sushichan044 e7d38e8
chore: vitest 導入
sushichan044 a58a43f
chore: DateRangePicker の挙動をテストする
sushichan044 075c1e2
fix: オーバーロードの追加
sushichan044 8ad2d42
chore(deps): add unplugin-icons
sushichan044 e5378ef
feat: ページネーションを可能にする
sushichan044 c746355
feat: 1 ページに表示する limit を調整可能にする
sushichan044 ce06768
feat: PC レイアウトではフォームと結果を横並びにする
sushichan044 b4a6d67
chore: スタイルの微調整
sushichan044 6f292c6
chore: tailwindcss の class 名を sort 可能にする
sushichan044 ffb9e20
chore: sort tailwind class
sushichan044 c7ee9d5
fix: remove default search params
sushichan044 9a64cbc
chore: 文字間隔調整
sushichan044 67fb75d
chore: add loading style for pagination button
sushichan044 d10cf0c
fix: set default limit to 25
sushichan044 80c079c
refactor: add useLanguageLiteral
sushichan044 e30eb26
perf: lazy media
sushichan044 0c3778c
chore: layout
sushichan044 016937b
perf: content-visibility: auto
sushichan044 1f50956
fix: ページレイアウト調整
sushichan044 7c93c13
fix: note id, note date の表示位置を上にする
sushichan044 707dd15
perf: cache date string calculation
sushichan044 8724e47
fix: useMultiSelectInputControl をそのままコンポーネントに渡さない
sushichan044 df583f3
feat: 詳細検索のみの条件を簡易検索画面でも保持する
sushichan044 d11a601
ci: add code check action
sushichan044 8e8b8c8
ci: add vitest reporter
sushichan044 5e040c1
chore: set mock locale
sushichan044 9b26a6d
auto: codegen
sushichan044 c562f79
chore: リンクラベルを短く
sushichan044 cb85562
fix: スマホでノートの言語が潰れていた
sushichan044 0fa012c
fix: スマホで大きな選択 UI が画面幅をはみ出していた
sushichan044 3ba704b
fix: always use UTC
sushichan044 46536fc
chore: add check script
sushichan044 c68473a
fix: ラベルの fallback は en とする
sushichan044 abdf607
docs: リリース前後の meta tag 操作コメント
sushichan044 fd1aa54
chore: remove dead file
sushichan044 6dfc798
fix(a11y): add alt text on media
sushichan044 e87098a
fix(a11y): pagination label
sushichan044 322b69b
fix(a11y): main tag
sushichan044 9bf9cab
add controls and links
yu23ki14 7291478
fix lint check
yu23ki14 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
branches: | ||
- main | ||
|
||
concurrency: | ||
group: ${{ github.workflow }}-${{ github.ref }} | ||
cancel-in-progress: true | ||
|
||
defaults: | ||
run: | ||
shell: bash | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
ci: | ||
name: Code Problem Check | ||
runs-on: ubuntu-24.04 | ||
timeout-minutes: 10 | ||
steps: | ||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
with: | ||
persist-credentials: false | ||
|
||
- name: Setup Node.js and yarn | ||
uses: ./.github/workflows/composite/setup | ||
|
||
- name: Build | ||
run: yarn run build | ||
|
||
- name: Run ESLint | ||
run: yarn run lint | ||
|
||
- name: Run Prettier | ||
run: yarn run format:ci | ||
|
||
- name: Run typecheck | ||
run: yarn run typecheck | ||
|
||
- name: Run Vitest | ||
run: yarn run test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: Setup Node.js | ||
description: Setup Node.js and yarn | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
- name: Setup yarn | ||
shell: bash | ||
run: corepack enable yarn | ||
|
||
- name: Get yarn cache directory path | ||
id: yarn-store | ||
shell: bash | ||
run: echo "store_path=$(yarn cache dir)" >> "$GITHUB_OUTPUT" | ||
|
||
- name: Setup Node.js | ||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 | ||
with: | ||
node-version-file: package.json | ||
|
||
- name: Restore yarn cache | ||
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 | ||
with: | ||
path: ${{ steps.yarn-store.outputs.store_path }} | ||
key: ${{ runner.os }}-yarn-store-${{ hashFiles('**/yarn.lock') }} | ||
restore-keys: | | ||
${{ runner.os }}-yarn-store- | ||
|
||
- name: Install Dependencies | ||
shell: bash | ||
run: yarn install --frozen-lockfile | ||
|
||
- name: Save pnpm cache if main branch | ||
if: github.ref_name == 'main' | ||
id: save-yarn-cache | ||
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 | ||
with: | ||
path: ${{ steps.yarn-store.outputs.store_path }} | ||
key: ${{ runner.os }}-yarn-store-${{ hashFiles('**/yarn.lock') }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,7 @@ node_modules | |
/.cache | ||
/build | ||
.env | ||
|
||
.vscode/* | ||
!.vscode/settings.example.json | ||
eslint-typegen.d.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"editor.defaultFormatter": "esbenp.prettier-vscode", | ||
"editor.formatOnSave": true, | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll.eslint": "explicit" | ||
}, | ||
"eslint.validate": [ | ||
"javascript", | ||
"javascriptreact", | ||
"typescript", | ||
"typescriptreact" | ||
], | ||
"typescript.tsdk": "node_modules/typescript/lib", | ||
"typescript.enablePromptUseWorkspaceTsdk": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
@import "tailwindcss"; | ||
|
||
@utility content-visibility-auto { | ||
content-visibility: auto; | ||
} | ||
|
||
body { | ||
color: #222; | ||
} | ||
|
||
* { | ||
letter-spacing: 0.05em; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { MantineSize } from "@mantine/core"; | ||
import { List, ListItem } from "@mantine/core"; | ||
import { useMemo } from "react"; | ||
|
||
type FormErrorProps = { | ||
errors: Array<string[] | null | undefined> | string[] | null | undefined; | ||
/** | ||
* @default "xs" | ||
*/ | ||
size?: MantineSize; | ||
}; | ||
|
||
export const FormError = ({ errors, size }: FormErrorProps) => { | ||
size ??= "xs"; | ||
|
||
const flattenErrors = useMemo( | ||
() => errors?.flat().filter((v) => v != null), | ||
[errors], | ||
); | ||
if (flattenErrors == null || flattenErrors.length === 0) { | ||
return undefined; | ||
} | ||
|
||
return ( | ||
<List listStyleType="none" size={size}> | ||
{flattenErrors.map((error, index) => ( | ||
<ListItem key={index}>{error}</ListItem> | ||
))} | ||
</List> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { ButtonProps, PolymorphicComponentProps } from "@mantine/core"; | ||
import { Button } from "@mantine/core"; | ||
import type React from "react"; | ||
|
||
type SubmitButtonProps = Omit< | ||
PolymorphicComponentProps<"button", ButtonProps>, | ||
"type" | ||
> & { | ||
children: React.ReactNode; | ||
loading?: boolean; | ||
}; | ||
|
||
export const SubmitButton = ({ disabled, ...rest }: SubmitButtonProps) => { | ||
// React19 へアップデートしたら useFormStatus() を追加して送信中はボタンを無効化することを検討する | ||
|
||
return <Button disabled={disabled} type="submit" {...rest} />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { getFormProps, useForm } from "@conform-to/react"; | ||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; | ||
|
||
import { render, screen } from "../../../test/test-react"; | ||
import { userEvent } from "../../../test/vitest-setup"; | ||
import { DateRangePicker } from "./DateRangePicker"; | ||
|
||
type Form = { | ||
/** | ||
* ISO String | ||
*/ | ||
start: string | null; | ||
/** | ||
* ISO String | ||
*/ | ||
end: string | null; | ||
}; | ||
|
||
type PageProps = { | ||
defaultValue: Form; | ||
}; | ||
|
||
beforeEach(() => { | ||
vi.useFakeTimers({ shouldAdvanceTime: true }); | ||
// GitHub Actions では TZ が UTC になっているため、テスト時の TZ も UTC に設定する | ||
process.env.TZ = "UTC"; | ||
}); | ||
|
||
afterEach(() => { | ||
vi.useRealTimers(); | ||
}); | ||
|
||
const Page = ({ defaultValue }: PageProps) => { | ||
const [form, fields] = useForm<Form>({ | ||
defaultValue, | ||
}); | ||
|
||
return ( | ||
<div> | ||
<form {...getFormProps(form)}> | ||
<DateRangePicker | ||
convertFormValueToMantine={(value) => | ||
value ? new Date(value) : null | ||
} | ||
convertMantineValueToForm={(date) => date?.toISOString()} | ||
fromField={fields.start} | ||
label="Date Range" | ||
toField={fields.end} | ||
/> | ||
<button type="submit">Submit</button> | ||
</form> | ||
<span aria-label="result"> | ||
{fields.start.value} – {fields.end.value} | ||
</span> | ||
</div> | ||
); | ||
}; | ||
|
||
describe("DateRangePicker", () => { | ||
test("convert で指定した処理を用いて UI の値をフォームに反映できる", async () => { | ||
// 確実に 2025 年 1 月のカレンダーを表示するため、システム時刻を固定する | ||
vi.setSystemTime(new Date("2025-01-15T00:00:00Z")); | ||
render(<Page defaultValue={{ start: null, end: null }} />); | ||
|
||
const button = screen.getByRole("button", { name: "Date Range" }); | ||
await userEvent.click(button); | ||
|
||
// start: 2025-01-09T15:00:00.000Z | ||
const date1 = screen.getByRole("button", { name: "10 1月 2025" }); | ||
await userEvent.click(date1); | ||
|
||
// end: 2025-01-14T15:00:00.000Z | ||
const date2 = screen.getByRole("button", { name: "15 1月 2025" }); | ||
await userEvent.click(date2); | ||
|
||
const span = screen.getByLabelText("result"); | ||
expect(span).toHaveTextContent( | ||
"2025-01-10T00:00:00.000Z – 2025-01-15T00:00:00.000Z", | ||
); | ||
}); | ||
|
||
test("convert で指定した処理を用いてフォームの値を UI に反映できる", () => { | ||
render( | ||
<Page | ||
defaultValue={{ | ||
start: "2025-01-09T15:00:00.000Z", | ||
end: "2025-01-14T15:00:00.000Z", | ||
}} | ||
/>, | ||
); | ||
|
||
const button = screen.getByRole("button", { name: "Date Range" }); | ||
expect(button).toHaveTextContent("2025.01.09 (木) – 2025.01.14 (火)"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { DatePickerInput } from "@mantine/dates"; | ||
|
||
import { useDateRangeInputControl } from "../../hooks/useDateRangeInputControl"; | ||
import { containsNonNullValues } from "../../utils/array"; | ||
import { FormError } from "../FormError"; | ||
|
||
type DateRangePickerProps = Parameters<typeof useDateRangeInputControl>[0] & { | ||
disabled?: boolean; | ||
label: string; | ||
/** | ||
* 入力した値を表示する際のフォーマット | ||
* | ||
* @default "YYYY.MM.DD (ddd)" | ||
* | ||
* @see {@link https://day.js.org/docs/en/display/format Day.js のフォーマット仕様} | ||
*/ | ||
valueFormat?: string; | ||
}; | ||
|
||
export const DateRangePicker = ({ | ||
disabled, | ||
toField, | ||
fromField, | ||
label, | ||
valueFormat, | ||
convertMantineValueToForm, | ||
convertFormValueToMantine, | ||
}: DateRangePickerProps) => { | ||
valueFormat ??= "YYYY.MM.DD (ddd)"; | ||
|
||
const { | ||
value, | ||
change: onChange, | ||
focus: onFocus, | ||
blur: onBlur, | ||
} = useDateRangeInputControl({ | ||
fromField, | ||
toField, | ||
convertMantineValueToForm, | ||
convertFormValueToMantine, | ||
}); | ||
|
||
return ( | ||
<DatePickerInput | ||
clearable | ||
disabled={disabled} | ||
error={ | ||
containsNonNullValues(fromField.errors, toField.errors) && ( | ||
<FormError errors={[fromField.errors, toField.errors]} /> | ||
) | ||
} | ||
errorProps={{ component: "div" }} | ||
label={label} | ||
onBlur={onBlur} | ||
onChange={onChange} | ||
onFocus={onFocus} | ||
type="range" | ||
value={value} | ||
valueFormat={valueFormat} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import "./fieldset.css"; | ||
|
||
import type { FieldsetProps as MantineFieldsetProps } from "@mantine/core"; | ||
// このファイルは no-restricted-imports で提案される代替コンポーネントなので問題ない | ||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports | ||
import { Fieldset as MantineFieldSet } from "@mantine/core"; | ||
type FieldSetProps = Omit<MantineFieldsetProps, "variant">; | ||
|
||
export const Fieldset = (props: FieldSetProps) => { | ||
return <MantineFieldSet variant="unstyled" {...props} />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { TextInputProps as MantineTextInputProps } from "@mantine/core"; | ||
// このファイルは no-restricted-imports で提案される代替コンポーネントなので問題ない | ||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports | ||
import { TextInput as MantineTextInput } from "@mantine/core"; | ||
|
||
import { mantineInputOrder } from "../../config/mantine"; | ||
|
||
type TextInputProps = Omit<MantineTextInputProps, "inputWrapperOrder">; | ||
|
||
export const TextInput = (props: TextInputProps) => { | ||
const { autoComplete, ...rest } = props; | ||
|
||
return ( | ||
<MantineTextInput | ||
autoComplete={autoComplete} | ||
inputWrapperOrder={mantineInputOrder} | ||
{...(autoComplete === "off" && { "data-1p-ignore": true })} | ||
{...rest} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.mantine-Fieldset-legend { | ||
font-size: 1.25em; | ||
font-weight: 700; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1password が必要以上に自動補完してくるのを防ぐ