Label document share as copy link
Sharing only copies the viewer URL, so rename the action and expose the same control on each documents list row. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -18,7 +18,10 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
// SOFTWARE.
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { Toast } from "@base-ui/react/toast";
|
||||||
|
import { LinkSimpleIcon } from "@phosphor-icons/react";
|
||||||
import { Checkbox } from "@probo/ui/src/v2/Checkbox/Checkbox";
|
import { Checkbox } from "@probo/ui/src/v2/Checkbox/Checkbox";
|
||||||
|
import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton";
|
||||||
import { ListItem } from "@probo/ui/src/v2/List/ListItem";
|
import { ListItem } from "@probo/ui/src/v2/List/ListItem";
|
||||||
import { ListItemContent } from "@probo/ui/src/v2/List/ListItemContent";
|
import { ListItemContent } from "@probo/ui/src/v2/List/ListItemContent";
|
||||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||||
@@ -27,6 +30,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link as RouterLink } from "react-router";
|
import { Link as RouterLink } from "react-router";
|
||||||
|
|
||||||
import { DocumentAccessAction } from "./DocumentAccessAction";
|
import { DocumentAccessAction } from "./DocumentAccessAction";
|
||||||
|
import { documentEntry } from "./variants";
|
||||||
|
|
||||||
interface DocumentEntryProps {
|
interface DocumentEntryProps {
|
||||||
// Primary line (document title, file name, or framework name).
|
// Primary line (document title, file name, or framework name).
|
||||||
@@ -51,8 +55,9 @@ interface DocumentEntryProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Presentational row shared by the document / file / report list items: a title
|
// Presentational row shared by the document / file / report list items: a title
|
||||||
// with accent metadata and the trailing access action. On small screens the
|
// with accent metadata, a copy-link control, and the trailing access action. On
|
||||||
// whole row is the hit target (the trailing icon is a status affordance only).
|
// small screens the whole row is the hit target (the trailing icon is a status
|
||||||
|
// affordance only).
|
||||||
export function DocumentEntry({
|
export function DocumentEntry({
|
||||||
title,
|
title,
|
||||||
meta,
|
meta,
|
||||||
@@ -65,6 +70,8 @@ export function DocumentEntry({
|
|||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
}: DocumentEntryProps) {
|
}: DocumentEntryProps) {
|
||||||
const { t } = useTranslation("documents");
|
const { t } = useTranslation("documents");
|
||||||
|
const toast = Toast.useToastManager();
|
||||||
|
const slots = documentEntry();
|
||||||
|
|
||||||
// Only locked rows (not yet authorized, no pending request) can be requested,
|
// Only locked rows (not yet authorized, no pending request) can be requested,
|
||||||
// so only they are selectable — the checkbox is disabled otherwise to avoid a
|
// so only they are selectable — the checkbox is disabled otherwise to avoid a
|
||||||
@@ -77,6 +84,14 @@ export function DocumentEntry({
|
|||||||
? t("actions.view")
|
? t("actions.view")
|
||||||
: t("actions.getAccess");
|
: t("actions.getAccess");
|
||||||
|
|
||||||
|
const handleCopyLink = () => {
|
||||||
|
const url = new URL(viewHref, window.location.origin);
|
||||||
|
navigator.clipboard.writeText(url.href).then(
|
||||||
|
() => toast.add({ title: t("viewer.linkCopied"), type: "success" }),
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
className={[
|
className={[
|
||||||
@@ -105,31 +120,42 @@ export function DocumentEntry({
|
|||||||
</Text>
|
</Text>
|
||||||
</ListItemContent>
|
</ListItemContent>
|
||||||
|
|
||||||
{/* Desktop: labeled interactive control. */}
|
<div className={slots.trailing()}>
|
||||||
<div className="max-sm:hidden">
|
<IconButton
|
||||||
<DocumentAccessAction
|
variant="ghost"
|
||||||
isAuthorized={isAuthorized}
|
color="neutral"
|
||||||
requested={requested}
|
aria-label={t("viewer.copyLink")}
|
||||||
viewHref={viewHref}
|
onClick={handleCopyLink}
|
||||||
onGetAccess={onGetAccess}
|
>
|
||||||
isRequesting={isRequesting}
|
<LinkSimpleIcon />
|
||||||
/>
|
</IconButton>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile: status icon only; the row overlay handles activation.
|
{/* Desktop: labeled interactive control. */}
|
||||||
Pending (`requested`) rows keep the icon in the a11y tree as status. */}
|
<div className={slots.accessDesktop()}>
|
||||||
<div
|
<DocumentAccessAction
|
||||||
className="hidden shrink-0 max-sm:block"
|
isAuthorized={isAuthorized}
|
||||||
aria-hidden={mobileHitLabel != null ? true : undefined}
|
requested={requested}
|
||||||
>
|
viewHref={viewHref}
|
||||||
<DocumentAccessAction
|
onGetAccess={onGetAccess}
|
||||||
isAuthorized={isAuthorized}
|
isRequesting={isRequesting}
|
||||||
requested={requested}
|
/>
|
||||||
viewHref={viewHref}
|
</div>
|
||||||
onGetAccess={onGetAccess}
|
|
||||||
isRequesting={isRequesting}
|
{/* Mobile: status icon only; the row overlay handles activation.
|
||||||
interactive={false}
|
Pending (`requested`) rows keep the icon in the a11y tree as status. */}
|
||||||
/>
|
<div
|
||||||
|
className={slots.accessMobile()}
|
||||||
|
aria-hidden={mobileHitLabel != null ? true : undefined}
|
||||||
|
>
|
||||||
|
<DocumentAccessAction
|
||||||
|
isAuthorized={isAuthorized}
|
||||||
|
requested={requested}
|
||||||
|
viewHref={viewHref}
|
||||||
|
onGetAccess={onGetAccess}
|
||||||
|
isRequesting={isRequesting}
|
||||||
|
interactive={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sit above the row content so title / icon areas are part of the hit target. */}
|
{/* Sit above the row content so title / icon areas are part of the hit target. */}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import {
|
|||||||
CaretLeftIcon,
|
CaretLeftIcon,
|
||||||
CaretRightIcon,
|
CaretRightIcon,
|
||||||
DownloadSimpleIcon,
|
DownloadSimpleIcon,
|
||||||
|
LinkSimpleIcon,
|
||||||
MagnifyingGlassMinusIcon,
|
MagnifyingGlassMinusIcon,
|
||||||
MagnifyingGlassPlusIcon,
|
MagnifyingGlassPlusIcon,
|
||||||
ShareNetworkIcon,
|
|
||||||
SpinnerGapIcon,
|
SpinnerGapIcon,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import { Button } from "@probo/ui/src/v2/Button/Button";
|
import { Button } from "@probo/ui/src/v2/Button/Button";
|
||||||
@@ -64,8 +64,8 @@ interface DocumentViewerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Full-page document viewer: a header band with the title and a toolbar
|
// Full-page document viewer: a header band with the title and a toolbar
|
||||||
// (page navigation + zoom for PDFs, share, download) above the scrollable body.
|
// (page navigation + zoom for PDFs, copy link, download) above the scrollable
|
||||||
// PDFs render with react-pdf, images inline, and anything else offers a
|
// body. PDFs render with react-pdf, images inline, and anything else offers a
|
||||||
// download.
|
// download.
|
||||||
export function DocumentViewer({ title, dataUri, downloadName }: DocumentViewerProps) {
|
export function DocumentViewer({ title, dataUri, downloadName }: DocumentViewerProps) {
|
||||||
const { t } = useTranslation("documents");
|
const { t } = useTranslation("documents");
|
||||||
@@ -87,7 +87,7 @@ export function DocumentViewer({ title, dataUri, downloadName }: DocumentViewerP
|
|||||||
setCurrentPage(next);
|
setCurrentPage(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = () => {
|
const handleCopyLink = () => {
|
||||||
navigator.clipboard.writeText(window.location.href).then(
|
navigator.clipboard.writeText(window.location.href).then(
|
||||||
() => toast.add({ title: t("viewer.linkCopied"), type: "success" }),
|
() => toast.add({ title: t("viewer.linkCopied"), type: "success" }),
|
||||||
() => {},
|
() => {},
|
||||||
@@ -168,11 +168,11 @@ export function DocumentViewer({ title, dataUri, downloadName }: DocumentViewerP
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
color="neutral"
|
color="neutral"
|
||||||
iconStart={<ShareNetworkIcon />}
|
iconStart={<LinkSimpleIcon />}
|
||||||
onClick={handleShare}
|
onClick={handleCopyLink}
|
||||||
aria-label={t("viewer.share")}
|
aria-label={t("viewer.copyLink")}
|
||||||
>
|
>
|
||||||
<span className={slots.actionLabel()}>{t("viewer.share")}</span>
|
<span className={slots.actionLabel()}>{t("viewer.copyLink")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Separator orientation="vertical" className={slots.separator()} />
|
<Separator orientation="vertical" className={slots.separator()} />
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ export const documentSection = tv({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Document list row trailing chrome: copy-link + access action. z-2 sits above
|
||||||
|
// the mobile full-row overlay so these controls stay tappable on small screens.
|
||||||
|
export const documentEntry = tv({
|
||||||
|
slots: {
|
||||||
|
trailing: "relative z-2 flex shrink-0 items-center gap-1",
|
||||||
|
accessDesktop: "max-sm:hidden",
|
||||||
|
accessMobile: "hidden shrink-0 max-sm:block",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// PDF preview: a scrollable grey stage holding the stacked, centered pages.
|
// PDF preview: a scrollable grey stage holding the stacked, centered pages.
|
||||||
export const pdfPreview = tv({
|
export const pdfPreview = tv({
|
||||||
slots: {
|
slots: {
|
||||||
@@ -48,7 +58,7 @@ export const documentViewer = tv({
|
|||||||
root: "flex h-full min-h-0 flex-1 flex-col",
|
root: "flex h-full min-h-0 flex-1 flex-col",
|
||||||
header: "flex w-full flex-col gap-3",
|
header: "flex w-full flex-col gap-3",
|
||||||
back: "-ml-2 self-start",
|
back: "-ml-2 self-start",
|
||||||
// Stay on one row so page/zoom controls and share/download share a baseline;
|
// Stay on one row so page/zoom controls and copy/download share a baseline;
|
||||||
// icon-only action labels on max-sm keep this viable on phones.
|
// icon-only action labels on max-sm keep this viable on phones.
|
||||||
toolbar: "flex min-h-16 flex-wrap items-center justify-between gap-x-4 gap-y-2",
|
toolbar: "flex min-h-16 flex-wrap items-center justify-between gap-x-4 gap-y-2",
|
||||||
toolbarStart: "flex min-w-0 flex-wrap items-center gap-2",
|
toolbarStart: "flex min-w-0 flex-wrap items-center gap-2",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Dokumente",
|
"back": "Dokumente",
|
||||||
"share": "Dokument teilen",
|
"copyLink": "Link kopieren",
|
||||||
"linkCopied": "Link in die Zwischenablage kopiert",
|
"linkCopied": "Link in die Zwischenablage kopiert",
|
||||||
"download": "Herunterladen",
|
"download": "Herunterladen",
|
||||||
"previewUnavailable": "Vorschau nicht verfügbar",
|
"previewUnavailable": "Vorschau nicht verfügbar",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Documents",
|
"back": "Documents",
|
||||||
"share": "Share document",
|
"copyLink": "Copy link",
|
||||||
"linkCopied": "Link copied to clipboard",
|
"linkCopied": "Link copied to clipboard",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"previewUnavailable": "Preview unavailable",
|
"previewUnavailable": "Preview unavailable",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Documentos",
|
"back": "Documentos",
|
||||||
"share": "Compartir documento",
|
"copyLink": "Copiar enlace",
|
||||||
"linkCopied": "Enlace copiado al portapapeles",
|
"linkCopied": "Enlace copiado al portapapeles",
|
||||||
"download": "Descargar",
|
"download": "Descargar",
|
||||||
"previewUnavailable": "Vista previa no disponible",
|
"previewUnavailable": "Vista previa no disponible",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Documents",
|
"back": "Documents",
|
||||||
"share": "Partager le document",
|
"copyLink": "Copier le lien",
|
||||||
"linkCopied": "Lien copié dans le presse-papiers",
|
"linkCopied": "Lien copié dans le presse-papiers",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
"previewUnavailable": "Aperçu indisponible",
|
"previewUnavailable": "Aperçu indisponible",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Dokumen",
|
"back": "Dokumen",
|
||||||
"share": "Bagikan dokumen",
|
"copyLink": "Salin tautan",
|
||||||
"linkCopied": "Tautan disalin ke clipboard",
|
"linkCopied": "Tautan disalin ke clipboard",
|
||||||
"download": "Unduh",
|
"download": "Unduh",
|
||||||
"previewUnavailable": "Pratinjau tidak tersedia",
|
"previewUnavailable": "Pratinjau tidak tersedia",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Documenti",
|
"back": "Documenti",
|
||||||
"share": "Condividi documento",
|
"copyLink": "Copia collegamento",
|
||||||
"linkCopied": "Link copiato negli appunti",
|
"linkCopied": "Link copiato negli appunti",
|
||||||
"download": "Scarica",
|
"download": "Scarica",
|
||||||
"previewUnavailable": "Anteprima non disponibile",
|
"previewUnavailable": "Anteprima non disponibile",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "ドキュメント",
|
"back": "ドキュメント",
|
||||||
"share": "ドキュメントを共有",
|
"copyLink": "リンクをコピー",
|
||||||
"linkCopied": "リンクをクリップボードにコピーしました",
|
"linkCopied": "リンクをクリップボードにコピーしました",
|
||||||
"download": "ダウンロード",
|
"download": "ダウンロード",
|
||||||
"previewUnavailable": "プレビューは利用できません",
|
"previewUnavailable": "プレビューは利用できません",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "문서",
|
"back": "문서",
|
||||||
"share": "문서 공유",
|
"copyLink": "링크 복사",
|
||||||
"linkCopied": "링크가 클립보드에 복사되었습니다",
|
"linkCopied": "링크가 클립보드에 복사되었습니다",
|
||||||
"download": "다운로드",
|
"download": "다운로드",
|
||||||
"previewUnavailable": "미리보기를 사용할 수 없습니다",
|
"previewUnavailable": "미리보기를 사용할 수 없습니다",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Dokumenty",
|
"back": "Dokumenty",
|
||||||
"share": "Udostępnij dokument",
|
"copyLink": "Kopiuj link",
|
||||||
"linkCopied": "Link skopiowany do schowka",
|
"linkCopied": "Link skopiowany do schowka",
|
||||||
"download": "Pobierz",
|
"download": "Pobierz",
|
||||||
"previewUnavailable": "Podgląd niedostępny",
|
"previewUnavailable": "Podgląd niedostępny",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Documentos",
|
"back": "Documentos",
|
||||||
"share": "Partilhar documento",
|
"copyLink": "Copiar ligação",
|
||||||
"linkCopied": "Link copiado para a área de transferência",
|
"linkCopied": "Link copiado para a área de transferência",
|
||||||
"download": "Transferir",
|
"download": "Transferir",
|
||||||
"previewUnavailable": "Pré-visualização indisponível",
|
"previewUnavailable": "Pré-visualização indisponível",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Belgeler",
|
"back": "Belgeler",
|
||||||
"share": "Belgeyi paylaş",
|
"copyLink": "Bağlantıyı kopyala",
|
||||||
"linkCopied": "Bağlantı panoya kopyalandı",
|
"linkCopied": "Bağlantı panoya kopyalandı",
|
||||||
"download": "İndir",
|
"download": "İndir",
|
||||||
"previewUnavailable": "Önizleme kullanılamıyor",
|
"previewUnavailable": "Önizleme kullanılamıyor",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "Документи",
|
"back": "Документи",
|
||||||
"share": "Поділитися документом",
|
"copyLink": "Копіювати посилання",
|
||||||
"linkCopied": "Посилання скопійовано до буфера обміну",
|
"linkCopied": "Посилання скопійовано до буфера обміну",
|
||||||
"download": "Завантажити",
|
"download": "Завантажити",
|
||||||
"previewUnavailable": "Попередній перегляд недоступний",
|
"previewUnavailable": "Попередній перегляд недоступний",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"back": "文档",
|
"back": "文档",
|
||||||
"share": "分享文档",
|
"copyLink": "复制链接",
|
||||||
"linkCopied": "链接已复制到剪贴板",
|
"linkCopied": "链接已复制到剪贴板",
|
||||||
"download": "下载",
|
"download": "下载",
|
||||||
"previewUnavailable": "无法预览",
|
"previewUnavailable": "无法预览",
|
||||||
|
|||||||
Reference in New Issue
Block a user