@dillingerstaffing/strand-vue 0.14.0 → 0.15.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/dist/components/CodeBlock/CodeBlock.vue.d.ts +7 -0
- package/dist/components/CodeBlock/CodeBlock.vue.d.ts.map +1 -1
- package/dist/components/InstrumentViewport/InstrumentViewport.vue.d.ts +5 -0
- package/dist/components/InstrumentViewport/InstrumentViewport.vue.d.ts.map +1 -1
- package/dist/css/strand-ui.css +2784 -223
- package/dist/index.js +365 -306
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/CodeBlock/CodeBlock.vue +78 -5
- package/src/components/InstrumentViewport/InstrumentViewport.test.ts +19 -0
- package/src/components/InstrumentViewport/InstrumentViewport.vue +6 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--! Strand Vue | MIT License | dillingerstaffing.com -->
|
|
2
2
|
<script setup lang="ts">
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, onBeforeUnmount, ref } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
/** The code content to display */
|
|
@@ -9,22 +9,95 @@ interface Props {
|
|
|
9
9
|
language?: string
|
|
10
10
|
/** Additional CSS class */
|
|
11
11
|
className?: string
|
|
12
|
+
/**
|
|
13
|
+
* Render the one-click copy-to-clipboard button. Defaults to true so
|
|
14
|
+
* every CodeBlock is copyable out of the box; pass false to opt out
|
|
15
|
+
* for blocks that should not advertise a copy affordance.
|
|
16
|
+
*/
|
|
17
|
+
copyable?: boolean
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
const COPIED_DURATION_MS = 1500
|
|
21
|
+
|
|
14
22
|
const props = withDefaults(defineProps<Props>(), {
|
|
15
23
|
className: '',
|
|
24
|
+
copyable: true,
|
|
16
25
|
})
|
|
17
26
|
|
|
18
27
|
const classes = computed(() =>
|
|
19
|
-
['strand-code-block', props.className]
|
|
20
|
-
.filter(Boolean)
|
|
21
|
-
.join(' '),
|
|
28
|
+
['strand-code-block', props.className].filter(Boolean).join(' '),
|
|
22
29
|
)
|
|
30
|
+
|
|
31
|
+
const copied = ref(false)
|
|
32
|
+
let timer: number | null = null
|
|
33
|
+
|
|
34
|
+
onBeforeUnmount(() => {
|
|
35
|
+
if (timer !== null) window.clearTimeout(timer)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
async function handleCopy() {
|
|
39
|
+
try {
|
|
40
|
+
if (navigator.clipboard?.writeText) {
|
|
41
|
+
await navigator.clipboard.writeText(props.code)
|
|
42
|
+
} else {
|
|
43
|
+
const ta = document.createElement('textarea')
|
|
44
|
+
ta.value = props.code
|
|
45
|
+
ta.setAttribute('readonly', '')
|
|
46
|
+
ta.style.position = 'absolute'
|
|
47
|
+
ta.style.left = '-9999px'
|
|
48
|
+
document.body.appendChild(ta)
|
|
49
|
+
ta.select()
|
|
50
|
+
document.execCommand('copy')
|
|
51
|
+
document.body.removeChild(ta)
|
|
52
|
+
}
|
|
53
|
+
copied.value = true
|
|
54
|
+
if (timer !== null) window.clearTimeout(timer)
|
|
55
|
+
timer = window.setTimeout(() => {
|
|
56
|
+
copied.value = false
|
|
57
|
+
}, COPIED_DURATION_MS)
|
|
58
|
+
} catch {
|
|
59
|
+
// Ignore copy failures.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
23
62
|
</script>
|
|
24
63
|
|
|
25
64
|
<template>
|
|
26
|
-
<div :class="classes" v-bind="$attrs">
|
|
65
|
+
<div :class="classes" :data-strand-copy="copyable ? '' : undefined" v-bind="$attrs">
|
|
27
66
|
<span v-if="language" class="strand-code-block__label">{{ language }}</span>
|
|
28
67
|
<pre class="strand-code-block__pre"><code>{{ code }}</code></pre>
|
|
68
|
+
<button
|
|
69
|
+
v-if="copyable"
|
|
70
|
+
type="button"
|
|
71
|
+
:class="['strand-code-block__copy', copied ? 'strand-code-block__copy--copied' : '']"
|
|
72
|
+
:aria-label="copied ? 'Copied' : 'Copy code to clipboard'"
|
|
73
|
+
@click="handleCopy"
|
|
74
|
+
>
|
|
75
|
+
<svg
|
|
76
|
+
class="strand-code-block__copy-icon strand-code-block__copy-icon--clipboard"
|
|
77
|
+
viewBox="0 0 16 16"
|
|
78
|
+
fill="none"
|
|
79
|
+
stroke="currentColor"
|
|
80
|
+
stroke-width="1.75"
|
|
81
|
+
stroke-linecap="round"
|
|
82
|
+
stroke-linejoin="round"
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
focusable="false"
|
|
85
|
+
>
|
|
86
|
+
<path d="M6 3 V2 a1 1 0 0 1 1-1 h2 a1 1 0 0 1 1 1 v1 M5 3 h6 a1 1 0 0 1 1 1 v9 a1 1 0 0 1 -1 1 h-6 a1 1 0 0 1 -1 -1 v-9 a1 1 0 0 1 1 -1 z" />
|
|
87
|
+
</svg>
|
|
88
|
+
<svg
|
|
89
|
+
class="strand-code-block__copy-icon strand-code-block__copy-icon--check"
|
|
90
|
+
viewBox="0 0 16 16"
|
|
91
|
+
fill="none"
|
|
92
|
+
stroke="currentColor"
|
|
93
|
+
stroke-width="1.75"
|
|
94
|
+
stroke-linecap="round"
|
|
95
|
+
stroke-linejoin="round"
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
focusable="false"
|
|
98
|
+
>
|
|
99
|
+
<path d="M3 8 l3 3 l7 -7" />
|
|
100
|
+
</svg>
|
|
101
|
+
</button>
|
|
29
102
|
</div>
|
|
30
103
|
</template>
|
|
@@ -37,6 +37,25 @@ describe('InstrumentViewport', () => {
|
|
|
37
37
|
expect(container.firstElementChild?.className).toContain('strand-instrument-viewport--grid')
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
// ── Full bleed modifier ──
|
|
41
|
+
|
|
42
|
+
it('does not apply full-bleed modifier by default', () => {
|
|
43
|
+
const { container } = render(InstrumentViewport, { slots: { default: 'Test' } })
|
|
44
|
+
expect(container.firstElementChild?.className).not.toContain(
|
|
45
|
+
'strand-instrument-viewport--full-bleed',
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('applies full-bleed modifier when fullBleed prop is true', () => {
|
|
50
|
+
const { container } = render(InstrumentViewport, {
|
|
51
|
+
props: { fullBleed: true },
|
|
52
|
+
slots: { default: 'Test' },
|
|
53
|
+
})
|
|
54
|
+
expect(container.firstElementChild?.className).toContain(
|
|
55
|
+
'strand-instrument-viewport--full-bleed',
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
40
59
|
// ── Custom className ──
|
|
41
60
|
|
|
42
61
|
it('merges custom className with component classes', () => {
|
|
@@ -5,12 +5,17 @@ import { computed } from 'vue'
|
|
|
5
5
|
interface Props {
|
|
6
6
|
/** Show grid overlay lines */
|
|
7
7
|
grid?: boolean
|
|
8
|
+
/** Render as page-filling instrument cabinet (DL Part 9.3 full-bleed mode).
|
|
9
|
+
* Requires the host page to apply `strand-body--instrument` to <body>
|
|
10
|
+
* so the dark surface reaches the screen edge. */
|
|
11
|
+
fullBleed?: boolean
|
|
8
12
|
/** Additional CSS class */
|
|
9
13
|
className?: string
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
const props = withDefaults(defineProps<Props>(), {
|
|
13
17
|
grid: false,
|
|
18
|
+
fullBleed: false,
|
|
14
19
|
className: '',
|
|
15
20
|
})
|
|
16
21
|
|
|
@@ -18,6 +23,7 @@ const classes = computed(() =>
|
|
|
18
23
|
[
|
|
19
24
|
'strand-instrument-viewport',
|
|
20
25
|
props.grid && 'strand-instrument-viewport--grid',
|
|
26
|
+
props.fullBleed && 'strand-instrument-viewport--full-bleed',
|
|
21
27
|
props.className,
|
|
22
28
|
]
|
|
23
29
|
.filter(Boolean)
|