All files / submodels/copy view.ts

100% Statements 14/14
100% Branches 0/0
100% Functions 9/9
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94                                    3x 8x 4x 4x     3x         715x 711x   4x                           3x   715x 715x 715x                   715x                                               711x                  
import clsx from 'clsx'
import { HashMap, Option } from 'effect'
import { Submodel } from 'foldkit'
import type { Html, HtmlBuilder } from 'foldkit/html'
 
import { Button } from '@foldkit/ui'
 
import { Message } from './message'
import { Feedback, type Model } from './model'
 
export type ViewInputs = Readonly<{
  cellId: string
  text: string
  ariaLabel: string
  lines: readonly [string, string]
  toneClass: string
}>
 
const feedbackText = (feedback: Feedback): string =>
  Feedback.match(feedback, {
    Copied: () => 'Copied',
    Failed: () => 'Copy failed',
  })
 
const secondLineView = (
  maybeFeedback: Option.Option<Feedback>,
  detail: string,
  h: HtmlBuilder<Message>,
): Html =>
  Option.match(maybeFeedback, {
    onNone: () => h.span([h.Class('block opacity-80')], [detail]),
    onSome: feedback =>
      h.span(
        [
          h.Class(
            clsx('block', {
              'text-ok': Feedback.guards.Copied(feedback),
              'text-danger': Feedback.guards.Failed(feedback),
            }),
          ),
        ],
        [feedbackText(feedback)],
      ),
  })
 
/** One readout cell: shows a reading, copies it on click, and announces the result. */
export const view = Submodel.defineView<Model, Message, ViewInputs>(
  (model, viewInputs, h) => {
    const maybeFeedback = HashMap.get(model.feedbackByCellId, viewInputs.cellId)
    const [reading, detail] = viewInputs.lines
    return h.span(
      [h.Class('block')],
      [
        Button.view(
          {
            onClick: Message.ClickedCopy({
              cellId: viewInputs.cellId,
              text: viewInputs.text,
            }),
            toView: attributes =>
              h.button(
                [
                  ...attributes.button,
                  h.AriaLabel(viewInputs.ariaLabel),
                  h.Title(`Copy ${viewInputs.text}`),
                  h.Class(
                    clsx(
                      'block w-full whitespace-nowrap px-3 py-1 text-left leading-5 outline-hidden hover:bg-ink/5 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus',
                      viewInputs.toneClass,
                    ),
                  ),
                ],
                [
                  h.span([h.Class('block')], [reading]),
                  secondLineView(maybeFeedback, detail, h),
                ],
              ),
          },
          h,
        ),
        h.span(
          [h.Role('status'), h.AriaLive('polite'), h.Class('sr-only')],
          [
            Option.match(maybeFeedback, {
              onNone: () => '',
              onSome: feedbackText,
            }),
          ],
        ),
      ],
    )
  },
)