@meith/web 0.35.1 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/(board)/member/[id]/reputation/page.tsx +2 -4
- package/app/(board)/plugins/[key]/[[...path]]/page.tsx +2 -1
- package/app/(board)/search/page.tsx +2 -1
- package/app/(board)/unsubscribe/page.tsx +2 -2
- package/app/admin/layout.tsx +9 -1
- package/app/admin/plugins/[key]/[[...path]]/page.tsx +1 -3
- package/app/install/page.tsx +2 -4
- package/app/layout.tsx +2 -0
- package/package.json +50 -50
- package/src/components/account/active-sessions.tsx +2 -2
- package/src/components/account/avatar-form.tsx +3 -2
- package/src/components/account/feed-token.tsx +5 -5
- package/src/components/account/notification-forms.tsx +4 -6
- package/src/components/account/push-device-form.tsx +3 -2
- package/src/components/account/reputation-forms.tsx +5 -4
- package/src/components/account/sign-in-methods.tsx +2 -2
- package/src/components/account/subscription-forms.tsx +6 -8
- package/src/components/account/two-factor-forms.tsx +3 -4
- package/src/components/account/usercp-forms.tsx +48 -59
- package/src/components/admin/admin-forms.tsx +27 -18
- package/src/components/admin/form-bits.tsx +2 -3
- package/src/components/admin/mail-test-card.tsx +3 -1
- package/src/components/admin/theme-forms.tsx +4 -5
- package/src/components/auth/form-controls.tsx +22 -11
- package/src/components/content/edit-post-form.tsx +3 -2
- package/src/components/content/new-thread-form.tsx +4 -4
- package/src/components/messages/message-forms.tsx +9 -10
- package/src/components/moderation/inline-moderation-form.tsx +3 -2
- package/src/components/moderation/queue-form.tsx +3 -3
- package/src/components/moderation/report-forms.tsx +3 -3
- package/src/components/moderation/thread-surgery-form.tsx +4 -4
- package/src/components/moderation/thread-tools-form.tsx +4 -4
- package/src/components/moderation/warning-forms.tsx +8 -8
- package/src/components/shell/install-action.tsx +3 -2
- package/src/components/shell/nav-disclosure-enhancer.tsx +3 -0
- package/src/components/shell/scheme-toggle.tsx +1 -1
- package/src/components/shell/theme-switcher.tsx +9 -8
- package/src/components/shell/view-tabs.tsx +1 -65
- package/src/server/upgrade-notice.ts +1 -1
- package/src/styles/globals.css +63 -0
- package/src/view/copy.ts +2 -0
- package/src/view/panel-nav.ts +8 -10
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
5
|
import type { SubscriptionMode } from '@meith/subscriptions'
|
|
6
|
+
import { buttonVariants, controlVariants, surfaceVariants } from '@meith/ui'
|
|
6
7
|
|
|
7
8
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
8
9
|
import {
|
|
@@ -14,20 +15,18 @@ import {
|
|
|
14
15
|
saveSignatureAction,
|
|
15
16
|
} from '@/server/usercp-actions'
|
|
16
17
|
|
|
17
|
-
import { FormError, PendingButton } from '../auth/form-controls'
|
|
18
|
+
import { Field, FormError, PendingButton } from '../auth/form-controls'
|
|
18
19
|
import { MarkdownEditor } from '../content/markdown-editor'
|
|
19
20
|
import { CustomField, type CustomFieldInput } from '../profile/custom-field'
|
|
20
21
|
import { type Copy, fromCopy } from '../shell/copy'
|
|
21
22
|
|
|
22
23
|
export type { CustomFieldInput }
|
|
23
24
|
|
|
24
|
-
const FIELD =
|
|
25
|
-
'w-full rounded-md border border-input bg-card px-3 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
25
|
+
const FIELD = controlVariants()
|
|
26
26
|
|
|
27
|
-
const BUTTON =
|
|
28
|
-
'inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
27
|
+
const BUTTON = buttonVariants({ variant: 'primary', size: 'default' })
|
|
29
28
|
|
|
30
|
-
const CARD =
|
|
29
|
+
const CARD = surfaceVariants({ padded: true })
|
|
31
30
|
|
|
32
31
|
export function ProfileForm({
|
|
33
32
|
location,
|
|
@@ -48,12 +47,12 @@ export function ProfileForm({
|
|
|
48
47
|
<form action={action} className={CARD}>
|
|
49
48
|
<FormError message={state.error} />
|
|
50
49
|
|
|
51
|
-
<label className="flex flex-col gap-
|
|
50
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
52
51
|
<span className="font-medium">{fromCopy(copy, 'accountForm.profile.location')}</span>
|
|
53
52
|
<input name="location" defaultValue={location} className={FIELD} maxLength={100} />
|
|
54
53
|
</label>
|
|
55
54
|
|
|
56
|
-
<label className="flex flex-col gap-
|
|
55
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
57
56
|
<span className="font-medium">{fromCopy(copy, 'accountForm.profile.website')}</span>
|
|
58
57
|
<input
|
|
59
58
|
name="website"
|
|
@@ -64,7 +63,7 @@ export function ProfileForm({
|
|
|
64
63
|
/>
|
|
65
64
|
</label>
|
|
66
65
|
|
|
67
|
-
<label className="flex flex-col gap-
|
|
66
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
68
67
|
<span className="font-medium">{fromCopy(copy, 'accountForm.profile.bio')}</span>
|
|
69
68
|
<textarea name="bio" defaultValue={bio} className={FIELD} rows={5} maxLength={1000} />
|
|
70
69
|
<span className="text-xs text-muted-foreground">
|
|
@@ -103,7 +102,7 @@ export function DisplayGroupForm({
|
|
|
103
102
|
</h2>
|
|
104
103
|
<FormError message={state.error} />
|
|
105
104
|
|
|
106
|
-
<label className="flex flex-col gap-
|
|
105
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
107
106
|
<span className="font-medium">{fromCopy(copy, 'accountForm.displayGroup.label')}</span>
|
|
108
107
|
<select name="displayGroupId" defaultValue={selected} className={FIELD}>
|
|
109
108
|
{choices.map((choice) => (
|
|
@@ -157,7 +156,7 @@ export function OptionsForm({
|
|
|
157
156
|
<form action={action} className={CARD}>
|
|
158
157
|
<FormError message={state.error} />
|
|
159
158
|
|
|
160
|
-
<label className="flex flex-col gap-
|
|
159
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
161
160
|
<span className="font-medium">{fromCopy(copy, 'accountForm.options.timezone')}</span>
|
|
162
161
|
<select name="timezone" defaultValue={timezone} className={FIELD}>
|
|
163
162
|
{timezones.map((zone) => (
|
|
@@ -171,7 +170,7 @@ export function OptionsForm({
|
|
|
171
170
|
</span>
|
|
172
171
|
</label>
|
|
173
172
|
|
|
174
|
-
<label className="flex flex-col gap-
|
|
173
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
175
174
|
<span className="font-medium">{fromCopy(copy, 'accountForm.options.locale')}</span>
|
|
176
175
|
<select name="locale" defaultValue={locale} className={FIELD}>
|
|
177
176
|
{locales.map((choice) => (
|
|
@@ -185,7 +184,7 @@ export function OptionsForm({
|
|
|
185
184
|
</span>
|
|
186
185
|
</label>
|
|
187
186
|
|
|
188
|
-
<label className="flex flex-col gap-
|
|
187
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
189
188
|
<span className="font-medium">{fromCopy(copy, 'accountForm.options.postsPerPage')}</span>
|
|
190
189
|
<input
|
|
191
190
|
name="postsPerPage"
|
|
@@ -196,7 +195,7 @@ export function OptionsForm({
|
|
|
196
195
|
/>
|
|
197
196
|
</label>
|
|
198
197
|
|
|
199
|
-
<label className="flex flex-col gap-
|
|
198
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
200
199
|
<span className="font-medium">{fromCopy(copy, 'accountForm.options.threadsPerPage')}</span>
|
|
201
200
|
<input
|
|
202
201
|
name="threadsPerPage"
|
|
@@ -221,7 +220,7 @@ export function OptionsForm({
|
|
|
221
220
|
</span>
|
|
222
221
|
</label>
|
|
223
222
|
|
|
224
|
-
<label className="flex flex-col gap-
|
|
223
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
225
224
|
<span className="font-medium">
|
|
226
225
|
{fromCopy(copy, 'accountForm.options.autoWatchOwnThreads')}
|
|
227
226
|
</span>
|
|
@@ -237,7 +236,7 @@ export function OptionsForm({
|
|
|
237
236
|
</span>
|
|
238
237
|
</label>
|
|
239
238
|
|
|
240
|
-
<label className="flex flex-col gap-
|
|
239
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
241
240
|
<span className="font-medium">
|
|
242
241
|
{fromCopy(copy, 'accountForm.options.autoWatchRepliedThreads')}
|
|
243
242
|
</span>
|
|
@@ -276,40 +275,32 @@ export function PasswordForm({ minLength, copy }: { minLength: number; copy: Cop
|
|
|
276
275
|
</h2>
|
|
277
276
|
<FormError message={state.error} />
|
|
278
277
|
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
/>
|
|
288
|
-
</label>
|
|
278
|
+
<Field
|
|
279
|
+
label={fromCopy(copy, 'accountForm.password.current')}
|
|
280
|
+
type="password"
|
|
281
|
+
name="currentPassword"
|
|
282
|
+
autoComplete="current-password"
|
|
283
|
+
required
|
|
284
|
+
id="password-currentPassword"
|
|
285
|
+
/>
|
|
289
286
|
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
required
|
|
299
|
-
/>
|
|
300
|
-
</label>
|
|
287
|
+
<Field
|
|
288
|
+
label={fromCopy(copy, 'accountForm.password.new')}
|
|
289
|
+
type="password"
|
|
290
|
+
name="newPassword"
|
|
291
|
+
autoComplete="new-password"
|
|
292
|
+
minLength={minLength}
|
|
293
|
+
required
|
|
294
|
+
/>
|
|
301
295
|
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
required
|
|
311
|
-
/>
|
|
312
|
-
</label>
|
|
296
|
+
<Field
|
|
297
|
+
label={fromCopy(copy, 'accountForm.password.confirm')}
|
|
298
|
+
type="password"
|
|
299
|
+
name="confirmPassword"
|
|
300
|
+
autoComplete="new-password"
|
|
301
|
+
minLength={minLength}
|
|
302
|
+
required
|
|
303
|
+
/>
|
|
313
304
|
|
|
314
305
|
<p className="text-xs text-muted-foreground">{fromCopy(copy, 'accountForm.password.note')}</p>
|
|
315
306
|
|
|
@@ -338,18 +329,16 @@ export function EmailForm({ email, copy }: { email: string; copy: Copy }) {
|
|
|
338
329
|
{copy['accountForm.email.currentTail']}
|
|
339
330
|
</p>
|
|
340
331
|
|
|
341
|
-
<
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
/>
|
|
350
|
-
</label>
|
|
332
|
+
<Field
|
|
333
|
+
label={fromCopy(copy, 'accountForm.password.current')}
|
|
334
|
+
type="password"
|
|
335
|
+
name="currentPassword"
|
|
336
|
+
autoComplete="current-password"
|
|
337
|
+
required
|
|
338
|
+
id="email-currentPassword"
|
|
339
|
+
/>
|
|
351
340
|
|
|
352
|
-
<label className="flex flex-col gap-
|
|
341
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
353
342
|
<span className="font-medium">{fromCopy(copy, 'accountForm.email.new')}</span>
|
|
354
343
|
<input type="email" name="newEmail" className={FIELD} autoComplete="email" required />
|
|
355
344
|
</label>
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants, cn, controlVariants, PageTitle, surfaceVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import {
|
|
6
8
|
adminAbandonSecondFactorAction,
|
|
7
9
|
adminSignInAction,
|
|
@@ -30,9 +32,9 @@ export function AdminSignInForm({
|
|
|
30
32
|
return (
|
|
31
33
|
<form
|
|
32
34
|
action={action}
|
|
33
|
-
className=
|
|
35
|
+
className={cn(surfaceVariants({ padded: true }), 'w-full max-w-md gap-6 p-6 sm:p-8')}
|
|
34
36
|
>
|
|
35
|
-
<
|
|
37
|
+
<PageTitle className="sm:text-2xl">{fromCopy(copy, 'adminPanel.title')}</PageTitle>
|
|
36
38
|
|
|
37
39
|
<p className="text-sm text-muted-foreground">
|
|
38
40
|
{reason === 'reauth'
|
|
@@ -52,14 +54,11 @@ export function AdminSignInForm({
|
|
|
52
54
|
name="password"
|
|
53
55
|
autoComplete="current-password"
|
|
54
56
|
required
|
|
55
|
-
className=
|
|
57
|
+
className={controlVariants()}
|
|
56
58
|
/>
|
|
57
59
|
</label>
|
|
58
60
|
|
|
59
|
-
<PendingButton
|
|
60
|
-
showWorking
|
|
61
|
-
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
62
|
-
>
|
|
61
|
+
<PendingButton showWorking className={buttonVariants({ variant: 'primary' })}>
|
|
63
62
|
{fromCopy(copy, 'adminPanel.enter')}
|
|
64
63
|
</PendingButton>
|
|
65
64
|
|
|
@@ -80,10 +79,8 @@ export function AdminSecondFactorForm({
|
|
|
80
79
|
const [state, action] = useActionState(adminVerifySecondFactorAction, EMPTY_STATE)
|
|
81
80
|
|
|
82
81
|
return (
|
|
83
|
-
<div className=
|
|
84
|
-
<
|
|
85
|
-
{fromCopy(copy, 'adminPanel.twoFactor.title')}
|
|
86
|
-
</h1>
|
|
82
|
+
<div className={cn(surfaceVariants({ padded: true }), 'w-full max-w-md gap-6 p-6 sm:p-8')}>
|
|
83
|
+
<PageTitle className="sm:text-2xl">{fromCopy(copy, 'adminPanel.twoFactor.title')}</PageTitle>
|
|
87
84
|
|
|
88
85
|
<p className="text-sm text-muted-foreground">{fromCopy(copy, 'adminPanel.twoFactor.lede')}</p>
|
|
89
86
|
|
|
@@ -101,10 +98,7 @@ export function AdminSecondFactorForm({
|
|
|
101
98
|
recovery={otpRecoveryFromCopy(copy)}
|
|
102
99
|
/>
|
|
103
100
|
|
|
104
|
-
<PendingButton
|
|
105
|
-
showWorking
|
|
106
|
-
className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
107
|
-
>
|
|
101
|
+
<PendingButton showWorking className={buttonVariants({ variant: 'primary' })}>
|
|
108
102
|
{fromCopy(copy, 'adminPanel.twoFactor.submit')}
|
|
109
103
|
</PendingButton>
|
|
110
104
|
</form>
|
|
@@ -125,10 +119,25 @@ export function AdminSignOutForm({ copy }: { copy: Copy }) {
|
|
|
125
119
|
return (
|
|
126
120
|
<form action={adminSignOutAction}>
|
|
127
121
|
<PendingButton
|
|
128
|
-
|
|
129
|
-
className=
|
|
122
|
+
title={fromCopy(copy, 'adminPanel.leave')}
|
|
123
|
+
className={cn(
|
|
124
|
+
buttonVariants({ variant: 'ghost', size: 'icon' }),
|
|
125
|
+
'size-11 sm:w-auto sm:px-3',
|
|
126
|
+
)}
|
|
130
127
|
>
|
|
131
|
-
|
|
128
|
+
<svg
|
|
129
|
+
aria-hidden="true"
|
|
130
|
+
viewBox="0 0 24 24"
|
|
131
|
+
fill="none"
|
|
132
|
+
stroke="currentColor"
|
|
133
|
+
strokeWidth="1.5"
|
|
134
|
+
strokeLinecap="round"
|
|
135
|
+
strokeLinejoin="round"
|
|
136
|
+
className="size-5 sm:hidden"
|
|
137
|
+
>
|
|
138
|
+
<path d="M9 5H5v14h4M14 8l4 4-4 4M9 12h9" />
|
|
139
|
+
</svg>
|
|
140
|
+
<span className="sr-only sm:not-sr-only">{fromCopy(copy, 'adminPanel.leave')}</span>
|
|
132
141
|
</PendingButton>
|
|
133
142
|
</form>
|
|
134
143
|
)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Alert, AlertDescription } from '@meith/ui'
|
|
1
|
+
import { Alert, AlertDescription, controlVariants } from '@meith/ui'
|
|
2
2
|
|
|
3
|
-
export const INPUT =
|
|
4
|
-
'w-full rounded-md border border-input bg-card px-3 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
3
|
+
export const INPUT = controlVariants()
|
|
5
4
|
|
|
6
5
|
export function Saved({ when = true, children }: { when?: boolean; children: React.ReactNode }) {
|
|
7
6
|
if (!when) return null
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { PANEL_CARD } from '@/components/shell/panel-list'
|
|
6
8
|
import { EMPTY_STATE, type FormState } from '@/server/auth-form-state'
|
|
7
9
|
import { sendTestMailAction } from '@/server/mail-test-actions'
|
|
@@ -69,7 +71,7 @@ export function MailTestCard({
|
|
|
69
71
|
<div>
|
|
70
72
|
<PendingButton
|
|
71
73
|
disabled={pending || !sends}
|
|
72
|
-
className=
|
|
74
|
+
className={buttonVariants({ variant: 'primary', size: 'default' })}
|
|
73
75
|
>
|
|
74
76
|
{pending
|
|
75
77
|
? fromCopy(copy, 'adminPanel.mail.sending')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState, useEffect, useState } from 'react'
|
|
4
4
|
|
|
5
|
-
import { textLinkVariants } from '@meith/ui'
|
|
5
|
+
import { buttonVariants, textLinkVariants } from '@meith/ui'
|
|
6
6
|
|
|
7
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
8
8
|
import {
|
|
@@ -42,8 +42,7 @@ import { ThemePreview, ValidatedSample } from './theme-preview'
|
|
|
42
42
|
const INPUT =
|
|
43
43
|
'w-full rounded-md border border-input bg-card px-3 py-2 font-mono text-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
44
44
|
|
|
45
|
-
const GHOST_BUTTON =
|
|
46
|
-
'inline-flex h-8 items-center justify-center rounded-md border border-border px-3 text-xs font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
45
|
+
const GHOST_BUTTON = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
47
46
|
|
|
48
47
|
const LINK = `${textLinkVariants({ tone: 'inherit', size: 'xs' })} focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`
|
|
49
48
|
|
|
@@ -437,7 +436,7 @@ export function ThemeEditorForm({
|
|
|
437
436
|
)
|
|
438
437
|
})}
|
|
439
438
|
|
|
440
|
-
<label className="flex flex-col gap-
|
|
439
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
441
440
|
<span className="font-medium">{fromCopy(copy, 'adminTheme.css.label')}</span>
|
|
442
441
|
<textarea
|
|
443
442
|
name="customCss"
|
|
@@ -552,7 +551,7 @@ export function ImportThemeForm({ themeKey, copy }: { themeKey: string; copy: Co
|
|
|
552
551
|
<Saved when={state.notice === 'imported'}>{fromCopy(copy, 'adminTheme.import.done')}</Saved>
|
|
553
552
|
<input type="hidden" name="key" value={themeKey} />
|
|
554
553
|
|
|
555
|
-
<label className="flex flex-col gap-
|
|
554
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
556
555
|
<span className="font-medium">{fromCopy(copy, 'adminTheme.import.label')}</span>
|
|
557
556
|
<textarea name="document" rows={8} className={INPUT} required />
|
|
558
557
|
<span className="text-xs text-muted-foreground">
|
|
@@ -5,6 +5,7 @@ import { useFormStatus } from 'react-dom'
|
|
|
5
5
|
|
|
6
6
|
import { Alert, AlertDescription, AlertTitle, cn, Input, Field as UiField } from '@meith/ui'
|
|
7
7
|
import { Button } from '@meith/ui/button'
|
|
8
|
+
import { PasswordInput } from '@meith/ui/password-input'
|
|
8
9
|
|
|
9
10
|
import { fromCopy, useCopy } from '../shell/copy'
|
|
10
11
|
|
|
@@ -115,6 +116,15 @@ export function Field({
|
|
|
115
116
|
error,
|
|
116
117
|
id,
|
|
117
118
|
}: FieldProps) {
|
|
119
|
+
const copy = useCopy()
|
|
120
|
+
const inputProps = {
|
|
121
|
+
required,
|
|
122
|
+
...(autoComplete === undefined ? {} : { autoComplete }),
|
|
123
|
+
...(defaultValue === undefined ? {} : { defaultValue }),
|
|
124
|
+
...(minLength === undefined ? {} : { minLength }),
|
|
125
|
+
...(maxLength === undefined ? {} : { maxLength }),
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
return (
|
|
119
129
|
<UiField
|
|
120
130
|
name={name}
|
|
@@ -123,17 +133,18 @@ export function Field({
|
|
|
123
133
|
{...(id === undefined ? {} : { id })}
|
|
124
134
|
{...(hint === undefined ? {} : { description: hint })}
|
|
125
135
|
>
|
|
126
|
-
{(control) =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
{(control) =>
|
|
137
|
+
type === 'password' ? (
|
|
138
|
+
<PasswordInput
|
|
139
|
+
{...control}
|
|
140
|
+
{...inputProps}
|
|
141
|
+
showLabel={fromCopy(copy, 'form.showPassword')}
|
|
142
|
+
hideLabel={fromCopy(copy, 'form.hidePassword')}
|
|
143
|
+
/>
|
|
144
|
+
) : (
|
|
145
|
+
<Input {...control} {...inputProps} type={type} />
|
|
146
|
+
)
|
|
147
|
+
}
|
|
137
148
|
</UiField>
|
|
138
149
|
)
|
|
139
150
|
}
|
|
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react'
|
|
|
4
4
|
import { useActionState } from 'react'
|
|
5
5
|
|
|
6
6
|
import type { UploadLimits } from '@meith/attachments/limits'
|
|
7
|
+
import { buttonVariants } from '@meith/ui'
|
|
7
8
|
|
|
8
9
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
9
10
|
import { deletePostAction, editPostAction, restorePostAction } from '@/server/content-actions'
|
|
@@ -131,7 +132,7 @@ export function EditPostForm({
|
|
|
131
132
|
name="intent"
|
|
132
133
|
value="preview"
|
|
133
134
|
showWorking
|
|
134
|
-
className=
|
|
135
|
+
className={buttonVariants({ variant: 'outline', size: 'default' })}
|
|
135
136
|
>
|
|
136
137
|
{fromCopy(copy, 'composer.preview')}
|
|
137
138
|
</PendingButton>
|
|
@@ -160,7 +161,7 @@ export function DeletePostForm({
|
|
|
160
161
|
<div>
|
|
161
162
|
<PendingButton
|
|
162
163
|
showWorking
|
|
163
|
-
className=
|
|
164
|
+
className={buttonVariants({ variant: 'destructive', size: 'default' })}
|
|
164
165
|
>
|
|
165
166
|
{fromCopy(copy, 'composer.edit.delete')}
|
|
166
167
|
</PendingButton>
|
|
@@ -6,7 +6,7 @@ import { useActionState, useEffect, useState } from 'react'
|
|
|
6
6
|
import type { UploadLimits } from '@meith/attachments/limits'
|
|
7
7
|
import type { Draft } from '@meith/drafts'
|
|
8
8
|
import { POLL_OPTION_MAX } from '@meith/polls/limits'
|
|
9
|
-
import { buttonVariants, Disclosure } from '@meith/ui'
|
|
9
|
+
import { buttonVariants, controlVariants, Disclosure } from '@meith/ui'
|
|
10
10
|
|
|
11
11
|
import type { PollDraftValues } from '@/server/auth-form-state'
|
|
12
12
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
@@ -128,7 +128,7 @@ export function NewThreadForm({
|
|
|
128
128
|
<select
|
|
129
129
|
name="prefixId"
|
|
130
130
|
defaultValue={state.values?.prefixId ?? draft?.prefixId?.toString() ?? ''}
|
|
131
|
-
className=
|
|
131
|
+
className={controlVariants()}
|
|
132
132
|
>
|
|
133
133
|
<option value="">
|
|
134
134
|
{requiresPrefix
|
|
@@ -183,7 +183,7 @@ export function NewThreadForm({
|
|
|
183
183
|
min={0}
|
|
184
184
|
step={1}
|
|
185
185
|
defaultValue={pollDraft?.maxOptions ?? '1'}
|
|
186
|
-
className=
|
|
186
|
+
className={controlVariants()}
|
|
187
187
|
/>
|
|
188
188
|
<span className="text-xs text-muted-foreground">
|
|
189
189
|
{fromCopy(copy, 'composer.newThread.pollChoicesHint')}
|
|
@@ -215,7 +215,7 @@ export function NewThreadForm({
|
|
|
215
215
|
type="datetime-local"
|
|
216
216
|
name="pollClosesAt"
|
|
217
217
|
defaultValue={pollDraft?.closesAt ?? ''}
|
|
218
|
-
className=
|
|
218
|
+
className={controlVariants()}
|
|
219
219
|
/>
|
|
220
220
|
<span className="text-xs text-muted-foreground">
|
|
221
221
|
{fromCopy(copy, 'pollEdit.closesAtHint')}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants, controlVariants, surfaceVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
6
8
|
import { messageBulkAction, sendMessageAction } from '@/server/message-actions'
|
|
7
9
|
|
|
@@ -11,16 +13,13 @@ import { ConfirmDialog } from '../shell/confirm-dialog'
|
|
|
11
13
|
import { type Copy, fromCopy } from '../shell/copy'
|
|
12
14
|
import { RecipientField } from './recipient-field'
|
|
13
15
|
|
|
14
|
-
const FIELD =
|
|
15
|
-
'w-full rounded-md border border-input bg-card px-3 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
16
|
+
const FIELD = controlVariants()
|
|
16
17
|
|
|
17
|
-
const BUTTON =
|
|
18
|
-
'inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
18
|
+
const BUTTON = buttonVariants({ variant: 'primary', size: 'default' })
|
|
19
19
|
|
|
20
|
-
const SECONDARY =
|
|
21
|
-
'inline-flex h-8 items-center justify-center rounded-md border border-border px-3 text-xs font-medium hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
20
|
+
const SECONDARY = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
22
21
|
|
|
23
|
-
const CARD =
|
|
22
|
+
const CARD = surfaceVariants({ padded: true })
|
|
24
23
|
|
|
25
24
|
export function ComposeForm({
|
|
26
25
|
to,
|
|
@@ -44,7 +43,7 @@ export function ComposeForm({
|
|
|
44
43
|
<FormError message={state.error} />
|
|
45
44
|
{replyToId === null ? null : <input type="hidden" name="replyTo" value={replyToId} />}
|
|
46
45
|
|
|
47
|
-
<label htmlFor="message-to" className="flex flex-col gap-
|
|
46
|
+
<label htmlFor="message-to" className="flex min-w-0 flex-col gap-2 text-sm">
|
|
48
47
|
<span className="font-medium">{fromCopy(copy, 'messageForm.to')}</span>
|
|
49
48
|
<RecipientField
|
|
50
49
|
id="message-to"
|
|
@@ -58,7 +57,7 @@ export function ComposeForm({
|
|
|
58
57
|
</span>
|
|
59
58
|
</label>
|
|
60
59
|
|
|
61
|
-
<label htmlFor="message-bcc" className="flex flex-col gap-
|
|
60
|
+
<label htmlFor="message-bcc" className="flex min-w-0 flex-col gap-2 text-sm">
|
|
62
61
|
<span className="font-medium">{fromCopy(copy, 'messageForm.bcc')}</span>
|
|
63
62
|
<RecipientField
|
|
64
63
|
id="message-bcc"
|
|
@@ -71,7 +70,7 @@ export function ComposeForm({
|
|
|
71
70
|
</span>
|
|
72
71
|
</label>
|
|
73
72
|
|
|
74
|
-
<label className="flex flex-col gap-
|
|
73
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
75
74
|
<span className="font-medium">{fromCopy(copy, 'messageForm.subject')}</span>
|
|
76
75
|
<input
|
|
77
76
|
name="subject"
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { BOARD_MEASURE } from '@/components/shell/measure'
|
|
6
8
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
7
9
|
import { inlineModerateAction } from '@/server/inline-moderation-actions'
|
|
@@ -11,8 +13,7 @@ import { FormError, PendingButton } from '../auth/form-controls'
|
|
|
11
13
|
import { ConfirmDialog } from '../shell/confirm-dialog'
|
|
12
14
|
import { type Copy, fromCopy } from '../shell/copy'
|
|
13
15
|
|
|
14
|
-
const BUTTON =
|
|
15
|
-
'inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
16
|
+
const BUTTON = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
16
17
|
|
|
17
18
|
export interface InlineMoveOption {
|
|
18
19
|
readonly id: number
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
-
import { TextLink } from '@meith/ui'
|
|
5
|
+
import { buttonVariants, TextLink } from '@meith/ui'
|
|
6
6
|
|
|
7
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
8
8
|
import { moderateQueueAction } from '@/server/moderation-actions'
|
|
@@ -74,14 +74,14 @@ export function QueueForm({ rows, copy }: { rows: readonly QueueFormRow[]; copy:
|
|
|
74
74
|
<PendingButton
|
|
75
75
|
name="decision"
|
|
76
76
|
value="approve"
|
|
77
|
-
className=
|
|
77
|
+
className={buttonVariants({ variant: 'primary', size: 'default' })}
|
|
78
78
|
>
|
|
79
79
|
{fromCopy(copy, 'moderationForm.queue.approve')}
|
|
80
80
|
</PendingButton>
|
|
81
81
|
<PendingButton
|
|
82
82
|
name="decision"
|
|
83
83
|
value="reject"
|
|
84
|
-
className=
|
|
84
|
+
className={buttonVariants({ variant: 'destructive', size: 'default' })}
|
|
85
85
|
>
|
|
86
86
|
{fromCopy(copy, 'moderationForm.queue.reject')}
|
|
87
87
|
</PendingButton>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
-
import { Card, CardContent, Field, Textarea, textLinkVariants } from '@meith/ui'
|
|
5
|
+
import { buttonVariants, Card, CardContent, Field, Textarea, textLinkVariants } from '@meith/ui'
|
|
6
6
|
|
|
7
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
8
8
|
import { assignReportAction, closeReportAction, fileReportAction } from '@/server/report-actions'
|
|
@@ -120,14 +120,14 @@ export function CloseReportForm({ reportId, copy }: { reportId: number; copy: Co
|
|
|
120
120
|
<PendingButton
|
|
121
121
|
name="status"
|
|
122
122
|
value="resolved"
|
|
123
|
-
className=
|
|
123
|
+
className={buttonVariants({ variant: 'primary', size: 'sm' })}
|
|
124
124
|
>
|
|
125
125
|
{fromCopy(copy, 'moderationForm.report.resolve')}
|
|
126
126
|
</PendingButton>
|
|
127
127
|
<PendingButton
|
|
128
128
|
name="status"
|
|
129
129
|
value="rejected"
|
|
130
|
-
className=
|
|
130
|
+
className={buttonVariants({ variant: 'outline', size: 'sm' })}
|
|
131
131
|
>
|
|
132
132
|
{fromCopy(copy, 'moderationForm.report.dismiss')}
|
|
133
133
|
</PendingButton>
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants, controlVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
6
8
|
import { mergeThreadAction, splitThreadAction } from '@/server/surgery-actions'
|
|
7
9
|
|
|
8
10
|
import { FormError, PendingButton } from '../auth/form-controls'
|
|
9
11
|
import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
|
|
10
12
|
|
|
11
|
-
const BUTTON =
|
|
12
|
-
|
|
13
|
-
const FIELD =
|
|
14
|
-
'h-8 min-w-0 max-w-full rounded-md border border-border bg-background px-2 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
13
|
+
const BUTTON = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
14
|
+
const FIELD = controlVariants({ size: 'sm', className: 'w-auto max-w-full' })
|
|
15
15
|
const LABEL = 'flex min-w-0 max-w-full items-center text-xs'
|
|
16
16
|
|
|
17
17
|
export interface SplitPoint {
|