@meith/theme-phasebook 0.7.0 → 0.8.0

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.
@@ -1,8 +1,29 @@
1
- import type { ThreadViewModel } from '@meith/theme-kit'
1
+ import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
2
3
 
3
- import { FEED, MUTED_LINK, NUMERIC, PAGE, PILL, PILL_PRIMARY, Prefix, Tag, count, plural } from '../shared'
4
+ import {
5
+ count,
6
+ FEED,
7
+ MUTED_LINK,
8
+ NUMERIC,
9
+ PAGE,
10
+ PILL,
11
+ PILL_PRIMARY,
12
+ Prefix,
13
+ plural,
14
+ Tag,
15
+ } from '../shared'
16
+
17
+ export function ThreadView({
18
+ thread,
19
+ forum,
20
+ replyHref,
21
+ markReadAction,
22
+ regions,
23
+ copy,
24
+ }: ThreadViewModel & { copy: SlotCopy }) {
25
+ const c = (key: string) => fromSlotCopy(copy, `phasebook.threadView.${key}`)
4
26
 
5
- export function ThreadView({ thread, forum, replyHref, markReadAction, regions }: ThreadViewModel) {
6
27
  return (
7
28
  <div className={`${PAGE} py-4 sm:py-6`}>
8
29
  <div className={`${FEED} flex flex-col gap-4`}>
@@ -19,13 +40,15 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
19
40
 
20
41
  <div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1">
21
42
  {thread.prefix !== null && <Prefix prefix={thread.prefix} />}
22
- {thread.isSticky && <Tag token="thread-pinned">Pinned</Tag>}
23
- {thread.isLocked && <Tag token="thread-locked">Locked — no new replies</Tag>}
24
- {thread.isMoved && <Tag token="thread-moved">Moved</Tag>}
43
+ {thread.isSticky && <Tag token="thread-pinned">{c('pinned')}</Tag>}
44
+ {thread.isLocked && <Tag token="thread-locked">{c('locked')}</Tag>}
45
+ {thread.isMoved && <Tag token="thread-moved">{c('moved')}</Tag>}
25
46
 
26
47
  <span className={`text-xs text-muted-foreground ${NUMERIC}`}>
27
- {count(thread.replyCount)} {plural(thread.replyCount, 'reply', 'replies')} ·{' '}
28
- {count(thread.viewCount)} {plural(thread.viewCount, 'view', 'views')}
48
+ {count(thread.replyCount)}{' '}
49
+ {plural(thread.replyCount, c('reply.one'), c('reply.other'))} ·{' '}
50
+ {count(thread.viewCount)}{' '}
51
+ {plural(thread.viewCount, c('view.one'), c('view.other'))}
29
52
  </span>
30
53
  </div>
31
54
  </div>
@@ -34,13 +57,13 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
34
57
  {markReadAction !== null && (
35
58
  <form action={markReadAction} method="post">
36
59
  <button type="submit" className={PILL}>
37
- Mark read
60
+ {c('markRead')}
38
61
  </button>
39
62
  </form>
40
63
  )}
41
64
  {replyHref !== null && (
42
65
  <a href={replyHref} className={PILL_PRIMARY}>
43
- Reply
66
+ {c('replyAction')}
44
67
  </a>
45
68
  )}
46
69
  </div>
@@ -1,7 +1,8 @@
1
+ import type { CountModel, SlotCopy, UserPanelModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Menu } from '@meith/ui/menu'
2
- import type { UserPanelModel } from '@meith/theme-kit'
3
4
 
4
- import { Circle, ICON_BUTTON, MUTED_LINK, PILL, PILL_PRIMARY, count } from '../shared'
5
+ import { Circle, count, ICON_BUTTON, MUTED_LINK, PILL, PILL_PRIMARY } from '../shared'
5
6
 
6
7
  function BellIcon() {
7
8
  return (
@@ -63,10 +64,10 @@ function IconButton({
63
64
  )
64
65
  }
65
66
 
66
- function CountBadge({ value, label }: { value: number; label: string }) {
67
+ function CountBadge({ value, label }: { value: CountModel; label: string }) {
67
68
  return (
68
69
  <span className="absolute -top-0.5 -right-0.5 inline-flex min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-xs font-semibold text-destructive-foreground ring-2 ring-card">
69
- <span aria-hidden="true">{value > 99 ? '99+' : value}</span>
70
+ <span aria-hidden="true">{value.value > 99 ? '99+' : value.label}</span>
70
71
  <span className="sr-only">
71
72
  {count(value)} {label}
72
73
  </span>
@@ -82,7 +83,10 @@ export function UserPanel({
82
83
  notificationsHref,
83
84
  messagesHref,
84
85
  children,
85
- }: UserPanelModel) {
86
+ copy,
87
+ }: UserPanelModel & { copy: SlotCopy }) {
88
+ const c = (key: string) => fromSlotCopy(copy, `phasebook.userPanel.${key}`)
89
+
86
90
  if (viewer.isGuest) {
87
91
  return (
88
92
  <div className="flex items-center gap-2">
@@ -95,7 +99,7 @@ export function UserPanel({
95
99
  )
96
100
  }
97
101
 
98
- const name = viewer.username ?? 'Signed in'
102
+ const name = viewer.username ?? c('signedIn')
99
103
 
100
104
  return (
101
105
  <div className="flex items-center gap-2">
@@ -106,23 +110,23 @@ export function UserPanel({
106
110
  }}
107
111
  />
108
112
 
109
- {unreadNotifications > 0 && (
110
- <IconButton href={notificationsHref ?? null} title="Notifications">
113
+ {unreadNotifications.value > 0 && (
114
+ <IconButton href={notificationsHref ?? null} title={c('notifications')}>
111
115
  <BellIcon />
112
- <CountBadge value={unreadNotifications} label="unread notifications" />
116
+ <CountBadge value={unreadNotifications} label={c('unreadNotifications')} />
113
117
  </IconButton>
114
118
  )}
115
119
 
116
- {unreadMessages > 0 && (
117
- <IconButton href={messagesHref ?? null} title="Messages">
120
+ {unreadMessages.value > 0 && (
121
+ <IconButton href={messagesHref ?? null} title={c('messages')}>
118
122
  <MessageIcon />
119
- <CountBadge value={unreadMessages} label="unread messages" />
123
+ <CountBadge value={unreadMessages} label={c('unreadMessages')} />
120
124
  </IconButton>
121
125
  )}
122
126
 
123
127
  <div data-account="menu" className="flex min-w-0 items-center">
124
128
  <Menu
125
- label="Your account"
129
+ label={c('accountAriaLabel')}
126
130
  items={links}
127
131
  trigger={
128
132
  <>
@@ -138,7 +142,7 @@ export function UserPanel({
138
142
  <nav
139
143
  data-account="plain"
140
144
  style={{ display: 'none' }}
141
- aria-label="Your account"
145
+ aria-label={c('accountAriaLabel')}
142
146
  className="flex-wrap items-center gap-x-3 gap-y-1 text-xs"
143
147
  >
144
148
  <span className="font-semibold text-foreground">{name}</span>
@@ -1,10 +1,24 @@
1
- import type { OnlineMemberModel, WhoIsOnlineModel } from '@meith/theme-kit'
1
+ import type { OnlineMemberModel, SlotCopy, WhoIsOnlineModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
2
3
 
3
- import { Circle, MUTED_LINK, NUMERIC, OnlineDot, Rail, Stamp, Tag, UserRef, count, plural } from '../shared'
4
+ import {
5
+ Circle,
6
+ count,
7
+ MUTED_LINK,
8
+ NUMERIC,
9
+ OnlineDot,
10
+ plural,
11
+ Rail,
12
+ Stamp,
13
+ Tag,
14
+ UserRef,
15
+ } from '../shared'
4
16
 
5
17
  const VISIBLE_NAMES = 8
6
18
 
7
- function Row({ member }: { member: OnlineMemberModel }) {
19
+ function Row({ member, copy }: { member: OnlineMemberModel; copy: SlotCopy }) {
20
+ const c = (key: string) => fromSlotCopy(copy, `phasebook.whoIsOnline.${key}`)
21
+
8
22
  return (
9
23
  <li className="flex items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-accent">
10
24
  <span className="relative shrink-0">
@@ -25,7 +39,7 @@ function Row({ member }: { member: OnlineMemberModel }) {
25
39
  )}
26
40
  </span>
27
41
 
28
- {member.isInvisible && <Tag>Invisible</Tag>}
42
+ {member.isInvisible && <Tag>{c('invisible')}</Tag>}
29
43
  </li>
30
44
  )
31
45
  }
@@ -33,44 +47,47 @@ function Row({ member }: { member: OnlineMemberModel }) {
33
47
  export function WhoIsOnline({
34
48
  guestCount,
35
49
  members,
50
+ memberCount,
36
51
  total,
37
52
  recordCount,
38
53
  recordAt,
39
54
  fullListHref,
40
- }: WhoIsOnlineModel) {
55
+ copy,
56
+ }: WhoIsOnlineModel & { copy: SlotCopy }) {
41
57
  const shown = members.slice(0, VISIBLE_NAMES)
42
58
  const hidden = members.length - shown.length
43
59
 
60
+ const c = (key: string) => fromSlotCopy(copy, `phasebook.whoIsOnline.${key}`)
61
+
44
62
  return (
45
63
  <Rail
46
- title="Online now"
64
+ title={c('title')}
47
65
  titleId="who-is-online-heading"
48
66
  action={
49
67
  <a href={fullListHref} className={`text-xs font-semibold ${MUTED_LINK}`}>
50
- See all
68
+ {c('seeAll')}
51
69
  </a>
52
70
  }
53
71
  >
54
72
  <p className={`px-3 text-xs text-muted-foreground ${NUMERIC}`}>
55
- {count(total)} online {count(members.length)} {plural(members.length, 'member', 'members')}
56
- , {count(guestCount)} {plural(guestCount, 'guest', 'guests')}
73
+ {count(total)} {c('online')} {count(memberCount)}{' '}
74
+ {plural(memberCount, c('member.one'), c('member.other'))}, {count(guestCount)}{' '}
75
+ {plural(guestCount, c('guest.one'), c('guest.other'))}
57
76
  </p>
58
77
 
59
- {members.length === 0 ? (
78
+ {memberCount.value === 0 ? (
60
79
  <p className="px-3 pt-2 pb-3 text-xs text-muted-foreground">
61
- {guestCount === 0
62
- ? 'Nobody is reading the board right now.'
63
- : 'Only guests are reading the board right now.'}
80
+ {guestCount.value === 0 ? c('nobody') : c('onlyGuests')}
64
81
  </p>
65
82
  ) : (
66
83
  <ul className="px-1 pt-1 pb-1">
67
84
  {shown.map((member) => (
68
- <Row key={member.userId ?? member.username} member={member} />
85
+ <Row key={member.userId ?? member.username} member={member} copy={copy} />
69
86
  ))}
70
87
  {hidden > 0 && (
71
88
  <li className="px-2 py-1.5">
72
89
  <a href={fullListHref} className={`text-xs font-semibold ${MUTED_LINK}`}>
73
- and {count(hidden)} more
90
+ {c('and')} {hidden} {c('more')}
74
91
  </a>
75
92
  </li>
76
93
  )}
@@ -79,7 +96,7 @@ export function WhoIsOnline({
79
96
 
80
97
  {recordAt !== null && (
81
98
  <p className={`border-t border-border px-3 py-2 text-xs text-muted-foreground ${NUMERIC}`}>
82
- Record: {count(recordCount)} on <Stamp at={recordAt} />
99
+ {c('record')} {count(recordCount)} {c('on')} <Stamp at={recordAt} />
83
100
  </p>
84
101
  )}
85
102
  </Rail>
package/src/theme.ts CHANGED
@@ -1,6 +1,36 @@
1
1
  import { defaultTheme } from '@meith/theme-default'
2
2
  import { defineTheme } from '@meith/theme-kit'
3
3
 
4
+ import {
5
+ announcementCopy,
6
+ boardIndexCopy,
7
+ boardStatsCopy,
8
+ discoveryViewCopy,
9
+ errorNoticeCopy,
10
+ footerCopy,
11
+ forumDisplayCopy,
12
+ forumRowCopy,
13
+ headerCopy,
14
+ latestPostsCopy,
15
+ latestThreadsCopy,
16
+ memberProfileCopy,
17
+ navigationCopy,
18
+ noticeCopy,
19
+ paginationCopy,
20
+ panelNavCopy,
21
+ postActionsCopy,
22
+ postBitCopy,
23
+ postFormCopy,
24
+ redirectNoticeCopy,
25
+ searchFormCopy,
26
+ searchResultsCopy,
27
+ shellCopy,
28
+ subforumListCopy,
29
+ threadRowCopy,
30
+ threadViewCopy,
31
+ userPanelCopy,
32
+ whoIsOnlineCopy,
33
+ } from './copy'
4
34
  import { Announcement } from './slots/announcement'
5
35
  import { AuthPage } from './slots/auth-page'
6
36
  import { BoardIndex } from './slots/board-index'
@@ -9,8 +39,8 @@ import { CategoryBlock } from './slots/category-block'
9
39
  import { DiscoveryView } from './slots/discovery-view'
10
40
  import { ErrorNotice } from './slots/error-notice'
11
41
  import { Footer } from './slots/footer'
12
- import { ForumJump } from './slots/forum-jump'
13
42
  import { ForumDisplay } from './slots/forum-display'
43
+ import { ForumJump } from './slots/forum-jump'
14
44
  import { ForumRow } from './slots/forum-row'
15
45
  import { Header } from './slots/header'
16
46
  import { LatestPosts } from './slots/latest-posts'
@@ -86,4 +116,34 @@ export const phasebookTheme = defineTheme({
86
116
  RedirectNotice,
87
117
  ErrorNotice,
88
118
  },
119
+ copy: {
120
+ Announcement: announcementCopy,
121
+ BoardIndex: boardIndexCopy,
122
+ BoardStats: boardStatsCopy,
123
+ DiscoveryView: discoveryViewCopy,
124
+ ErrorNotice: errorNoticeCopy,
125
+ Footer: footerCopy,
126
+ ForumDisplay: forumDisplayCopy,
127
+ ForumRow: forumRowCopy,
128
+ Header: headerCopy,
129
+ LatestPosts: latestPostsCopy,
130
+ LatestThreads: latestThreadsCopy,
131
+ MemberProfile: memberProfileCopy,
132
+ Navigation: navigationCopy,
133
+ Notice: noticeCopy,
134
+ Pagination: paginationCopy,
135
+ PanelNav: panelNavCopy,
136
+ PostActions: postActionsCopy,
137
+ PostBit: postBitCopy,
138
+ PostForm: postFormCopy,
139
+ RedirectNotice: redirectNoticeCopy,
140
+ SearchForm: searchFormCopy,
141
+ SearchResults: searchResultsCopy,
142
+ Shell: shellCopy,
143
+ SubforumList: subforumListCopy,
144
+ ThreadRow: threadRowCopy,
145
+ ThreadView: threadViewCopy,
146
+ UserPanel: userPanelCopy,
147
+ WhoIsOnline: whoIsOnlineCopy,
148
+ },
89
149
  })