Skip to content

Commit f0d5b47

Browse files
committed
refactor(admin): tighten modal chrome and introduce ModalFooter
Shrink ModalHeader to h-12 px-4 with text-base title to match the denser DSv2 modal scale. Add a shared ModalFooter (px-4 py-2, gap-2, border-t border-border) and migrate 13 modals that previously hand- rolled the footer with mismatched padding and raw neutral-200 borders. Update apps/admin/CLAUDE.md typography table to split card title (text-lg) from modal header (text-base).
1 parent b6865e6 commit f0d5b47

14 files changed

Lines changed: 87 additions & 72 deletions

File tree

apps/admin/CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ Tailwind classes:
104104
|---------|-------|------|----------|
105105
| Page title | `text-2xl` | 24px | Main page titles |
106106
| Section title | `text-xl` | 20px | Section headers |
107-
| Card/Modal title | `text-lg` | 18px | Card titles, modal headers |
107+
| Card title | `text-lg` | 18px | Card titles |
108+
| Modal header | `text-base` | 16px | Modal headers |
108109
| Secondary title | `text-base` | 16px | Sub-headings, stats |
109110
| Body text | `text-sm` | 14px | List items, form labels, buttons |
110111
| Metadata | `text-xs` | 12px | Timestamps, badges, descriptions |

apps/admin/src/features/categories/components/CategoryFormModal.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { useMutation } from '@tanstack/react-query'
22
import { Loader2 } from 'lucide-react'
3-
import { FormEvent, useState } from 'react'
3+
import type { FormEvent } from 'react'
4+
import { useState } from 'react'
45
import { toast } from 'sonner'
5-
import type { CreateCategoryData } from '~/api/categories'
6-
import type { CategoryModel } from '~/models/category'
7-
import type { CategoryFormMode } from '../types/categories'
86

7+
import type { CreateCategoryData } from '~/api/categories'
98
import { createCategory, updateCategory } from '~/api/categories'
109
import { useI18n } from '~/i18n'
11-
import { ModalHeader } from '~/ui/feedback/modal'
10+
import type { CategoryModel } from '~/models/category'
11+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1212
import { present, useModal } from '~/ui/feedback/modal-imperative'
1313
import { Button } from '~/ui/primitives/button'
1414
import { TextInput } from '~/ui/primitives/text-field'
1515

16+
import type { CategoryFormMode } from '../types/categories'
1617
import { getErrorMessage } from '../utils/errors'
1718

1819
interface CategoryFormModalProps {
@@ -84,7 +85,7 @@ function CategoryFormModal(props: CategoryFormModalProps) {
8485
value={slug}
8586
/>
8687
</div>
87-
<div className="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
88+
<ModalFooter>
8889
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
8990
{t('common.cancel')}
9091
</Button>
@@ -94,7 +95,7 @@ function CategoryFormModal(props: CategoryFormModalProps) {
9495
) : null}
9596
{t('common.save')}
9697
</Button>
97-
</div>
98+
</ModalFooter>
9899
</form>
99100
)
100101
}

apps/admin/src/features/friends/components/AuditReasonModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { useMutation } from '@tanstack/react-query'
22
import { useState } from 'react'
33
import { toast } from 'sonner'
4-
import type { LinkModel } from '~/models/link'
54

65
import { auditLinkWithReason } from '~/api/links'
76
import { useI18n } from '~/i18n'
7+
import type { LinkModel } from '~/models/link'
88
import { LinkState, LinkStateNameKeys } from '~/models/link'
9-
import { ModalHeader } from '~/ui/feedback/modal'
9+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1010
import { present, useModal } from '~/ui/feedback/modal-imperative'
1111
import { Button } from '~/ui/primitives/button'
1212
import { SelectField } from '~/ui/primitives/select'
@@ -59,7 +59,7 @@ function AuditReasonModal(props: AuditReasonModalProps) {
5959
value={reason}
6060
/>
6161
</div>
62-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
62+
<ModalFooter>
6363
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
6464
{t('common.cancel')}
6565
</Button>
@@ -70,7 +70,7 @@ function AuditReasonModal(props: AuditReasonModalProps) {
7070
>
7171
{t('friends.audit.send')}
7272
</Button>
73-
</div>
73+
</ModalFooter>
7474
</div>
7575
)
7676
}

apps/admin/src/features/friends/components/FriendEditorModal.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { useMutation } from '@tanstack/react-query'
2-
import { FormEvent, useState } from 'react'
2+
import type { FormEvent } from 'react'
3+
import { useState } from 'react'
34
import { toast } from 'sonner'
4-
import type { LinkModel } from '~/models/link'
55

66
import { createLink, updateLink } from '~/api/links'
77
import { useI18n } from '~/i18n'
8+
import type { LinkModel } from '~/models/link'
89
import { LinkState, LinkStateNameKeys, LinkType } from '~/models/link'
9-
import { ModalHeader } from '~/ui/feedback/modal'
10+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1011
import { present, useModal } from '~/ui/feedback/modal-imperative'
1112
import { Button } from '~/ui/primitives/button'
1213
import { SelectField } from '~/ui/primitives/select'
@@ -137,14 +138,14 @@ function FriendEditorModal(props: FriendEditorModalProps) {
137138
{error ? <span className="text-xs text-red-500">{error}</span> : null}
138139
</div>
139140

140-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
141+
<ModalFooter>
141142
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
142143
{t('common.cancel')}
143144
</Button>
144145
<Button disabled={mutation.isPending} type="submit">
145146
{t('friends.editor.submit')}
146147
</Button>
147-
</div>
148+
</ModalFooter>
148149
</form>
149150
)
150151
}

apps/admin/src/features/notes/components/NoteMetaEditModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState } from 'react'
22

33
import { useI18n } from '~/i18n'
4-
import { ModalHeader } from '~/ui/feedback/modal'
4+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
55
import { present, useModal } from '~/ui/feedback/modal-imperative'
66
import { Button } from '~/ui/primitives/button'
77

@@ -42,12 +42,12 @@ function NoteMetaEditDialog(props: NoteMetaEditDialogProps) {
4242
value={value}
4343
/>
4444
</div>
45-
<div className="flex justify-end gap-2 border-t border-neutral-200 px-4 py-3 dark:border-neutral-800">
45+
<ModalFooter>
4646
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
4747
{t('common.cancel')}
4848
</Button>
4949
<Button type="submit">{t('common.save')}</Button>
50-
</div>
50+
</ModalFooter>
5151
</form>
5252
)
5353
}

apps/admin/src/features/readers/components/modals/BanReaderModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { useState } from 'react'
2-
import type { ReaderModel } from '~/api/readers'
32

3+
import type { ReaderModel } from '~/api/readers'
44
import { useI18n } from '~/i18n'
5-
import { ModalHeader } from '~/ui/feedback/modal'
5+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
66
import { present, useModal } from '~/ui/feedback/modal-imperative'
77
import { Button } from '~/ui/primitives/button'
88
import { TextArea } from '~/ui/primitives/text-field'
@@ -38,7 +38,7 @@ function BanReaderModal(props: BanReaderModalProps) {
3838
value={reason}
3939
/>
4040
</div>
41-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
41+
<ModalFooter>
4242
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
4343
{t('common.cancel')}
4444
</Button>
@@ -49,7 +49,7 @@ function BanReaderModal(props: BanReaderModalProps) {
4949
>
5050
{t('readers.ban.submit')}
5151
</Button>
52-
</div>
52+
</ModalFooter>
5353
</div>
5454
)
5555
}

apps/admin/src/features/recently/components/RecentlyEditorModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { resolveEnrichment } from '~/api/enrichment'
88
import { createRecently, updateRecently } from '~/api/recently'
99
import { useI18n } from '~/i18n'
1010
import type { RecentlyModel } from '~/models/recently'
11-
import { ModalHeader } from '~/ui/feedback/modal'
11+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1212
import { present, useModal } from '~/ui/feedback/modal-imperative'
1313
import { Button } from '~/ui/primitives/button'
1414
import { TextArea } from '~/ui/primitives/text-field'
@@ -181,8 +181,8 @@ function RecentlyEditorModal(props: RecentlyEditorModalProps) {
181181
) : null}
182182
</div>
183183

184-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
185-
<span className="mr-auto text-xs text-neutral-400">
184+
<ModalFooter>
185+
<span className="mr-auto text-xs text-fg-subtle">
186186
{t('recently.editor.shortcut')}
187187
</span>
188188
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
@@ -191,7 +191,7 @@ function RecentlyEditorModal(props: RecentlyEditorModalProps) {
191191
<Button disabled={mutation.isPending} type="submit">
192192
{t('common.save')}
193193
</Button>
194-
</div>
194+
</ModalFooter>
195195
</form>
196196
)
197197
}

apps/admin/src/features/says/components/SayEditorModal.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useMutation } from '@tanstack/react-query'
2-
import { FormEvent, useState } from 'react'
2+
import type { FormEvent } from 'react'
3+
import { useState } from 'react'
34
import { toast } from 'sonner'
4-
import type { SayModel } from '~/models/say'
55

66
import { createSay, updateSay } from '~/api/says'
77
import { useI18n } from '~/i18n'
8-
import { ModalHeader } from '~/ui/feedback/modal'
8+
import type { SayModel } from '~/models/say'
9+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
910
import { present, useModal } from '~/ui/feedback/modal-imperative'
1011
import { Button } from '~/ui/primitives/button'
1112
import { TextArea, TextInput } from '~/ui/primitives/text-field'
@@ -100,8 +101,8 @@ function SayEditorModal(props: SayEditorModalProps) {
100101
/>
101102
</div>
102103

103-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
104-
<span className="mr-auto text-xs text-neutral-400">
104+
<ModalFooter>
105+
<span className="mr-auto text-xs text-fg-subtle">
105106
{t('says.dialog.shortcut')}
106107
</span>
107108
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
@@ -110,7 +111,7 @@ function SayEditorModal(props: SayEditorModalProps) {
110111
<Button disabled={mutation.isPending} type="submit">
111112
{isEdit ? t('common.save') : t('says.dialog.publish')}
112113
</Button>
113-
</div>
114+
</ModalFooter>
114115
</form>
115116
)
116117
}

apps/admin/src/features/topics/components/TopicFormModal.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import { useMutation, useQuery } from '@tanstack/react-query'
22
import { Loader2, Save, Upload } from 'lucide-react'
3-
import { FormEvent, useEffect, useRef, useState } from 'react'
3+
import type { FormEvent } from 'react'
4+
import { useEffect, useRef, useState } from 'react'
45
import { toast } from 'sonner'
5-
import type { CreateTopicData } from '~/api/topics'
6-
import type { TopicModel } from '~/models/topic'
7-
import type { TopicFormMode } from '../types/topics'
86

97
import { uploadFile } from '~/api/files'
8+
import type { CreateTopicData } from '~/api/topics'
109
import { createTopic, getTopic, updateTopic } from '~/api/topics'
1110
import { useI18n } from '~/i18n'
11+
import type { TopicModel } from '~/models/topic'
1212
import { adminQueryKeys } from '~/query/keys'
13-
import { ModalHeader } from '~/ui/feedback/modal'
13+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1414
import { present, useModal } from '~/ui/feedback/modal-imperative'
1515
import { Button } from '~/ui/primitives/button'
1616
import { TextArea, TextInput } from '~/ui/primitives/text-field'
1717

18+
import type { TopicFormMode } from '../types/topics'
1819
import { getErrorMessage } from '../utils/errors'
1920
import { validateTopicForm } from '../utils/topic-form'
2021

@@ -186,7 +187,7 @@ function TopicFormModal(props: TopicFormModalProps) {
186187
</div>
187188
)}
188189

189-
<div className="flex justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
190+
<ModalFooter>
190191
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
191192
{t('common.cancel')}
192193
</Button>
@@ -201,7 +202,7 @@ function TopicFormModal(props: TopicFormModalProps) {
201202
)}
202203
{t('common.save')}
203204
</Button>
204-
</div>
205+
</ModalFooter>
205206
</form>
206207
)
207208
}

apps/admin/src/features/webhooks/components/WebhookEditorModal.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useMutation, useQuery } from '@tanstack/react-query'
2-
import { FormEvent, useState } from 'react'
2+
import type { FormEvent } from 'react'
3+
import { useState } from 'react'
34
import { toast } from 'sonner'
4-
import type { WebhookModel } from '~/api/webhooks'
55

6+
import type { WebhookModel } from '~/api/webhooks'
67
import {
78
createWebhook,
89
EventScope,
@@ -11,7 +12,7 @@ import {
1112
} from '~/api/webhooks'
1213
import { useI18n } from '~/i18n'
1314
import { adminQueryKeys } from '~/query/keys'
14-
import { ModalHeader } from '~/ui/feedback/modal'
15+
import { ModalFooter, ModalHeader } from '~/ui/feedback/modal'
1516
import { present, useModal } from '~/ui/feedback/modal-imperative'
1617
import { Button } from '~/ui/primitives/button'
1718
import { Checkbox } from '~/ui/primitives/checkbox'
@@ -187,14 +188,14 @@ function WebhookEditorModal(props: WebhookEditorModalProps) {
187188
{error ? <span className="text-xs text-red-500">{error}</span> : null}
188189
</Scroll>
189190

190-
<div className="flex items-center justify-end gap-2 border-t border-neutral-200 px-5 py-4 dark:border-neutral-800">
191+
<ModalFooter>
191192
<Button onClick={() => modal.dismiss()} type="button" variant="subtle">
192193
{t('common.cancel')}
193194
</Button>
194195
<Button disabled={mutation.isPending} type="submit">
195196
{isEdit ? t('common.save') : t('common.create')}
196197
</Button>
197-
</div>
198+
</ModalFooter>
198199
</form>
199200
)
200201
}

0 commit comments

Comments
 (0)