129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
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)
|
|
}
|