@stasho/ds 0.6.1 → 0.7.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/package.json
CHANGED
|
@@ -7,25 +7,28 @@ import {
|
|
|
7
7
|
useRef,
|
|
8
8
|
useState,
|
|
9
9
|
type HTMLAttributes,
|
|
10
|
+
type ReactNode,
|
|
10
11
|
} from "react";
|
|
11
12
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
12
13
|
import { ArrowUpRight, Copy } from "@phosphor-icons/react";
|
|
13
14
|
import { cn } from "@ac/lib/cn";
|
|
14
15
|
|
|
15
|
-
const copyableTextVariants = cva(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
sm: "text-xs gap-1",
|
|
21
|
-
md: "text-sm gap-1.5",
|
|
22
|
-
},
|
|
16
|
+
const copyableTextVariants = cva("items-center font-mono select-none", {
|
|
17
|
+
variants: {
|
|
18
|
+
size: {
|
|
19
|
+
sm: "text-xs gap-1",
|
|
20
|
+
md: "text-sm gap-1.5",
|
|
23
21
|
},
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
fluid: {
|
|
23
|
+
true: "flex w-full",
|
|
24
|
+
false: "inline-flex",
|
|
26
25
|
},
|
|
27
26
|
},
|
|
28
|
-
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
size: "md",
|
|
29
|
+
fluid: false,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
29
32
|
|
|
30
33
|
const iconSize: Record<"sm" | "md", string> = {
|
|
31
34
|
sm: "size-3.5",
|
|
@@ -50,6 +53,26 @@ function truncateMiddle(
|
|
|
50
53
|
return `${text.slice(0, startChars)}...${text.slice(-endChars)}`;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
function renderTextContent(
|
|
57
|
+
text: string,
|
|
58
|
+
fluid: boolean,
|
|
59
|
+
startChars: number,
|
|
60
|
+
endChars: number,
|
|
61
|
+
): ReactNode {
|
|
62
|
+
if (!fluid) return truncateMiddle(text, startChars, endChars);
|
|
63
|
+
if (text.length <= endChars) return text;
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<span className="min-w-0 flex-initial overflow-hidden text-ellipsis whitespace-nowrap">
|
|
67
|
+
{text.slice(0, -endChars)}
|
|
68
|
+
</span>
|
|
69
|
+
<span className="flex-none whitespace-nowrap">
|
|
70
|
+
{text.slice(-endChars)}
|
|
71
|
+
</span>
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
type CopyableTextProps = Omit<HTMLAttributes<HTMLSpanElement>, "children"> &
|
|
54
77
|
VariantProps<typeof copyableTextVariants> & {
|
|
55
78
|
text: string;
|
|
@@ -66,6 +89,7 @@ const CopyableText = forwardRef<HTMLSpanElement, CopyableTextProps>(
|
|
|
66
89
|
endChars = 4,
|
|
67
90
|
href,
|
|
68
91
|
size = "md",
|
|
92
|
+
fluid = false,
|
|
69
93
|
className,
|
|
70
94
|
...rest
|
|
71
95
|
},
|
|
@@ -80,24 +104,30 @@ const CopyableText = forwardRef<HTMLSpanElement, CopyableTextProps>(
|
|
|
80
104
|
};
|
|
81
105
|
}, []);
|
|
82
106
|
|
|
83
|
-
const handleCopy = useCallback(
|
|
84
|
-
e.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
const handleCopy = useCallback(
|
|
108
|
+
(e: React.MouseEvent) => {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
void navigator.clipboard.writeText(text).then(() => {
|
|
111
|
+
setCopied(true);
|
|
112
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
113
|
+
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
[text],
|
|
117
|
+
);
|
|
91
118
|
|
|
92
119
|
const resolvedSize = size ?? "md";
|
|
93
120
|
const iconCn = iconSize[resolvedSize];
|
|
94
121
|
const btnCn = buttonSize[resolvedSize];
|
|
122
|
+
const isFluid = fluid ?? false;
|
|
123
|
+
const content = renderTextContent(text, isFluid, startChars, endChars);
|
|
124
|
+
const titleAttr = isFluid ? text : undefined;
|
|
95
125
|
|
|
96
126
|
return (
|
|
97
127
|
<span
|
|
98
128
|
ref={ref}
|
|
99
129
|
className={cn(
|
|
100
|
-
copyableTextVariants({ size }),
|
|
130
|
+
copyableTextVariants({ size, fluid }),
|
|
101
131
|
href && "text-accent-500 dark:text-accent",
|
|
102
132
|
className,
|
|
103
133
|
)}
|
|
@@ -106,62 +136,69 @@ const CopyableText = forwardRef<HTMLSpanElement, CopyableTextProps>(
|
|
|
106
136
|
{href ? (
|
|
107
137
|
<a
|
|
108
138
|
href={href}
|
|
139
|
+
title={titleAttr}
|
|
109
140
|
{...(isExternalUrl(href)
|
|
110
141
|
? { target: "_blank", rel: "noopener noreferrer" }
|
|
111
142
|
: {})}
|
|
112
143
|
onClick={(e) => e.stopPropagation()}
|
|
113
|
-
className=
|
|
144
|
+
className={cn(
|
|
145
|
+
"cursor-pointer hover:underline",
|
|
146
|
+
isFluid && "flex min-w-0 flex-1 overflow-hidden",
|
|
147
|
+
)}
|
|
114
148
|
>
|
|
115
|
-
{
|
|
149
|
+
{content}
|
|
116
150
|
</a>
|
|
117
151
|
) : (
|
|
118
|
-
<span
|
|
119
|
-
{
|
|
152
|
+
<span
|
|
153
|
+
title={titleAttr}
|
|
154
|
+
className={cn("cursor-default", isFluid && "flex min-w-0 flex-1")}
|
|
155
|
+
>
|
|
156
|
+
{content}
|
|
120
157
|
</span>
|
|
121
158
|
)}
|
|
122
159
|
|
|
123
160
|
<button
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
type="button"
|
|
162
|
+
onClick={handleCopy}
|
|
163
|
+
className={cn(
|
|
164
|
+
"relative inline-flex items-center justify-center",
|
|
165
|
+
"rounded-none cursor-pointer shrink-0",
|
|
166
|
+
"hover:bg-foreground/10 transition-colors",
|
|
167
|
+
btnCn,
|
|
168
|
+
)}
|
|
169
|
+
aria-label={copied ? "Copied" : "Copy to clipboard"}
|
|
170
|
+
>
|
|
171
|
+
<Copy
|
|
172
|
+
weight="bold"
|
|
126
173
|
className={cn(
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
|
|
174
|
+
iconCn,
|
|
175
|
+
"text-muted-foreground",
|
|
176
|
+
"transition-opacity duration-100",
|
|
177
|
+
"motion-reduce:transition-none",
|
|
178
|
+
copied && "opacity-0",
|
|
179
|
+
)}
|
|
180
|
+
aria-hidden="true"
|
|
181
|
+
/>
|
|
182
|
+
<svg
|
|
183
|
+
viewBox="0 0 24 24"
|
|
184
|
+
fill="none"
|
|
185
|
+
stroke="currentColor"
|
|
186
|
+
strokeWidth={3}
|
|
187
|
+
strokeLinecap="round"
|
|
188
|
+
strokeLinejoin="round"
|
|
189
|
+
className={cn(
|
|
190
|
+
iconCn,
|
|
191
|
+
"text-muted-foreground absolute",
|
|
192
|
+
"[stroke-dasharray:20] [stroke-dashoffset:20]",
|
|
193
|
+
"transition-[stroke-dashoffset] duration-300 delay-75 ease-out",
|
|
194
|
+
"motion-reduce:transition-none",
|
|
195
|
+
copied && "[stroke-dashoffset:0]",
|
|
131
196
|
)}
|
|
132
|
-
aria-
|
|
197
|
+
aria-hidden="true"
|
|
133
198
|
>
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
iconCn,
|
|
138
|
-
"text-muted-foreground",
|
|
139
|
-
"transition-opacity duration-100",
|
|
140
|
-
"motion-reduce:transition-none",
|
|
141
|
-
copied && "opacity-0",
|
|
142
|
-
)}
|
|
143
|
-
aria-hidden="true"
|
|
144
|
-
/>
|
|
145
|
-
<svg
|
|
146
|
-
viewBox="0 0 24 24"
|
|
147
|
-
fill="none"
|
|
148
|
-
stroke="currentColor"
|
|
149
|
-
strokeWidth={3}
|
|
150
|
-
strokeLinecap="round"
|
|
151
|
-
strokeLinejoin="round"
|
|
152
|
-
className={cn(
|
|
153
|
-
iconCn,
|
|
154
|
-
"text-muted-foreground absolute",
|
|
155
|
-
"[stroke-dasharray:20] [stroke-dashoffset:20]",
|
|
156
|
-
"transition-[stroke-dashoffset] duration-300 delay-75 ease-out",
|
|
157
|
-
"motion-reduce:transition-none",
|
|
158
|
-
copied && "[stroke-dashoffset:0]",
|
|
159
|
-
)}
|
|
160
|
-
aria-hidden="true"
|
|
161
|
-
>
|
|
162
|
-
<polyline points="4 12 9 17 20 6" />
|
|
163
|
-
</svg>
|
|
164
|
-
</button>
|
|
199
|
+
<polyline points="4 12 9 17 20 6" />
|
|
200
|
+
</svg>
|
|
201
|
+
</button>
|
|
165
202
|
|
|
166
203
|
{href && isExternalUrl(href) ? (
|
|
167
204
|
<a
|
|
@@ -170,18 +207,14 @@ const CopyableText = forwardRef<HTMLSpanElement, CopyableTextProps>(
|
|
|
170
207
|
rel="noopener noreferrer"
|
|
171
208
|
onClick={(e) => e.stopPropagation()}
|
|
172
209
|
className={cn(
|
|
173
|
-
"inline-flex items-center justify-center rounded-none",
|
|
210
|
+
"inline-flex items-center justify-center rounded-none shrink-0",
|
|
174
211
|
"text-muted-foreground hover:text-foreground",
|
|
175
212
|
"hover:bg-foreground/10 transition-colors",
|
|
176
213
|
btnCn,
|
|
177
214
|
)}
|
|
178
215
|
aria-label="Open in new tab"
|
|
179
216
|
>
|
|
180
|
-
<ArrowUpRight
|
|
181
|
-
weight="bold"
|
|
182
|
-
className={iconCn}
|
|
183
|
-
aria-hidden="true"
|
|
184
|
-
/>
|
|
217
|
+
<ArrowUpRight weight="bold" className={iconCn} aria-hidden="true" />
|
|
185
218
|
</a>
|
|
186
219
|
) : null}
|
|
187
220
|
</span>
|