midcut 0.0.1 → 0.1.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 +51 -0
- package/dist/Midcut.d.mts +21 -0
- package/dist/Midcut.mjs +67 -0
- package/package.json +54 -3
- package/src/Midcut.tsx +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# midcut
|
|
2
|
+
|
|
3
|
+
A <1kb React component that accurately middle-truncates monospaced text. Uses CSS `round()` and container queries (no JS measurement).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -S midcut
|
|
9
|
+
pnpm add midcut
|
|
10
|
+
bun add midcut
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Midcut } from "midcut";
|
|
17
|
+
|
|
18
|
+
function App() {
|
|
19
|
+
return <Midcut prefix="0x" value="0x1234567890abcdef1234567890abcdef12345678" />;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Important:** only use with a monospace font.
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
| ---------- | ------------------ | --------- | ------------------------------------------------------- |
|
|
29
|
+
| `value` | `string` | `""` | The string to truncate |
|
|
30
|
+
| `prefix` | `string` | `""` | A prefix that is always visible (e.g. `"0x"`) |
|
|
31
|
+
| `min` | `number` | `1` | Minimum characters visible on each side of the ellipsis |
|
|
32
|
+
| `ellipsis` | `string` | `"…"` | The ellipsis character(s) |
|
|
33
|
+
| `align` | `"start" \| "end"` | `"start"` | Alignment of the truncated string within its container |
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
1. The string is split into two halves displayed as inline flex items
|
|
38
|
+
2. A CSS container query activates truncation when the container is narrower than the full string
|
|
39
|
+
3. `round(down, ..., 2ch)` snaps the truncatable width to character boundaries so characters are never partially clipped
|
|
40
|
+
4. The end half uses `justify-content: flex-end` to truncate from the start
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun install
|
|
46
|
+
bun dev
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/Midcut.d.ts
|
|
4
|
+
declare function Midcut({
|
|
5
|
+
align,
|
|
6
|
+
ellipsis,
|
|
7
|
+
min,
|
|
8
|
+
prefix,
|
|
9
|
+
value
|
|
10
|
+
}: Midcut.Props): string | _$react_jsx_runtime0.JSX.Element;
|
|
11
|
+
declare namespace Midcut {
|
|
12
|
+
interface Props {
|
|
13
|
+
align?: "start" | "end";
|
|
14
|
+
ellipsis?: string;
|
|
15
|
+
min?: number;
|
|
16
|
+
prefix?: string;
|
|
17
|
+
value?: string;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { Midcut };
|
package/dist/Midcut.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/Midcut.tsx
|
|
4
|
+
function Midcut({ align = "start", ellipsis = "…", min = 1, prefix = "", value = "" }) {
|
|
5
|
+
const id = useId();
|
|
6
|
+
const body = value.slice(value.startsWith(prefix) ? prefix.length : 0);
|
|
7
|
+
if (body.length < 2) return prefix + ellipsis;
|
|
8
|
+
const cutAt = 1 + Math.ceil((body.length - 1) / 2);
|
|
9
|
+
const [start, end] = [body.slice(1, cutAt), body.slice(cutAt, -1)];
|
|
10
|
+
const minWidth = prefix.length + min * 2 + 1;
|
|
11
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
12
|
+
id,
|
|
13
|
+
title: value,
|
|
14
|
+
style: {
|
|
15
|
+
display: "inline-flex",
|
|
16
|
+
justifyContent: align === "end" ? "flex-end" : void 0,
|
|
17
|
+
width: "100%",
|
|
18
|
+
minWidth: `${minWidth}ch`,
|
|
19
|
+
textDecoration: "inherit",
|
|
20
|
+
containerType: "inline-size",
|
|
21
|
+
containerName: id
|
|
22
|
+
},
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
25
|
+
.${id}-ellipsis { display: none }
|
|
26
|
+
@container ${id} (max-width: ${value.length + 1}ch) {
|
|
27
|
+
.${id}-ellipsis { display: flex }
|
|
28
|
+
.${id}-part {
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
width: calc((100cqw - ${prefix.length + 3}ch) / 2);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
` }),
|
|
34
|
+
/* @__PURE__ */ jsxs("span", {
|
|
35
|
+
style: {
|
|
36
|
+
display: "inline-flex",
|
|
37
|
+
maxWidth: `${value.length - 1}ch`,
|
|
38
|
+
width: "round(down, calc(100% - 1ch), 2ch)",
|
|
39
|
+
whiteSpace: "nowrap"
|
|
40
|
+
},
|
|
41
|
+
children: [
|
|
42
|
+
prefix,
|
|
43
|
+
body.at(0),
|
|
44
|
+
/* @__PURE__ */ jsx("span", {
|
|
45
|
+
className: `${id}-part`,
|
|
46
|
+
children: start
|
|
47
|
+
}),
|
|
48
|
+
/* @__PURE__ */ jsx("span", {
|
|
49
|
+
className: `${id}-ellipsis`,
|
|
50
|
+
children: ellipsis
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ jsx("span", {
|
|
53
|
+
className: `${id}-part`,
|
|
54
|
+
style: {
|
|
55
|
+
display: "flex",
|
|
56
|
+
justifyContent: "flex-end"
|
|
57
|
+
},
|
|
58
|
+
children: end
|
|
59
|
+
})
|
|
60
|
+
]
|
|
61
|
+
}),
|
|
62
|
+
body.at(-1)
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { Midcut };
|
package/package.json
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "midcut",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"
|
|
5
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A <1kb React component that accurately middle-truncates monospaced text",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"css",
|
|
7
|
+
"ellipsis",
|
|
8
|
+
"middle",
|
|
9
|
+
"monospace",
|
|
10
|
+
"react",
|
|
11
|
+
"truncate"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": "tempoxyz/midcut",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src/Midcut.tsx"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "dist/Midcut.mjs",
|
|
21
|
+
"module": "dist/Midcut.mjs",
|
|
22
|
+
"types": "dist/Midcut.d.mts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/Midcut.d.mts",
|
|
26
|
+
"import": "./dist/Midcut.mjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "vp dev",
|
|
31
|
+
"build": "vp pack src/Midcut.tsx --dts --tsconfig tsconfig.app.json",
|
|
32
|
+
"build:demo": "vp build --outDir dist/client && vp build --outDir dist/server --ssr entry-server.tsx && bun scripts/prerender.ts",
|
|
33
|
+
"lint": "vp lint .",
|
|
34
|
+
"preview": "vp preview",
|
|
35
|
+
"prepublishOnly": "bun run build",
|
|
36
|
+
"prepare": "vp config"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^19.2.5",
|
|
40
|
+
"@types/react-dom": "^19.2.3",
|
|
41
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
42
|
+
"react": "^19.2.0",
|
|
43
|
+
"react-dom": "^19.2.0",
|
|
44
|
+
"typescript": "~5.9.3",
|
|
45
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
46
|
+
"vite-plus": "latest"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"overrides": {
|
|
52
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
53
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
54
|
+
},
|
|
55
|
+
"packageManager": "bun@1.3.11"
|
|
56
|
+
}
|
package/src/Midcut.tsx
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
|
|
3
|
+
export function Midcut({
|
|
4
|
+
align = "start",
|
|
5
|
+
ellipsis = "…",
|
|
6
|
+
min = 1,
|
|
7
|
+
prefix = "",
|
|
8
|
+
value = "",
|
|
9
|
+
}: Midcut.Props) {
|
|
10
|
+
const id = useId();
|
|
11
|
+
|
|
12
|
+
const body = value.slice(value.startsWith(prefix) ? prefix.length : 0);
|
|
13
|
+
if (body.length < 2) return prefix + ellipsis;
|
|
14
|
+
|
|
15
|
+
const cutAt = 1 + Math.ceil((body.length - 1) / 2);
|
|
16
|
+
const [start, end] = [body.slice(1, cutAt), body.slice(cutAt, -1)];
|
|
17
|
+
|
|
18
|
+
const minWidth = prefix.length + min * 2 + 1;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
id={id}
|
|
23
|
+
title={value}
|
|
24
|
+
style={{
|
|
25
|
+
display: "inline-flex",
|
|
26
|
+
justifyContent: align === "end" ? "flex-end" : undefined,
|
|
27
|
+
width: "100%",
|
|
28
|
+
minWidth: `${minWidth}ch`,
|
|
29
|
+
textDecoration: "inherit",
|
|
30
|
+
containerType: "inline-size",
|
|
31
|
+
containerName: id,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<style>
|
|
35
|
+
{`
|
|
36
|
+
.${id}-ellipsis { display: none }
|
|
37
|
+
@container ${id} (max-width: ${value.length + 1}ch) {
|
|
38
|
+
.${id}-ellipsis { display: flex }
|
|
39
|
+
.${id}-part {
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
width: calc((100cqw - ${prefix.length + 3}ch) / 2);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
`}
|
|
45
|
+
</style>
|
|
46
|
+
<span
|
|
47
|
+
style={{
|
|
48
|
+
display: "inline-flex",
|
|
49
|
+
maxWidth: `${value.length - 1}ch`,
|
|
50
|
+
width: "round(down, calc(100% - 1ch), 2ch)",
|
|
51
|
+
whiteSpace: "nowrap",
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{prefix}
|
|
55
|
+
{body.at(0)}
|
|
56
|
+
<span className={`${id}-part`}>{start}</span>
|
|
57
|
+
<span className={`${id}-ellipsis`}>{ellipsis}</span>
|
|
58
|
+
<span
|
|
59
|
+
className={`${id}-part`}
|
|
60
|
+
style={{
|
|
61
|
+
display: "flex",
|
|
62
|
+
justifyContent: "flex-end",
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{end}
|
|
66
|
+
</span>
|
|
67
|
+
</span>
|
|
68
|
+
{body.at(-1)}
|
|
69
|
+
</span>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export namespace Midcut {
|
|
74
|
+
export interface Props {
|
|
75
|
+
align?: "start" | "end";
|
|
76
|
+
ellipsis?: string;
|
|
77
|
+
min?: number;
|
|
78
|
+
prefix?: string;
|
|
79
|
+
value?: string;
|
|
80
|
+
}
|
|
81
|
+
}
|