@openrails/contentkit-ui 0.79.0 → 0.81.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/README.md +82 -54
- package/dist/client/beacons.d.ts +53 -0
- package/dist/client/beacons.js +72 -0
- package/dist/client/beacons.js.map +1 -0
- package/dist/client/catalog.d.ts +2 -2
- package/dist/client/catalog.js +1 -1
- package/dist/client/catalog.js.map +1 -1
- package/dist/client/client.d.ts +24 -11
- package/dist/client/client.js +37 -29
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/comments.d.ts +12 -8
- package/dist/client/content/comments.js +33 -20
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.d.ts +24 -9
- package/dist/client/content/favorites.js +21 -22
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.d.ts +1 -1
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/reactions.d.ts +11 -6
- package/dist/client/content/reactions.js +23 -18
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/content/types.d.ts +40 -21
- package/dist/client/content/types.js +5 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +55 -0
- package/dist/client/contents.js +144 -0
- package/dist/client/contents.js.map +1 -0
- package/dist/client/discovery.d.ts +126 -0
- package/dist/client/discovery.js +125 -0
- package/dist/client/discovery.js.map +1 -0
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +12 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +1244 -370
- package/dist/client/generated/routes.js +1870 -554
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +960 -134
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/group-fields.d.ts +25 -0
- package/dist/client/group-fields.js +63 -0
- package/dist/client/group-fields.js.map +1 -0
- package/dist/client/groups.d.ts +58 -0
- package/dist/client/groups.js +119 -0
- package/dist/client/groups.js.map +1 -0
- package/dist/client/http.d.ts +24 -12
- package/dist/client/http.js +20 -24
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +11 -6
- package/dist/client/media/api.d.ts +1 -1
- package/dist/client/media/api.js +4 -2
- package/dist/client/media/api.js.map +1 -1
- package/dist/client/media/client.d.ts +8 -9
- package/dist/client/media/client.js +58 -71
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/ref.d.ts +30 -0
- package/dist/client/media/ref.js +10 -3
- package/dist/client/media/ref.js.map +1 -1
- package/dist/client/offers.d.ts +41 -0
- package/dist/client/offers.js +66 -0
- package/dist/client/offers.js.map +1 -0
- package/dist/client/route.d.ts +2 -0
- package/dist/client/route.js +2 -1
- package/dist/client/route.js.map +1 -1
- package/dist/client/versions.d.ts +20 -0
- package/dist/client/versions.js +53 -0
- package/dist/client/versions.js.map +1 -0
- package/dist/client.js +35 -30
- package/dist/components/admin/content-manager.d.ts +80 -0
- package/dist/components/admin/content-manager.js +591 -0
- package/dist/components/admin/content-manager.js.map +1 -0
- package/dist/components/admin/group-manager.d.ts +24 -0
- package/dist/components/admin/group-manager.js +244 -0
- package/dist/components/admin/group-manager.js.map +1 -0
- package/dist/components/admin/media-item-panel.js +1 -1
- package/dist/components/admin/media-item-panel.js.map +1 -1
- package/dist/components/content/card.d.ts +44 -0
- package/dist/components/content/card.js +285 -0
- package/dist/components/content/card.js.map +1 -0
- package/dist/components/content/editors.d.ts +40 -0
- package/dist/components/content/editors.js +764 -0
- package/dist/components/content/editors.js.map +1 -0
- package/dist/components/content/filters.d.ts +28 -0
- package/dist/components/content/filters.js +134 -0
- package/dist/components/content/filters.js.map +1 -0
- package/dist/components/content/grid.d.ts +62 -0
- package/dist/components/content/grid.js +233 -0
- package/dist/components/content/grid.js.map +1 -0
- package/dist/components/content/index.d.ts +8 -0
- package/dist/components/content/listing.d.ts +20 -0
- package/dist/components/content/listing.js +150 -0
- package/dist/components/content/listing.js.map +1 -0
- package/dist/components/content/offer-fields.d.ts +8 -0
- package/dist/components/content/offer-fields.js +127 -0
- package/dist/components/content/offer-fields.js.map +1 -0
- package/dist/components/content/paywall.d.ts +53 -0
- package/dist/components/content/paywall.js +221 -0
- package/dist/components/content/paywall.js.map +1 -0
- package/dist/components/content/search.d.ts +27 -0
- package/dist/components/content/search.js +164 -0
- package/dist/components/content/search.js.map +1 -0
- package/dist/components/groups/banner.d.ts +51 -0
- package/dist/components/groups/banner.js +194 -0
- package/dist/components/groups/banner.js.map +1 -0
- package/dist/components/groups/directory.d.ts +37 -0
- package/dist/components/groups/directory.js +117 -0
- package/dist/components/groups/directory.js.map +1 -0
- package/dist/components/groups/editor.d.ts +44 -0
- package/dist/components/groups/editor.js +860 -0
- package/dist/components/groups/editor.js.map +1 -0
- package/dist/components/groups/fields.d.ts +13 -0
- package/dist/components/groups/fields.js +68 -0
- package/dist/components/groups/fields.js.map +1 -0
- package/dist/components/groups/header.d.ts +25 -0
- package/dist/components/groups/header.js +131 -0
- package/dist/components/groups/header.js.map +1 -0
- package/dist/components/groups/index.d.ts +6 -0
- package/dist/components/groups/memberships.d.ts +55 -0
- package/dist/components/groups/memberships.js +527 -0
- package/dist/components/groups/memberships.js.map +1 -0
- package/dist/components/groups/parts.d.ts +49 -0
- package/dist/components/groups/parts.js +172 -0
- package/dist/components/groups/parts.js.map +1 -0
- package/dist/components/media-folder-editor.js +153 -152
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/comments.d.ts +7 -5
- package/dist/components/social/comments.js +6 -2
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +4 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/components/social/moderation.d.ts +1 -1
- package/dist/components/social/moderation.js +3 -3
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/editor/rich-text-editor.d.ts +4 -4
- package/dist/editor/rich-text-editor.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +23 -9
- package/dist/locales/admin/de.js +5 -66
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +5 -66
- package/dist/locales/admin/en.js +19 -80
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +5 -66
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +5 -66
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +5 -66
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +5 -66
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/content/de.d.ts +2 -0
- package/dist/locales/content/de.js +98 -0
- package/dist/locales/content/de.js.map +1 -0
- package/dist/locales/content/en.d.ts +93 -0
- package/dist/locales/content/en.js +98 -0
- package/dist/locales/content/en.js.map +1 -0
- package/dist/locales/content/es.d.ts +2 -0
- package/dist/locales/content/es.js +98 -0
- package/dist/locales/content/es.js.map +1 -0
- package/dist/locales/content/ja.d.ts +2 -0
- package/dist/locales/content/ja.js +98 -0
- package/dist/locales/content/ja.js.map +1 -0
- package/dist/locales/content/ko.d.ts +2 -0
- package/dist/locales/content/ko.js +98 -0
- package/dist/locales/content/ko.js.map +1 -0
- package/dist/locales/content/zh.d.ts +2 -0
- package/dist/locales/content/zh.js +98 -0
- package/dist/locales/content/zh.js.map +1 -0
- package/dist/locales/de.js +11 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.d.ts +2 -0
- package/dist/locales/discovery/de.js +68 -0
- package/dist/locales/discovery/de.js.map +1 -0
- package/dist/locales/discovery/en.d.ts +63 -0
- package/dist/locales/discovery/en.js +68 -0
- package/dist/locales/discovery/en.js.map +1 -0
- package/dist/locales/discovery/es.d.ts +2 -0
- package/dist/locales/discovery/es.js +68 -0
- package/dist/locales/discovery/es.js.map +1 -0
- package/dist/locales/discovery/ja.d.ts +2 -0
- package/dist/locales/discovery/ja.js +68 -0
- package/dist/locales/discovery/ja.js.map +1 -0
- package/dist/locales/discovery/ko.d.ts +2 -0
- package/dist/locales/discovery/ko.js +68 -0
- package/dist/locales/discovery/ko.js.map +1 -0
- package/dist/locales/discovery/zh.d.ts +2 -0
- package/dist/locales/discovery/zh.js +68 -0
- package/dist/locales/discovery/zh.js.map +1 -0
- package/dist/locales/en.d.ts +288 -67
- package/dist/locales/en.js +11 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +11 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.d.ts +2 -0
- package/dist/locales/groups/de.js +133 -0
- package/dist/locales/groups/de.js.map +1 -0
- package/dist/locales/groups/en.d.ts +128 -0
- package/dist/locales/groups/en.js +133 -0
- package/dist/locales/groups/en.js.map +1 -0
- package/dist/locales/groups/es.d.ts +2 -0
- package/dist/locales/groups/es.js +133 -0
- package/dist/locales/groups/es.js.map +1 -0
- package/dist/locales/groups/ja.d.ts +2 -0
- package/dist/locales/groups/ja.js +133 -0
- package/dist/locales/groups/ja.js.map +1 -0
- package/dist/locales/groups/ko.d.ts +2 -0
- package/dist/locales/groups/ko.js +133 -0
- package/dist/locales/groups/ko.js.map +1 -0
- package/dist/locales/groups/zh.d.ts +2 -0
- package/dist/locales/groups/zh.js +133 -0
- package/dist/locales/groups/zh.js.map +1 -0
- package/dist/locales/ja.js +11 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +11 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +2 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +2 -1
- package/dist/locales/social/en.js +2 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +2 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +2 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +2 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +2 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +11 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/admin.d.ts +1 -1
- package/dist/react/admin.js +8 -6
- package/dist/react/admin.js.map +1 -1
- package/dist/react/comments.d.ts +9 -7
- package/dist/react/comments.js +42 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +57 -0
- package/dist/react/contents.js +214 -0
- package/dist/react/contents.js.map +1 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/crop.js +45 -44
- package/dist/react/crop.js.map +1 -1
- package/dist/react/detail.d.ts +30 -0
- package/dist/react/detail.js +42 -0
- package/dist/react/detail.js.map +1 -0
- package/dist/react/discovery.d.ts +138 -0
- package/dist/react/discovery.js +299 -0
- package/dist/react/discovery.js.map +1 -0
- package/dist/react/engagement.d.ts +5 -5
- package/dist/react/engagement.js +55 -41
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.js +54 -53
- package/dist/react/folder.js.map +1 -1
- package/dist/react/groups.d.ts +115 -0
- package/dist/react/groups.js +229 -0
- package/dist/react/groups.js.map +1 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/public.d.ts +6 -5
- package/dist/react/public.js +19 -22
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +105 -106
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +2 -2
- package/dist/react/resources.js +69 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/store.js +41 -39
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.js +54 -53
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +2 -1
- package/dist/react/use-resource.js +1 -0
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.d.ts +57 -0
- package/dist/react/versions.js +94 -0
- package/dist/react/versions.js.map +1 -0
- package/dist/react/video.js +87 -86
- package/dist/react/video.js.map +1 -1
- package/dist/react.js +9 -5
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/dist/client/content/posts.d.ts +0 -52
- package/dist/client/content/posts.js +0 -84
- package/dist/client/content/posts.js.map +0 -1
- package/dist/client/taxonomy.d.ts +0 -34
- package/dist/client/taxonomy.js +0 -84
- package/dist/client/taxonomy.js.map +0 -1
- package/dist/components/admin/post-manager.d.ts +0 -45
- package/dist/components/admin/post-manager.js +0 -602
- package/dist/components/admin/post-manager.js.map +0 -1
- package/dist/components/admin/taxonomy-manager.d.ts +0 -54
- package/dist/components/admin/taxonomy-manager.js +0 -976
- package/dist/components/admin/taxonomy-manager.js.map +0 -1
- package/dist/react/posts.d.ts +0 -61
- package/dist/react/posts.js +0 -183
- package/dist/react/posts.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paywall.js","names":[],"sources":["../../../src/components/content/paywall.tsx"],"sourcesContent":["import { LockIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useMemo, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Decision, Sale, Terms, Version } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { contentError, ErrorLine, formatDate, useAction } from \"../social/parts.js\";\nimport { labelOf, useLanguageName, type Labels } from \"../groups/parts.js\";\nimport type { PlayerMenuItem } from \"../video-player.js\";\n\n/** An offer's price and period in words: \"$2.99\", \"$4.99 / 30 days\", \"$1.99 for 48 hours\". */\nexport function priceLabel(m: Translator, terms: Terms): string {\n let price: string;\n try {\n price = new Intl.NumberFormat(m.language, { style: \"currency\", currency: terms.currency }).format(Number(terms.amount));\n } catch {\n price = `${terms.amount} ${terms.currency}`;\n }\n const period = (hours: number) => (hours % 24 === 0 ? m.plural(\"paywall.days\", hours / 24) : m.plural(\"paywall.hours\", hours));\n if (terms.renew_hours) return m.t(\"paywall.renews\", { price, period: period(terms.renew_hours) });\n if (terms.access_hours) return m.t(\"paywall.rents\", { price, period: period(terms.access_hours) });\n return price;\n}\n\nexport interface PaywallProps {\n /** The locked version's (or card's) decision. */\n decision: Decision;\n /**\n * Starts a purchase: the host creates the order through billing-ui\n * (createOrder with offer.price_id) and shows OrderCheckout; for a tier\n * (no offer), its subscription page. A free group offer joins here instead.\n */\n onCheckout: (o: { key: string; offer?: Sale }) => void;\n /** Tiers' names and prices from the host's catalog, by tier key. */\n tiers?: Readonly<Record<string, { label?: string; price?: string }>>;\n /** Asks a signed-out reader to sign in (they may hold it already); default the provider's. */\n onSignIn?: () => void;\n labels?: Labels;\n /** Below the offers: the host's own. */\n children?: ReactNode;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Why content is locked and how to open it: its offers, the tiers that include it, previews and when it turns free. */\nexport function Paywall({ decision, onCheckout, tiers, onSignIn, labels, children, client: own, onError, className, appearance }: PaywallProps) {\n const m = useMessages();\n const { t } = m;\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n const { kinds } = useKinds({ client: own });\n const join = useAction(\"membership.join\", onError);\n if (decision.allowed) return null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offers = decision.offers ?? [];\n const sold = new Set(offers.map((o) => o.key));\n const siteTiers = kinds?.tiers ?? Object.keys(tiers ?? {});\n const tierKeys = (decision.sell ?? []).filter((k) => !sold.has(k) && (siteTiers.includes(k) || !!tiers?.[k]));\n const required = decision.requires;\n // A required key nothing here offers gets its own button; the others wait for it.\n const requires = required && !offers.some((o) => o.key === required) ? required : undefined;\n const waits = (key: string) => !!required && key !== required;\n const preview = decision.preview;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3 rounded-lg border border-border p-4 text-sm\", className)} data-ckui=\"paywall\">\n <p className=\"flex items-center gap-2 font-medium\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-4\" aria-hidden />\n {decision.unknown ? t(\"paywall.unknown\") : required ? t(\"paywall.requiresFirst\") : t(\"paywall.locked\")}\n </p>\n {preview?.pages ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewPages\", preview.pages)}</p> : null}\n {preview?.segments ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewSegments\", preview.segments)}</p> : null}\n {decision.free_after && <p className=\"text-muted-foreground\">{t(\"paywall.freeFrom\", { date: formatDate(m, decision.free_after, false) })}</p>}\n {!decision.unknown && (\n <div className=\"flex flex-wrap gap-2\" data-ckui=\"paywall-offers\">\n {requires && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => onCheckout({ key: requires })}>\n {t(\"paywall.joinFirst\")}\n </Button>\n )}\n {offers.map((o) =>\n o.target.kind === \"group\" && Number(o.terms.amount) === 0 ? (\n <Button\n key={o.key}\n size=\"sm\"\n disabled={waits(o.key) || join.pending}\n onClick={() => (ctx?.viewer === null && signIn ? signIn() : void join.run(() => client.offers.join(o.target.id)))}\n data-ckui=\"paywall-offer\"\n data-target=\"group\"\n data-free=\"\"\n >\n {t(\"paywall.joinFirst\")}\n </Button>\n ) : (\n <Button key={o.key} size=\"sm\" disabled={waits(o.key)} onClick={() => onCheckout({ key: o.key, offer: o })} data-ckui=\"paywall-offer\" data-target={o.target.kind}>\n {t(o.target.kind === \"group\" ? \"paywall.joinFor\" : o.target.kind === \"version\" ? \"paywall.unlockVersionFor\" : \"paywall.unlockFor\", { price: priceLabel(m, o.terms) })}\n </Button>\n ),\n )}\n {tierKeys.map((k) => (\n <Button key={k} size=\"sm\" variant={offers.length ? \"outline\" : \"default\"} onClick={() => onCheckout({ key: k })} data-ckui=\"paywall-tier\" data-tier={k}>\n {t(\"paywall.subscribe\", { tier: tiers?.[k]?.label ?? labelOf(labels, k) })}\n {tiers?.[k]?.price && <span className=\"ms-1 opacity-80\">{tiers[k]!.price}</span>}\n </Button>\n ))}\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n {ctx?.viewer === null && signIn && (\n <p className=\"text-muted-foreground\">\n {t(\"paywall.haveIt\")}{\" \"}\n <button type=\"button\" className=\"font-medium text-foreground underline-offset-4 hover:underline\" onClick={signIn}>\n {t(\"paywall.signIn\")}\n </button>\n </p>\n )}\n {children}\n </ContentKitUiRoot>\n );\n}\n\nexport interface VersionPickerProps {\n /** The content's live versions (useContentDetail().versions). */\n versions: readonly Version[];\n /** The version shown. */\n value: string | null | undefined;\n onChange: (version: string) => void;\n labels?: Labels;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A version's name: its label, else its traits, else its languages. */\nfunction versionName(v: Version, languageName: (l: string) => string, labels?: Labels): string {\n return v.label || (v.traits.length ? v.traits.map((x) => labelOf(labels, x)).join(\" · \") : v.languages.map(languageName).join(\", \"));\n}\n\nconst primary = (v: Version) => v.languages[0] ?? \"\";\n\n/** The shown version, the versions' languages and the shown language's variants. */\nfunction choices(versions: readonly Version[], value: string | null | undefined) {\n const current = versions.find((v) => v.id === value) ?? versions[0]!;\n const language = primary(current);\n return { current, language, languages: [...new Set(versions.map(primary))], variants: versions.filter((v) => primary(v) === language) };\n}\n\n/** Chooses among a content's versions: a language first when they differ in language, then its variants (traits, labels). */\nexport function VersionPicker({ versions, value, onChange, labels, className, appearance }: VersionPickerProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n if (versions.length < 2) return null;\n const { current, language, languages, variants } = choices(versions, value);\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"flex flex-wrap items-center gap-2 text-sm\", className)} data-ckui=\"version-picker\">\n {languages.length > 1 && (\n <Select\n value={language}\n onValueChange={(l) => {\n const v = versions.find((x) => primary(x) === l);\n if (v) onChange(v.id);\n }}\n >\n <SelectTrigger size=\"sm\" aria-label={t(\"versions.language\")}>\n <SelectValue>{(l: string) => languageName(l)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {variants.length > 1 && (\n <div role=\"radiogroup\" aria-label={t(\"versions.version\")} className=\"flex flex-wrap gap-1\">\n {variants.map((v) => (\n <Button\n key={v.id}\n size=\"sm\"\n role=\"radio\"\n aria-checked={v.id === current.id}\n variant={v.id === current.id ? \"default\" : \"outline\"}\n onClick={() => onChange(v.id)}\n data-ckui=\"version-option\"\n data-locked={v.decision.allowed ? undefined : \"\"}\n >\n {!v.decision.allowed && <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-label={t(\"versions.locked\")} />}\n {versionName(v, languageName, labels)}\n </Button>\n ))}\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\n/** VersionPicker as VideoPlayer settings entries (menuItems), for a watch page. */\nexport function useVersionMenuItems(\n versions: readonly Version[],\n value: string | null | undefined,\n onChange: (version: string) => void,\n labels?: Labels,\n): PlayerMenuItem[] {\n const { t } = useMessages();\n const languageName = useLanguageName();\n return useMemo(() => {\n if (versions.length < 2) return [];\n const { current, language, languages, variants } = choices(versions, value);\n const out: PlayerMenuItem[] = [];\n if (languages.length > 1)\n out.push({\n label: t(\"versions.language\"),\n value: language,\n options: languages.map((l) => ({ value: l, label: languageName(l) })),\n onChange: (l) => {\n const v = versions.find((x) => primary(x) === l);\n if (v) onChange(v.id);\n },\n });\n if (variants.length > 1)\n out.push({\n label: t(\"versions.version\"),\n value: current.id,\n options: variants.map((v) => {\n const name = versionName(v, languageName, labels);\n return { value: v.id, label: v.decision.allowed ? name : t(\"versions.lockedOption\", { name }) };\n }),\n onChange,\n });\n return out;\n }, [versions, value, onChange, labels, t, languageName]);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ContentKitUiAppearance } from '../../appearance.js';
|
|
2
|
+
import { ContentKitClient } from '../../client/client.js';
|
|
3
|
+
import { Card, GroupCard } from '../../client/generated/wire.js';
|
|
4
|
+
import { Labels } from '../groups/parts.js';
|
|
5
|
+
export interface SearchBoxProps {
|
|
6
|
+
/** Submits the text: the host shows its search page. */
|
|
7
|
+
onSearch: (q: string) => void;
|
|
8
|
+
/** Suggest contents of this kind only. */
|
|
9
|
+
kind?: string;
|
|
10
|
+
/** The group kinds suggested; every kind with a page by default. */
|
|
11
|
+
groupKinds?: readonly string[];
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
/** A suggestion's link; default its canonical path. */
|
|
15
|
+
href?: (item: Card | GroupCard) => string | undefined;
|
|
16
|
+
labels?: Labels;
|
|
17
|
+
/** The reader's recent searches while the box is empty. Default true. */
|
|
18
|
+
recent?: boolean;
|
|
19
|
+
client?: ContentKitClient;
|
|
20
|
+
className?: string;
|
|
21
|
+
appearance?: ContentKitUiAppearance;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A search field suggesting contents and groups as the reader types, and
|
|
25
|
+
* recent searches while it is empty; Enter searches, or opens the suggestion chosen.
|
|
26
|
+
*/
|
|
27
|
+
export declare function SearchBox(p: SearchBoxProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { RenditionImg as e } from "../rendition-img.js";
|
|
2
|
+
import { useMessages as t } from "../../i18n/context.js";
|
|
3
|
+
import { ContentKitUiRoot as n } from "../../scope.js";
|
|
4
|
+
import { ContentKitContext as r } from "../../react/context.js";
|
|
5
|
+
import { Input as i } from "../../ui/input.js";
|
|
6
|
+
import { useRecentSearches as a, useTypeahead as o } from "../../react/discovery.js";
|
|
7
|
+
import { labelOf as s } from "../groups/parts.js";
|
|
8
|
+
import { useContext as c, useId as l, useMemo as u, useState as d } from "react";
|
|
9
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
10
|
+
import { cn as h } from "cn";
|
|
11
|
+
import { Cancel01Icon as g, Clock01Icon as _, Search01Icon as v } from "@hugeicons/core-free-icons";
|
|
12
|
+
import { HugeiconsIcon as y } from "@hugeicons/react";
|
|
13
|
+
//#region src/components/content/search.tsx
|
|
14
|
+
function b(b) {
|
|
15
|
+
let { t: x } = t(), S = l(), C = c(r)?.navigate, [w, T] = d(b.defaultValue ?? ""), [E, D] = d(!1), [O, k] = d(-1), [A, j] = d(!1), M = !w.trim(), N = o(w, {
|
|
16
|
+
kind: b.kind,
|
|
17
|
+
group_kinds: b.groupKinds,
|
|
18
|
+
client: b.client
|
|
19
|
+
}), P = a({
|
|
20
|
+
enabled: b.recent !== !1 && A,
|
|
21
|
+
client: b.client
|
|
22
|
+
}), F = u(() => M ? P.searches.map((e) => ({
|
|
23
|
+
type: "recent",
|
|
24
|
+
query: e.query
|
|
25
|
+
})) : [...N.contents.map((e) => ({
|
|
26
|
+
type: "content",
|
|
27
|
+
card: e
|
|
28
|
+
})), ...N.groups.map((e) => ({
|
|
29
|
+
type: "group",
|
|
30
|
+
group: e
|
|
31
|
+
}))], [
|
|
32
|
+
M,
|
|
33
|
+
P.searches,
|
|
34
|
+
N.contents,
|
|
35
|
+
N.groups
|
|
36
|
+
]), I = E && F.length > 0 && (M || N.query === w.trim()), L = (e) => {
|
|
37
|
+
D(!1), e && b.onSearch(e);
|
|
38
|
+
}, R = (e) => {
|
|
39
|
+
if (e.type === "recent") return T(e.query), L(e.query);
|
|
40
|
+
let t = b.href ? b.href(e.type === "content" ? e.card : e.group) : e.type === "content" ? e.card.path : e.group.path;
|
|
41
|
+
D(!1), t && (C ? C(t) : location.assign(t));
|
|
42
|
+
};
|
|
43
|
+
return /* @__PURE__ */ m(n, {
|
|
44
|
+
appearance: b.appearance,
|
|
45
|
+
className: h("relative text-sm", b.className),
|
|
46
|
+
"data-ckui": "search-box",
|
|
47
|
+
children: [/* @__PURE__ */ m("form", {
|
|
48
|
+
role: "search",
|
|
49
|
+
onSubmit: (e) => {
|
|
50
|
+
if (e.preventDefault(), I && O >= 0) return R(F[O]);
|
|
51
|
+
L(w.trim());
|
|
52
|
+
},
|
|
53
|
+
children: [/* @__PURE__ */ p(y, {
|
|
54
|
+
icon: v,
|
|
55
|
+
strokeWidth: 2,
|
|
56
|
+
className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground",
|
|
57
|
+
"aria-hidden": !0
|
|
58
|
+
}), /* @__PURE__ */ p(i, {
|
|
59
|
+
type: "search",
|
|
60
|
+
role: "combobox",
|
|
61
|
+
"aria-label": x("contents.search"),
|
|
62
|
+
"aria-expanded": I,
|
|
63
|
+
"aria-controls": `${S}-list`,
|
|
64
|
+
"aria-activedescendant": I && O >= 0 ? `${S}-${O}` : void 0,
|
|
65
|
+
"aria-autocomplete": "list",
|
|
66
|
+
autoComplete: "off",
|
|
67
|
+
className: "h-9 ps-8",
|
|
68
|
+
value: w,
|
|
69
|
+
placeholder: b.placeholder ?? x("contents.searchPlaceholder"),
|
|
70
|
+
onChange: (e) => {
|
|
71
|
+
T(e.target.value), D(!0), k(-1);
|
|
72
|
+
},
|
|
73
|
+
onFocus: () => {
|
|
74
|
+
D(!0), j(!0);
|
|
75
|
+
},
|
|
76
|
+
onBlur: () => D(!1),
|
|
77
|
+
onKeyDown: (e) => {
|
|
78
|
+
if (e.key === "Escape") return D(!1);
|
|
79
|
+
I && (e.key === "ArrowDown" ? (e.preventDefault(), k((e) => Math.min(F.length - 1, e + 1))) : e.key === "ArrowUp" && (e.preventDefault(), k((e) => Math.max(-1, e - 1))));
|
|
80
|
+
}
|
|
81
|
+
})]
|
|
82
|
+
}), I && /* @__PURE__ */ m("div", {
|
|
83
|
+
className: "absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md",
|
|
84
|
+
children: [M && /* @__PURE__ */ m("div", {
|
|
85
|
+
className: "flex items-center justify-between gap-2 px-2 py-1 text-xs text-muted-foreground",
|
|
86
|
+
children: [/* @__PURE__ */ p("span", {
|
|
87
|
+
id: `${S}-recent`,
|
|
88
|
+
children: x("contents.recentSearches")
|
|
89
|
+
}), /* @__PURE__ */ p("button", {
|
|
90
|
+
type: "button",
|
|
91
|
+
className: "rounded px-1 hover:text-foreground",
|
|
92
|
+
onMouseDown: (e) => {
|
|
93
|
+
e.preventDefault(), P.clear();
|
|
94
|
+
},
|
|
95
|
+
children: x("contents.clearRecent")
|
|
96
|
+
})]
|
|
97
|
+
}), /* @__PURE__ */ p("ul", {
|
|
98
|
+
id: `${S}-list`,
|
|
99
|
+
role: "listbox",
|
|
100
|
+
"aria-label": x(M ? "contents.recentSearches" : "contents.suggestions"),
|
|
101
|
+
className: "grid",
|
|
102
|
+
children: F.map((t, n) => /* @__PURE__ */ p("li", {
|
|
103
|
+
id: `${S}-${n}`,
|
|
104
|
+
role: "option",
|
|
105
|
+
"aria-selected": n === O,
|
|
106
|
+
"data-ckui": "suggestion",
|
|
107
|
+
"data-type": t.type,
|
|
108
|
+
className: h("flex cursor-pointer items-center gap-2 px-2 py-1.5", n === O && "bg-accent text-accent-foreground"),
|
|
109
|
+
onMouseEnter: () => k(n),
|
|
110
|
+
onMouseDown: (e) => {
|
|
111
|
+
e.preventDefault(), R(t);
|
|
112
|
+
},
|
|
113
|
+
children: t.type === "content" ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("span", {
|
|
114
|
+
className: "h-10 w-8 shrink-0 overflow-hidden rounded bg-muted",
|
|
115
|
+
children: /* @__PURE__ */ p(e, {
|
|
116
|
+
outputs: t.card.cover,
|
|
117
|
+
alt: "",
|
|
118
|
+
fit: "cover",
|
|
119
|
+
className: "size-full"
|
|
120
|
+
})
|
|
121
|
+
}), /* @__PURE__ */ p("span", {
|
|
122
|
+
className: "min-w-0 truncate",
|
|
123
|
+
children: t.card.title || t.card.code
|
|
124
|
+
})] }) : t.type === "group" ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("span", {
|
|
125
|
+
className: "min-w-0 flex-1 truncate",
|
|
126
|
+
children: t.group.name || t.group.slug
|
|
127
|
+
}), /* @__PURE__ */ p("span", {
|
|
128
|
+
className: "text-xs text-muted-foreground",
|
|
129
|
+
children: s(b.labels, t.group.kind)
|
|
130
|
+
})] }) : /* @__PURE__ */ m(f, { children: [
|
|
131
|
+
/* @__PURE__ */ p(y, {
|
|
132
|
+
icon: _,
|
|
133
|
+
strokeWidth: 2,
|
|
134
|
+
className: "size-4 shrink-0 text-muted-foreground",
|
|
135
|
+
"aria-hidden": !0
|
|
136
|
+
}),
|
|
137
|
+
/* @__PURE__ */ p("span", {
|
|
138
|
+
className: "min-w-0 flex-1 truncate",
|
|
139
|
+
children: t.query
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ p("button", {
|
|
142
|
+
type: "button",
|
|
143
|
+
className: "rounded p-0.5 text-muted-foreground hover:text-foreground",
|
|
144
|
+
"aria-label": x("contents.removeRecent", { q: t.query }),
|
|
145
|
+
onMouseDown: (e) => {
|
|
146
|
+
e.preventDefault(), e.stopPropagation(), P.clear(t.query);
|
|
147
|
+
},
|
|
148
|
+
children: /* @__PURE__ */ p(y, {
|
|
149
|
+
icon: g,
|
|
150
|
+
strokeWidth: 2,
|
|
151
|
+
className: "size-3.5",
|
|
152
|
+
"aria-hidden": !0
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
] })
|
|
156
|
+
}, t.type === "content" ? `c:${t.card.id}` : t.type === "group" ? `g:${t.group.id}` : `r:${t.query}`))
|
|
157
|
+
})]
|
|
158
|
+
})]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { b as SearchBox };
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, Search01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useId, useMemo, useState } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** The reader's recent searches while the box is empty. Default true. */\n recent?: boolean;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string };\n\n/**\n * A search field suggesting contents and groups as the reader types, and\n * recent searches while it is empty; Enter searches, or opens the suggestion chosen.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n const [active, setActive] = useState(-1);\n const [touched, setTouched] = useState(false);\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, group_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && touched, client: p.client });\n const items = useMemo<Suggestion[]>(\n () =>\n empty\n ? recent.searches.map((r) => ({ type: \"recent\" as const, query: r.query }))\n : [...found.contents.map((card) => ({ type: \"content\" as const, card })), ...found.groups.map((group) => ({ type: \"group\" as const, group }))],\n [empty, recent.searches, found.contents, found.groups],\n );\n const shown = open && items.length > 0 && (empty || found.query === text.trim());\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\">\n <form\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n if (shown && active >= 0) return go(items[active]!);\n search(text.trim());\n }}\n >\n <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />\n <Input\n type=\"search\"\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={shown}\n aria-controls={`${id}-list`}\n aria-activedescendant={shown && active >= 0 ? `${id}-${active}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n className=\"h-9 ps-8\"\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(-1);\n }}\n onFocus={() => {\n setOpen(true);\n setTouched(true);\n }}\n onBlur={() => setOpen(false)}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") return setOpen(false);\n if (!shown) return;\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActive((a) => Math.min(items.length - 1, a + 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((a) => Math.max(-1, a - 1));\n }\n }}\n />\n </form>\n {shown && (\n <div className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\">\n {empty && (\n <div className=\"flex items-center justify-between gap-2 px-2 py-1 text-xs text-muted-foreground\">\n <span id={`${id}-recent`}>{t(\"contents.recentSearches\")}</span>\n <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n void recent.clear();\n }}\n >\n {t(\"contents.clearRecent\")}\n </button>\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className=\"grid\">\n {items.map((s, i) => (\n <li\n key={s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center gap-2 px-2 py-1.5\", i === active && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(i)}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n void recent.clear(s.query);\n }}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ContentKitUiAppearance } from '../../appearance.js';
|
|
3
|
+
import { ContentKitClient } from '../../client/client.js';
|
|
4
|
+
import { GroupAccess, GroupDetail, Sale } from '../../client/generated/wire.js';
|
|
5
|
+
import { GroupImageSlot } from '../../client/groups.js';
|
|
6
|
+
import { ContentKitErrorHandler } from '../../react/context.js';
|
|
7
|
+
import { GroupLike } from './parts.js';
|
|
8
|
+
export interface MembershipBoxProps {
|
|
9
|
+
group: Pick<GroupDetail, "id" | "kind" | "name"> & {
|
|
10
|
+
access?: GroupAccess;
|
|
11
|
+
};
|
|
12
|
+
/** Buys a sold membership: the host creates the order through billing-ui. */
|
|
13
|
+
onCheckout?: (o: {
|
|
14
|
+
key: string;
|
|
15
|
+
offer: Sale;
|
|
16
|
+
}) => void;
|
|
17
|
+
/** Asks a signed-out visitor to sign in; default the provider's. */
|
|
18
|
+
onSignIn?: () => void;
|
|
19
|
+
client?: ContentKitClient;
|
|
20
|
+
onError?: ContentKitErrorHandler;
|
|
21
|
+
className?: string;
|
|
22
|
+
appearance?: ContentKitUiAppearance;
|
|
23
|
+
}
|
|
24
|
+
/** A group's membership for the reader: member, join free, or join for its price. */
|
|
25
|
+
export declare function MembershipBox({ group, onCheckout, onSignIn, client: own, onError, className, appearance }: MembershipBoxProps): import('react').JSX.Element | null;
|
|
26
|
+
export interface GroupBannerProps {
|
|
27
|
+
/** The group the host already read; or give id. */
|
|
28
|
+
group?: GroupDetail;
|
|
29
|
+
id?: string;
|
|
30
|
+
language?: string;
|
|
31
|
+
onCheckout?: MembershipBoxProps["onCheckout"];
|
|
32
|
+
/** Beside the membership box: the host's actions (follow, share, edit). */
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
client?: ContentKitClient;
|
|
35
|
+
className?: string;
|
|
36
|
+
appearance?: ContentKitUiAppearance;
|
|
37
|
+
}
|
|
38
|
+
/** A group's page head: its cover across, its avatar, name, description, content count and membership. */
|
|
39
|
+
export declare function GroupBanner(p: GroupBannerProps): import('react').JSX.Element;
|
|
40
|
+
export interface GroupImageEditorProps {
|
|
41
|
+
group: GroupLike;
|
|
42
|
+
/** The cover, or a user-owned kind's avatar. */
|
|
43
|
+
slot: GroupImageSlot;
|
|
44
|
+
/** Crop before uploading. Default true. */
|
|
45
|
+
crop?: boolean;
|
|
46
|
+
client?: ContentKitClient;
|
|
47
|
+
onError?: ContentKitErrorHandler;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
/** A group's cover or avatar for its editors: pick (and crop) an image, upload it and select it; or go back to none (a cover: automatic). */
|
|
51
|
+
export declare function GroupImageEditor({ group, slot, crop, client, onError, className }: GroupImageEditorProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { useMessages as e } from "../../i18n/context.js";
|
|
2
|
+
import { ContentKitUiRoot as t } from "../../scope.js";
|
|
3
|
+
import { ContentKitContext as n, useContentKitClient as r } from "../../react/context.js";
|
|
4
|
+
import { decodeImage as i } from "../../client/image.js";
|
|
5
|
+
import { Button as a } from "../../ui/button.js";
|
|
6
|
+
import { ImageCropDialog as o } from "../image-crop-dialog.js";
|
|
7
|
+
import { Badge as s } from "../../ui/badge.js";
|
|
8
|
+
import { Skeleton as c } from "../../ui/skeleton.js";
|
|
9
|
+
import { ErrorLine as l, contentError as u, useAction as d } from "../social/parts.js";
|
|
10
|
+
import { useGroup as f } from "../../react/groups.js";
|
|
11
|
+
import { GroupAvatar as p, GroupCover as m } from "./parts.js";
|
|
12
|
+
import { priceLabel as h } from "../content/paywall.js";
|
|
13
|
+
import { useContext as g, useRef as _, useState as v } from "react";
|
|
14
|
+
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
15
|
+
import { cn as x } from "cn";
|
|
16
|
+
//#region src/components/groups/banner.tsx
|
|
17
|
+
function S({ group: i, onCheckout: o, onSignIn: c, client: f, onError: p, className: m, appearance: _ }) {
|
|
18
|
+
let v = e(), { t: S } = v, C = r(f), w = g(n), T = d("membership.join", p), E = i.access;
|
|
19
|
+
if (!E || E.editor) return null;
|
|
20
|
+
let D = w?.viewer === null, O = c ?? w?.onSignIn, k = E.offer && Number(E.offer.terms.amount) > 0 ? E.offer : void 0;
|
|
21
|
+
return /* @__PURE__ */ b(t, {
|
|
22
|
+
appearance: _,
|
|
23
|
+
className: x("grid gap-2 rounded-lg border border-border p-3 text-sm", m),
|
|
24
|
+
"data-ckui": "membership-box",
|
|
25
|
+
"data-member": E.member,
|
|
26
|
+
children: [E.unknown ? /* @__PURE__ */ y("p", {
|
|
27
|
+
className: "text-muted-foreground",
|
|
28
|
+
children: S("membership.unknown")
|
|
29
|
+
}) : E.member ? /* @__PURE__ */ b("div", {
|
|
30
|
+
className: "flex flex-wrap items-center gap-2",
|
|
31
|
+
children: [/* @__PURE__ */ y(s, { children: S("membership.member") }), !k && /* @__PURE__ */ y(a, {
|
|
32
|
+
size: "sm",
|
|
33
|
+
variant: "ghost",
|
|
34
|
+
disabled: T.pending,
|
|
35
|
+
onClick: () => void T.run(() => C.offers.join(i.id, !1)),
|
|
36
|
+
children: S("membership.leave")
|
|
37
|
+
})]
|
|
38
|
+
}) : /* @__PURE__ */ b("div", {
|
|
39
|
+
className: "grid gap-2",
|
|
40
|
+
children: [/* @__PURE__ */ y("p", {
|
|
41
|
+
className: "text-muted-foreground",
|
|
42
|
+
children: S(k ? "membership.paidPitch" : "membership.freePitch")
|
|
43
|
+
}), /* @__PURE__ */ y(a, {
|
|
44
|
+
size: "sm",
|
|
45
|
+
className: "justify-self-start",
|
|
46
|
+
disabled: T.pending || !!k && !o,
|
|
47
|
+
onClick: () => {
|
|
48
|
+
if (D && O) return O();
|
|
49
|
+
if (k) return o?.({
|
|
50
|
+
key: E.key,
|
|
51
|
+
offer: k
|
|
52
|
+
});
|
|
53
|
+
T.run(() => C.offers.join(i.id));
|
|
54
|
+
},
|
|
55
|
+
children: k ? S("membership.joinFor", { price: h(v, k.terms) }) : S("membership.join")
|
|
56
|
+
})]
|
|
57
|
+
}), T.error && /* @__PURE__ */ y(l, { children: u(v, T.error) })]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function C(n) {
|
|
61
|
+
let r = e(), i = f(n.group ? null : n.id, {
|
|
62
|
+
language: n.language,
|
|
63
|
+
client: n.client
|
|
64
|
+
}), a = n.group ?? i.group;
|
|
65
|
+
if (!a) return i.error ? /* @__PURE__ */ y(l, { children: u(r, i.error) }) : /* @__PURE__ */ y(c, { className: "h-48 w-full" });
|
|
66
|
+
let o = a.text.find((e) => e.language === (n.language ?? a.name_language)) ?? a.text.find((e) => e.description), s = a.counts.filter((e) => e.language === (n.language ?? "")).reduce((e, t) => e + t.count, 0);
|
|
67
|
+
return /* @__PURE__ */ b(t, {
|
|
68
|
+
appearance: n.appearance,
|
|
69
|
+
className: x("grid gap-3 text-sm", n.className),
|
|
70
|
+
"data-ckui": "group-banner",
|
|
71
|
+
"data-kind": a.kind,
|
|
72
|
+
children: [
|
|
73
|
+
/* @__PURE__ */ y(m, {
|
|
74
|
+
group: a,
|
|
75
|
+
className: "aspect-[3/1] w-full"
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ b("div", {
|
|
78
|
+
className: "flex flex-wrap items-end gap-4 px-2 sm:px-4",
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ y(p, {
|
|
81
|
+
group: a,
|
|
82
|
+
className: "-mt-12 size-20 border-4 border-background sm:-mt-16 sm:size-28"
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ b("div", {
|
|
85
|
+
className: "grid min-w-0 flex-1 gap-1",
|
|
86
|
+
children: [/* @__PURE__ */ y("h1", {
|
|
87
|
+
className: "text-xl font-semibold break-words",
|
|
88
|
+
children: a.name || a.slug
|
|
89
|
+
}), /* @__PURE__ */ y("p", {
|
|
90
|
+
className: "text-xs text-muted-foreground tabular-nums",
|
|
91
|
+
children: r.plural("groups.items", s)
|
|
92
|
+
})]
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ b("div", {
|
|
95
|
+
className: "flex flex-wrap items-start gap-2",
|
|
96
|
+
children: [/* @__PURE__ */ y(S, {
|
|
97
|
+
group: a,
|
|
98
|
+
onCheckout: n.onCheckout,
|
|
99
|
+
client: n.client
|
|
100
|
+
}), n.children]
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
}),
|
|
104
|
+
o?.description && /* @__PURE__ */ y("p", {
|
|
105
|
+
className: "px-2 break-words whitespace-pre-line sm:px-4",
|
|
106
|
+
children: o.description
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function w({ group: t, slot: n, crop: r = !0, client: s, onError: c, className: u }) {
|
|
112
|
+
let { t: h } = e(), g = f(t.id, { client: s }), S = _(null), C = d("group.save", c), [w, T] = v(null), [E, D] = v(null), O = g.group ?? t, k = n === "cover" ? !!O.cover?.image || !!O.cover?.content_id && !O.cover.auto : !!O.avatar?.image, A = (e, t) => C.run(() => g.uploadImage(n, e, {
|
|
113
|
+
wait: !1,
|
|
114
|
+
edit: t
|
|
115
|
+
})), j = () => {
|
|
116
|
+
w?.revoke?.(), T(null), D(null);
|
|
117
|
+
};
|
|
118
|
+
return /* @__PURE__ */ b("div", {
|
|
119
|
+
className: x("flex flex-wrap items-center gap-3", u),
|
|
120
|
+
"data-ckui": "group-image-editor",
|
|
121
|
+
"data-slot": n,
|
|
122
|
+
children: [
|
|
123
|
+
n === "avatar" ? /* @__PURE__ */ y(p, {
|
|
124
|
+
group: O,
|
|
125
|
+
src: g.previews.avatar,
|
|
126
|
+
className: "size-16"
|
|
127
|
+
}) : /* @__PURE__ */ y(m, {
|
|
128
|
+
group: O,
|
|
129
|
+
src: g.previews.cover,
|
|
130
|
+
className: "aspect-[3/1] w-48"
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ b("div", {
|
|
133
|
+
className: "grid gap-2",
|
|
134
|
+
children: [
|
|
135
|
+
/* @__PURE__ */ b("div", {
|
|
136
|
+
className: "flex flex-wrap gap-2",
|
|
137
|
+
children: [/* @__PURE__ */ y(a, {
|
|
138
|
+
type: "button",
|
|
139
|
+
size: "sm",
|
|
140
|
+
variant: "outline",
|
|
141
|
+
disabled: C.pending,
|
|
142
|
+
onClick: () => S.current?.click(),
|
|
143
|
+
children: C.pending ? h("groupEditor.uploading") : h(n === "avatar" ? "groupEditor.uploadAvatar" : "groupEditor.uploadCover")
|
|
144
|
+
}), k && /* @__PURE__ */ y(a, {
|
|
145
|
+
type: "button",
|
|
146
|
+
size: "sm",
|
|
147
|
+
variant: "ghost",
|
|
148
|
+
disabled: C.pending,
|
|
149
|
+
onClick: () => void C.run(() => g.update(n === "cover" ? { cover: {} } : { avatar: {} })),
|
|
150
|
+
children: h(n === "avatar" ? "groupEditor.removeAvatar" : "groupEditor.coverUseAuto")
|
|
151
|
+
})]
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ y("input", {
|
|
154
|
+
ref: S,
|
|
155
|
+
type: "file",
|
|
156
|
+
accept: "image/*",
|
|
157
|
+
hidden: !0,
|
|
158
|
+
"aria-label": h(n === "avatar" ? "groupEditor.uploadAvatar" : "groupEditor.uploadCover"),
|
|
159
|
+
onChange: (e) => {
|
|
160
|
+
let t = e.target.files?.[0];
|
|
161
|
+
if (e.target.value = "", t) {
|
|
162
|
+
if (!r) return void A(t);
|
|
163
|
+
i(t).then((e) => {
|
|
164
|
+
D(t), T(e);
|
|
165
|
+
}, () => void A(t));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ y(l, { error: C.error })
|
|
170
|
+
]
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ y(o, {
|
|
173
|
+
open: !!w,
|
|
174
|
+
onOpenChange: (e) => !e && j(),
|
|
175
|
+
source: w,
|
|
176
|
+
aspect: n === "avatar" ? "1:1" : "3:1",
|
|
177
|
+
aspects: n === "avatar" ? void 0 : [
|
|
178
|
+
"3:1",
|
|
179
|
+
"16:9",
|
|
180
|
+
""
|
|
181
|
+
],
|
|
182
|
+
round: n === "avatar",
|
|
183
|
+
onConfirm: (e) => {
|
|
184
|
+
let t = E;
|
|
185
|
+
j(), t && A(t, e ?? void 0);
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
export { C as GroupBanner, w as GroupImageEditor, S as MembershipBox };
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"banner.js","names":[],"sources":["../../../src/components/groups/banner.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Edit, GroupAccess, GroupDetail, Sale } from \"../../client/generated/wire.js\";\nimport type { GroupImageSlot } from \"../../client/groups.js\";\nimport { decodeImage, type CropSource } from \"../../client/image.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useGroup } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { ImageCropDialog } from \"../image-crop-dialog.js\";\nimport { contentError, ErrorLine, useAction } from \"../social/parts.js\";\nimport { priceLabel } from \"../content/paywall.js\";\nimport { GroupAvatar, GroupCover, type GroupLike } from \"./parts.js\";\n\nexport interface MembershipBoxProps {\n group: Pick<GroupDetail, \"id\" | \"kind\" | \"name\"> & { access?: GroupAccess };\n /** Buys a sold membership: the host creates the order through billing-ui. */\n onCheckout?: (o: { key: string; offer: Sale }) => void;\n /** Asks a signed-out visitor to sign in; default the provider's. */\n onSignIn?: () => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's membership for the reader: member, join free, or join for its price. */\nexport function MembershipBox({ group, onCheckout, onSignIn, client: own, onError, className, appearance }: MembershipBoxProps) {\n const m = useMessages();\n const { t } = m;\n const client = useContentKitClient(own);\n const ctx = useContext(ContentKitContext);\n const join = useAction(\"membership.join\", onError);\n const a = group.access;\n if (!a || a.editor) return null;\n const signedOut = ctx?.viewer === null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offer: Sale | undefined = a.offer && Number(a.offer.terms.amount) > 0 ? a.offer : undefined;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-2 rounded-lg border border-border p-3 text-sm\", className)} data-ckui=\"membership-box\" data-member={a.member}>\n {a.unknown ? (\n <p className=\"text-muted-foreground\">{t(\"membership.unknown\")}</p>\n ) : a.member ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge>{t(\"membership.member\")}</Badge>\n {!offer && (\n <Button size=\"sm\" variant=\"ghost\" disabled={join.pending} onClick={() => void join.run(() => client.offers.join(group.id, false))}>\n {t(\"membership.leave\")}\n </Button>\n )}\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <p className=\"text-muted-foreground\">{t(offer ? \"membership.paidPitch\" : \"membership.freePitch\")}</p>\n <Button\n size=\"sm\"\n className=\"justify-self-start\"\n disabled={join.pending || (!!offer && !onCheckout)}\n onClick={() => {\n if (signedOut && signIn) return signIn();\n if (offer) return onCheckout?.({ key: a.key, offer });\n void join.run(() => client.offers.join(group.id));\n }}\n >\n {offer ? t(\"membership.joinFor\", { price: priceLabel(m, offer.terms) }) : t(\"membership.join\")}\n </Button>\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupBannerProps {\n /** The group the host already read; or give id. */\n group?: GroupDetail;\n id?: string;\n language?: string;\n onCheckout?: MembershipBoxProps[\"onCheckout\"];\n /** Beside the membership box: the host's actions (follow, share, edit). */\n children?: ReactNode;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A group's page head: its cover across, its avatar, name, description, content count and membership. */\nexport function GroupBanner(p: GroupBannerProps) {\n const m = useMessages();\n const read = useGroup(p.group ? null : p.id, { language: p.language, client: p.client });\n const g = p.group ?? read.group;\n if (!g) return read.error ? <ErrorLine>{contentError(m, read.error)}</ErrorLine> : <Skeleton className=\"h-48 w-full\" />;\n const text = g.text.find((x) => x.language === (p.language ?? g.name_language)) ?? g.text.find((x) => x.description);\n const count = g.counts.filter((c) => c.language === (p.language ?? \"\")).reduce((n, c) => n + c.count, 0);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"group-banner\" data-kind={g.kind}>\n <GroupCover group={g} className=\"aspect-[3/1] w-full\" />\n <div className=\"flex flex-wrap items-end gap-4 px-2 sm:px-4\">\n <GroupAvatar group={g} className=\"-mt-12 size-20 border-4 border-background sm:-mt-16 sm:size-28\" />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <h1 className=\"text-xl font-semibold break-words\">{g.name || g.slug}</h1>\n <p className=\"text-xs text-muted-foreground tabular-nums\">{m.plural(\"groups.items\", count)}</p>\n </div>\n <div className=\"flex flex-wrap items-start gap-2\">\n <MembershipBox group={g} onCheckout={p.onCheckout} client={p.client} />\n {p.children}\n </div>\n </div>\n {text?.description && <p className=\"px-2 break-words whitespace-pre-line sm:px-4\">{text.description}</p>}\n </ContentKitUiRoot>\n );\n}\n\nexport interface GroupImageEditorProps {\n group: GroupLike;\n /** The cover, or a user-owned kind's avatar. */\n slot: GroupImageSlot;\n /** Crop before uploading. Default true. */\n crop?: boolean;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n}\n\n/** A group's cover or avatar for its editors: pick (and crop) an image, upload it and select it; or go back to none (a cover: automatic). */\nexport function GroupImageEditor({ group, slot, crop = true, client, onError, className }: GroupImageEditorProps) {\n const { t } = useMessages();\n const g = useGroup(group.id, { client });\n const input = useRef<HTMLInputElement>(null);\n const save = useAction(\"group.save\", onError);\n const [source, setSource] = useState<CropSource | null>(null);\n const [file, setFile] = useState<File | null>(null);\n const shown = g.group ?? group;\n const set = slot === \"cover\" ? !!shown.cover?.image || (!!shown.cover?.content_id && !shown.cover.auto) : !!shown.avatar?.image;\n const upload = (f: Blob & { name?: string }, edit?: Edit) => save.run(() => g.uploadImage(slot, f, { wait: false, edit }));\n const close = () => {\n source?.revoke?.();\n setSource(null);\n setFile(null);\n };\n return (\n <div className={cn(\"flex flex-wrap items-center gap-3\", className)} data-ckui=\"group-image-editor\" data-slot={slot}>\n {slot === \"avatar\" ? <GroupAvatar group={shown} src={g.previews.avatar} className=\"size-16\" /> : <GroupCover group={shown} src={g.previews.cover} className=\"aspect-[3/1] w-48\" />}\n <div className=\"grid gap-2\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={save.pending} onClick={() => input.current?.click()}>\n {save.pending ? t(\"groupEditor.uploading\") : t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n </Button>\n {set && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={save.pending} onClick={() => void save.run(() => g.update(slot === \"cover\" ? { cover: {} } : { avatar: {} }))}>\n {t(slot === \"avatar\" ? \"groupEditor.removeAvatar\" : \"groupEditor.coverUseAuto\")}\n </Button>\n )}\n </div>\n <input\n ref={input}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(slot === \"avatar\" ? \"groupEditor.uploadAvatar\" : \"groupEditor.uploadCover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (!f) return;\n if (!crop) return void upload(f);\n decodeImage(f).then(\n (s) => {\n setFile(f);\n setSource(s);\n },\n () => void upload(f),\n );\n }}\n />\n <ErrorLine error={save.error} />\n </div>\n <ImageCropDialog\n open={!!source}\n onOpenChange={(open) => !open && close()}\n source={source}\n aspect={slot === \"avatar\" ? \"1:1\" : \"3:1\"}\n aspects={slot === \"avatar\" ? undefined : [\"3:1\", \"16:9\", \"\"]}\n round={slot === \"avatar\"}\n onConfirm={(edit) => {\n const f = file;\n close();\n if (f) void upload(f, edit ?? undefined);\n }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgCA,SAAgB,EAAc,EAAE,UAAO,eAAY,aAAU,QAAQ,GAAK,YAAS,cAAW,iBAAkC;CAC9H,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAS,EAAoB,CAAG,GAChC,IAAM,EAAW,CAAiB,GAClC,IAAO,EAAU,mBAAmB,CAAO,GAC3C,IAAI,EAAM;CAChB,IAAI,CAAC,KAAK,EAAE,QAAQ,OAAO;CAC3B,IAAM,IAAY,GAAK,WAAW,MAC5B,IAAS,KAAY,GAAK,UAC1B,IAA0B,EAAE,SAAS,OAAO,EAAE,MAAM,MAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,KAAA;CACxF,OACE,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,0DAA0D,CAAS;EAAG,aAAU;EAAiB,eAAa,EAAE;EAAxK,UAAA,CACG,EAAE,UACD,kBAAC,KAAD;GAAG,WAAU;GAAyB,UAAA,EAAE,oBAAoB;EAAK,CAAA,IAC/D,EAAE,SACJ,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,mBAAmB,EAAS,CAAA,GACrC,CAAC,KACA,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,UAAU,EAAK;IAAS,eAAe,KAAK,EAAK,UAAU,EAAO,OAAO,KAAK,EAAM,IAAI,EAAK,CAAC;IAC7H,UAAA,EAAE,kBAAkB;GACf,CAAA,CAEP;EAEL,CAAA,IAAA,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,IAAQ,yBAAyB,sBAAsB;GAAK,CAAA,GACpG,kBAAC,GAAD;IACE,MAAK;IACL,WAAU;IACV,UAAU,EAAK,WAAY,CAAC,CAAC,KAAS,CAAC;IACvC,eAAe;KACb,IAAI,KAAa,GAAQ,OAAO,EAAO;KACvC,IAAI,GAAO,OAAO,IAAa;MAAE,KAAK,EAAE;MAAK;KAAM,CAAC;KACpD,EAAU,UAAU,EAAO,OAAO,KAAK,EAAM,EAAE,CAAC;IAClD;IAEC,UAAA,IAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAW,GAAG,EAAM,KAAK,EAAE,CAAC,IAAI,EAAE,iBAAiB;GACvF,CAAA,CACL;EAEN,CAAA,GAAA,EAAK,SAAS,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAK,KAAK,EAAa,CAAA,CAClD;;AAEtB;AAgBA,SAAgB,EAAY,GAAqB;CAC/C,IAAM,IAAI,EAAY,GAChB,IAAO,EAAS,EAAE,QAAQ,OAAO,EAAE,IAAI;EAAE,UAAU,EAAE;EAAU,QAAQ,EAAE;CAAO,CAAC,GACjF,IAAI,EAAE,SAAS,EAAK;CAC1B,IAAI,CAAC,GAAG,OAAO,EAAK,QAAQ,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAK,KAAK,EAAa,CAAA,IAAI,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA;CACtH,IAAM,IAAO,EAAE,KAAK,MAAM,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,KAAK,EAAE,KAAK,MAAM,MAAM,EAAE,WAAW,GAC7G,IAAQ,EAAE,OAAO,QAAQ,MAAM,EAAE,cAAc,EAAE,YAAY,GAAG,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;CACvG,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,aAAW,EAAE;EAApI,UAAA;GACE,kBAAC,GAAD;IAAY,OAAO;IAAG,WAAU;GAAuB,CAAA;GACvD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,GAAD;MAAa,OAAO;MAAG,WAAU;KAAkE,CAAA;KACnG,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;OAAqC,UAAA,EAAE,QAAQ,EAAE;MAAS,CAAA,GACxE,kBAAC,KAAD;OAAG,WAAU;OAA8C,UAAA,EAAE,OAAO,gBAAgB,CAAK;MAAK,CAAA,CAC3F;;KACL,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAe,OAAO;OAAG,YAAY,EAAE;OAAY,QAAQ,EAAE;MAAS,CAAA,GACrE,EAAE,QACA;;IACF;;GACJ,GAAM,eAAe,kBAAC,KAAD;IAAG,WAAU;IAAgD,UAAA,EAAK;GAAe,CAAA;EACvF;;AAEtB;AAcA,SAAgB,EAAiB,EAAE,UAAO,SAAM,UAAO,IAAM,WAAQ,YAAS,gBAAoC;CAChH,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,EAAS,EAAM,IAAI,EAAE,UAAO,CAAC,GACjC,IAAQ,EAAyB,IAAI,GACrC,IAAO,EAAU,cAAc,CAAO,GACtC,CAAC,GAAQ,KAAa,EAA4B,IAAI,GACtD,CAAC,GAAM,KAAW,EAAsB,IAAI,GAC5C,IAAQ,EAAE,SAAS,GACnB,IAAM,MAAS,UAAU,CAAC,CAAC,EAAM,OAAO,SAAU,CAAC,CAAC,EAAM,OAAO,cAAc,CAAC,EAAM,MAAM,OAAQ,CAAC,CAAC,EAAM,QAAQ,OACpH,KAAU,GAA6B,MAAgB,EAAK,UAAU,EAAE,YAAY,GAAM,GAAG;EAAE,MAAM;EAAO;CAAK,CAAC,CAAC,GACnH,UAAc;EAGlB,AAFA,GAAQ,SAAS,GACjB,EAAU,IAAI,GACd,EAAQ,IAAI;CACd;CACA,OACE,kBAAC,OAAD;EAAK,WAAW,EAAG,qCAAqC,CAAS;EAAG,aAAU;EAAqB,aAAW;EAA9G,UAAA;GACG,MAAS,WAAW,kBAAC,GAAD;IAAa,OAAO;IAAO,KAAK,EAAE,SAAS;IAAQ,WAAU;GAAW,CAAA,IAAI,kBAAC,GAAD;IAAY,OAAO;IAAO,KAAK,EAAE,SAAS;IAAO,WAAU;GAAqB,CAAA;GACjL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,kBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,kBAAC,GAAD;OAAQ,MAAK;OAAS,MAAK;OAAK,SAAQ;OAAU,UAAU,EAAK;OAAS,eAAe,EAAM,SAAS,MAAM;OAC3G,UAAA,EAAK,UAAU,EAAE,uBAAuB,IAAI,EAAE,MAAS,WAAW,6BAA6B,yBAAyB;MACnH,CAAA,GACP,KACC,kBAAC,GAAD;OAAQ,MAAK;OAAS,MAAK;OAAK,SAAQ;OAAQ,UAAU,EAAK;OAAS,eAAe,KAAK,EAAK,UAAU,EAAE,OAAO,MAAS,UAAU,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;OACnK,UAAA,EAAE,MAAS,WAAW,6BAA6B,0BAA0B;MACxE,CAAA,CAEP;;KACL,kBAAC,SAAD;MACE,KAAK;MACL,MAAK;MACL,QAAO;MACP,QAAA;MACA,cAAY,EAAE,MAAS,WAAW,6BAA6B,yBAAyB;MACxF,WAAW,MAAM;OACf,IAAM,IAAI,EAAE,OAAO,QAAQ;OAC3B,MAAE,OAAO,QAAQ,IACZ,GACL;YAAI,CAAC,GAAM,OAAO,KAAK,EAAO,CAAC;QAC/B,EAAY,CAAC,CAAC,CAAC,MACZ,MAAM;SAEL,AADA,EAAQ,CAAC,GACT,EAAU,CAAC;QACb,SACM,KAAK,EAAO,CAAC,CACrB;OAP+B;MAQjC;KACD,CAAA;KACD,kBAAC,GAAD,EAAW,OAAO,EAAK,MAAQ,CAAA;IAC5B;;GACL,kBAAC,GAAD;IACE,MAAM,CAAC,CAAC;IACR,eAAe,MAAS,CAAC,KAAQ,EAAM;IAC/B;IACR,QAAQ,MAAS,WAAW,QAAQ;IACpC,SAAS,MAAS,WAAW,KAAA,IAAY;KAAC;KAAO;KAAQ;IAAE;IAC3D,OAAO,MAAS;IAChB,YAAY,MAAS;KACnB,IAAM,IAAI;KAEV,AADA,EAAM,GACF,KAAG,EAAY,GAAG,KAAQ,KAAA,CAAS;IACzC;GACD,CAAA;EACE;;AAET"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ContentKitUiAppearance } from '../../appearance.js';
|
|
3
|
+
import { ContentKitClient } from '../../client/client.js';
|
|
4
|
+
import { GroupCard } from '../../client/generated/wire.js';
|
|
5
|
+
import { GroupSort } from '../../react/groups.js';
|
|
6
|
+
import { GroupLike } from './parts.js';
|
|
7
|
+
export interface GroupDirectoryProps {
|
|
8
|
+
/** The group kind listed: tag, artist, series. */
|
|
9
|
+
kind: string;
|
|
10
|
+
/** Names, counts and paths in this language; default the provider's. */
|
|
11
|
+
language?: string;
|
|
12
|
+
/** Count contents of this kind only; default every kind. */
|
|
13
|
+
contentKind?: string;
|
|
14
|
+
/** Children of this group. */
|
|
15
|
+
parent?: string;
|
|
16
|
+
/** Groups scoped by this group (characters of a series). */
|
|
17
|
+
scope?: string;
|
|
18
|
+
/** Default "name". */
|
|
19
|
+
sort?: GroupSort;
|
|
20
|
+
/** The letter bucket shown, a-z or "#", null for every group. With onLetterChange the host keeps it (in its URL). */
|
|
21
|
+
letter?: string | null;
|
|
22
|
+
onLetterChange?: (letter: string | null) => void;
|
|
23
|
+
/** Hide the A-Z bar. */
|
|
24
|
+
letters?: boolean;
|
|
25
|
+
/** "list" (names with counts, default) or "grid" (covers). */
|
|
26
|
+
layout?: "list" | "grid";
|
|
27
|
+
/** A group's link; default its canonical path. */
|
|
28
|
+
href?: (group: GroupLike) => string | undefined;
|
|
29
|
+
/** A group's entry instead of the default one. */
|
|
30
|
+
renderGroup?: (group: GroupCard) => ReactNode;
|
|
31
|
+
pageSize?: number;
|
|
32
|
+
client?: ContentKitClient;
|
|
33
|
+
className?: string;
|
|
34
|
+
appearance?: ContentKitUiAppearance;
|
|
35
|
+
}
|
|
36
|
+
/** A directory of one kind's groups: A-Z buckets, names with content counts or covers, paged. */
|
|
37
|
+
export declare function GroupDirectory(p: GroupDirectoryProps): import('react').JSX.Element;
|