@meith/theme-phasebook 0.15.0 → 0.17.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/theme-phasebook",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "A theme for Meith with a familiar social shape, built on the default theme's slots.",
5
5
  "license": "LGPL-3.0-or-later",
6
6
  "repository": {
@@ -20,9 +20,9 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@meith/theme-default": "^0.15.0",
24
- "@meith/theme-kit": "^0.15.0",
25
- "@meith/ui": "^0.15.0"
23
+ "@meith/theme-default": "^0.17.0",
24
+ "@meith/theme-kit": "^0.17.0",
25
+ "@meith/ui": "^0.17.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^19.2.0"
package/src/copy.ts CHANGED
@@ -141,6 +141,7 @@ export function postActionsCopy(t: Translator): SlotCopy {
141
141
  return copyFor(t, [
142
142
  'phasebook.postActions.ariaLabel',
143
143
  'phasebook.postActions.edit',
144
+ 'phasebook.postActions.history',
144
145
  'phasebook.postActions.moderate',
145
146
  'phasebook.postActions.quote',
146
147
  'phasebook.postActions.rate',
@@ -66,6 +66,7 @@
66
66
  "phasebook.panelNav.waiting": "waiting",
67
67
  "phasebook.postActions.ariaLabel": "Post actions",
68
68
  "phasebook.postActions.edit": "Edit",
69
+ "phasebook.postActions.history": "History",
69
70
  "phasebook.postActions.moderate": "Moderate",
70
71
  "phasebook.postActions.quote": "Quote",
71
72
  "phasebook.postActions.rate": "Rate",
@@ -0,0 +1,72 @@
1
+ 'use client'
2
+
3
+ import {
4
+ applyEditorTag,
5
+ type EditorTag,
6
+ type EditorToolbarModel,
7
+ type SlotCopy,
8
+ } from '@meith/theme-kit'
9
+
10
+ const GLYPHS: Readonly<Record<EditorTag, string>> = {
11
+ bold: 'B',
12
+ italic: 'I',
13
+ strikethrough: 'S',
14
+ link: 'Link',
15
+ quote: '“”',
16
+ code: '</>',
17
+ spoiler: 'Spoiler',
18
+ bulletedList: '•',
19
+ numberedList: '1.',
20
+ heading: 'H',
21
+ }
22
+
23
+ const BUTTON =
24
+ 'inline-flex h-8 min-w-8 items-center justify-center rounded-full px-2.5 text-xs font-semibold text-muted-foreground transition-colors duration-100 hover:bg-accent hover:text-foreground'
25
+
26
+ function runTag(textareaId: string, tag: EditorTag, placeholder: string | null): void {
27
+ const field = document.getElementById(textareaId)
28
+ if (field instanceof HTMLTextAreaElement) applyEditorTag(field, tag, placeholder)
29
+ }
30
+
31
+ export function EditorToolbar({
32
+ textareaId,
33
+ groupLabel,
34
+ buttons,
35
+ attachment,
36
+ }: EditorToolbarModel & { copy: SlotCopy }) {
37
+ return (
38
+ <div
39
+ role="group"
40
+ aria-label={groupLabel}
41
+ className="flex flex-wrap gap-1 border-b border-border bg-secondary/40 px-3 py-2"
42
+ >
43
+ {buttons.map((button) => (
44
+ <button
45
+ key={button.tag}
46
+ type="button"
47
+ onMouseDown={(event) => event.preventDefault()}
48
+ onClick={() => runTag(textareaId, button.tag, button.placeholder)}
49
+ title={button.title}
50
+ aria-label={button.label}
51
+ {...(button.keyShortcut === null ? {} : { 'aria-keyshortcuts': button.keyShortcut })}
52
+ className={BUTTON}
53
+ >
54
+ <span aria-hidden="true">{GLYPHS[button.tag]}</span>
55
+ </button>
56
+ ))}
57
+
58
+ {attachment !== null && (
59
+ <button
60
+ type="button"
61
+ onMouseDown={(event) => event.preventDefault()}
62
+ onClick={() => document.getElementById(attachment.inputId)?.click()}
63
+ title={attachment.label}
64
+ aria-label={attachment.label}
65
+ className={BUTTON}
66
+ >
67
+ <span aria-hidden="true">{attachment.label}</span>
68
+ </button>
69
+ )}
70
+ </div>
71
+ )
72
+ }
@@ -20,6 +20,7 @@ export function PostActions({
20
20
  const reader: Action[] = [
21
21
  actions.quoteHref === null ? null : { href: actions.quoteHref, label: c('quote') },
22
22
  actions.editHref === null ? null : { href: actions.editHref, label: c('edit') },
23
+ actions.historyHref == null ? null : { href: actions.historyHref, label: c('history') },
23
24
  actions.rateHref === null ? null : { href: actions.rateHref, label: c('rate') },
24
25
  actions.reportHref === null ? null : { href: actions.reportHref, label: c('report') },
25
26
  ].filter((action): action is Action => action !== null)
@@ -0,0 +1,9 @@
1
+ 'use client'
2
+
3
+ import type { QuickReplyModel, SlotCopy } from '@meith/theme-kit'
4
+
5
+ export function QuickReply({ placeholder, children }: QuickReplyModel & { copy: SlotCopy }) {
6
+ if (children === undefined) return null
7
+
8
+ return <section aria-label={placeholder}>{children}</section>
9
+ }
package/src/theme.ts CHANGED
@@ -37,6 +37,7 @@ import { BoardIndex } from './slots/board-index'
37
37
  import { BoardStats } from './slots/board-stats'
38
38
  import { CategoryBlock } from './slots/category-block'
39
39
  import { DiscoveryView } from './slots/discovery-view'
40
+ import { EditorToolbar } from './slots/editor-toolbar'
40
41
  import { ErrorNotice } from './slots/error-notice'
41
42
  import { Footer } from './slots/footer'
42
43
  import { ForumDisplay } from './slots/forum-display'
@@ -55,6 +56,7 @@ import { PanelShell } from './slots/panel-shell'
55
56
  import { PostActions } from './slots/post-actions'
56
57
  import { PostBit } from './slots/post-bit'
57
58
  import { PostForm } from './slots/post-form'
59
+ import { QuickReply } from './slots/quick-reply'
58
60
  import { RedirectNotice } from './slots/redirect-notice'
59
61
  import { SearchForm } from './slots/search-form'
60
62
  import { SearchResults } from './slots/search-results'
@@ -94,8 +96,10 @@ export const phasebookTheme = defineTheme({
94
96
  ThreadView,
95
97
  PostBit,
96
98
  PostActions,
99
+ QuickReply,
97
100
 
98
101
  PostForm,
102
+ EditorToolbar,
99
103
 
100
104
  MemberProfile,
101
105