summaryrefslogtreecommitdiffhomepage
path: root/packages/web/src/components/share/common.tsx
blob: ad50e425fa4d0cb8cf86e8098f5bb40630a1f764 (plain)
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { createContext, createSignal, splitProps, useContext } from "solid-js"
import type { JSX } from "solid-js/jsx-runtime"
import { makeResizeObserver } from "@solid-primitives/resize-observer"
import { IconCheckCircle, IconHashtag } from "../icons"

export type ShareMessages = { locale: string } & Record<string, string>

const shareContext = createContext<ShareMessages>()

export function ShareI18nProvider(props: { messages: ShareMessages; children: JSX.Element }) {
  return <shareContext.Provider value={props.messages}>{props.children}</shareContext.Provider>
}

export function useShareMessages() {
  const value = useContext(shareContext)
  if (value) {
    return value
  }
  throw new Error("ShareI18nProvider is required")
}

export function normalizeLocale(locale: string) {
  return locale === "root" ? "en" : locale
}

export function formatNumber(value: number, locale: string) {
  return new Intl.NumberFormat(normalizeLocale(locale)).format(value)
}

export function formatCurrency(value: number, locale: string) {
  return new Intl.NumberFormat(normalizeLocale(locale), {
    style: "currency",
    currency: "USD",
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(value)
}

export function formatCount(value: number, locale: string, singular: string, plural: string) {
  const unit = value === 1 ? singular : plural
  return `${formatNumber(value, locale)} ${unit}`
}

interface AnchorProps extends JSX.HTMLAttributes<HTMLDivElement> {
  id: string
}
export function AnchorIcon(props: AnchorProps) {
  const [local, rest] = splitProps(props, ["id", "children"])
  const [copied, setCopied] = createSignal(false)
  const messages = useShareMessages()

  return (
    <div {...rest} data-element-anchor title={messages.link_to_message} data-status={copied() ? "copied" : ""}>
      <a
        href={`#${local.id}`}
        onClick={(e) => {
          e.preventDefault()

          const anchor = e.currentTarget
          const hash = anchor.getAttribute("href") || ""
          const { origin, pathname, search } = window.location

          navigator.clipboard
            .writeText(`${origin}${pathname}${search}${hash}`)
            .catch((err) => console.error("Copy failed", err))

          setCopied(true)
          setTimeout(() => setCopied(false), 3000)
        }}
      >
        {local.children}
        <IconHashtag width={18} height={18} />
        <IconCheckCircle width={18} height={18} />
      </a>
      <span data-element-tooltip>{messages.copied}</span>
    </div>
  )
}

export function createOverflow() {
  const [overflow, setOverflow] = createSignal(false)
  return {
    get status() {
      return overflow()
    },
    ref(el: HTMLElement) {
      const sync = () => {
        setOverflow(el.scrollHeight > el.clientHeight + 1)
      }

      const obs = makeResizeObserver(sync)
      obs.observe(el)

      sync()
    },
  }
}

export function formatDuration(ms: number, locale: string): string {
  const normalized = normalizeLocale(locale)
  const ONE_SECOND = 1000
  const ONE_MINUTE = 60 * ONE_SECOND

  if (ms >= ONE_MINUTE) {
    return new Intl.NumberFormat(normalized, {
      style: "unit",
      unit: "minute",
      unitDisplay: "narrow",
      maximumFractionDigits: 0,
    }).format(Math.floor(ms / ONE_MINUTE))
  }

  if (ms >= ONE_SECOND) {
    return new Intl.NumberFormat(normalized, {
      style: "unit",
      unit: "second",
      unitDisplay: "narrow",
      maximumFractionDigits: 0,
    }).format(Math.floor(ms / ONE_SECOND))
  }

  return new Intl.NumberFormat(normalized, {
    style: "unit",
    unit: "millisecond",
    unitDisplay: "narrow",
    maximumFractionDigits: 0,
  }).format(ms)
}