• HTML

    code-block.html html

    <code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"><code-block collapsed language="html" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
    	<p class="meta">
    		<span class="file">code-block.html</span>
    		<span class="language">html</span>
    	</p>
    	<pre><code class="language-html"></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">
    			<span class="label">Copy</span>
    		</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block></code></pre>
    	<input-button class="copy">
    		<button type="button" class="secondary small">
    			<span class="label">Copy</span>
    		</button>
    	</input-button>
    	<button type="button" class="overlay">Expand</button>
    </code-block>
    CSS

    code-block.css css

    code-block {
    	position: relative;
    	display: block;
    	margin: 0 0 var(--space-l);
    
    	.meta {
    		display: flex;
    		margin-bottom: var(--space-xs);
    		font-size: var(--font-size-s);
    		color: var(--color-text-soft);
    
    		&:not(:has(.file)) .language {
    			margin-block-start: calc(-1 * var(--space-m));
    		}
    	}
    
    	.language {
    		margin-left: auto;
    		text-transform: uppercase;
    	}
    
    	& pre {
    		font-size: var(--font-size-s);
    		color: var(--color-gray-10);
    		background: var(--color-gray-90);
    		padding: var(--space-s);
    		margin: var(--space-xs) 0;
    		overflow: auto;
    		border-radius: var(--space-xs);
    	}
    
    	.copy {
    		position: absolute;
    		right: var(--space-s);
    		bottom: var(--space-s);
    	}
    
    	.overlay {
    		display: none;
    	}
    
    	&[collapsed] {
    		max-height: 12rem;
    		overflow: hidden;
    
    		&::after {
    			content: '';
    			display: block;
    			position: absolute;
    			bottom: 0;
    			width: 100%;
    			height: var(--space-m);
    			background: linear-gradient(-135deg, var(--color-secondary) 0.5rem, transparent 0) 0 0.5rem, linear-gradient(135deg, var(--color-secondary) 0.5rem, var(--color-background) 0) 0 0.5rem;
    			background-color: var(--color-secondary);
    			background-size: var(--space-m) var(--space-m);
    			background-position: bottom;
    		}
    
    		.copy {
    			display: none;
    		}
    
    		.overlay {
    			display: flex;
    			flex-direction: column-reverse;
    			align-items: center;
    			position: absolute;
    			bottom: 0;
    			left: 0;
    			width: 100%;
    			height: 6rem;
    			color: var(--color-text);
    			background: linear-gradient(transparent, var(--color-secondary));
    			border: 0;
    			cursor: pointer;
    			padding: var(--space-xs) var(--space-s);
    			margin-bottom: var(--space-m);
    			font-size: var(--font-size-s);
    			transition: background-color var(--transition-short) var(--easing-inout);
    			text-shadow: var(--color-background) 1px 0 var(--space-xs);
    
    			&:hover,
    			&:active {
    				text-shadow: var(--color-text-inverted) var(--space-xs) 0 var(--space-s);
    			}
    		}
    	}
    }
    TS

    code-block.ts ts

    import { asBoolean, toggleAttribute, UIElement } from '../../../'
    // import Prism from 'prismjs'
    // import 'prismjs/components/prism-bash';
    // import 'prismjs/components/prism-json';
    // import 'prismjs/components/prism-typescript';
    
    import type { InputButton } from '../input-button/input-button'
    
    export class CodeBlock extends UIElement<{ collapsed: boolean }> {
    	static readonly localName = 'code-block'
    	static observedAttributes = ['collapsed']
    
    	init = {
    		collapsed: asBoolean
    	}
    
      	connectedCallback() {
    
    		// Enhance code block with Prism.js
    		// const language = this.getAttribute('language') || 'html' 
    		const content = this.querySelector('code')
    		if (content) {
    			/* this.set('code', content.textContent?.trim(), false)
    			effect(() => {
    				// Apply syntax highlighting while preserving Lit's marker nodes in Storybook
    				const code = document.createElement('code')
    				code.innerHTML = Prism.highlight(
    					this.get('code') ?? '',
    					Prism.languages[language],
    					language
    				)
    				enqueue(() => {
    					Array.from(code.childNodes)
    						.filter(node => node.nodeType !== Node.COMMENT_NODE)
    						.forEach(node => node.remove())
    					Array.from(code.childNodes)
    						.forEach(node => code.appendChild(node))
    				}, [code, 'h'])
    			}) */
    
    			// Copy to clipboard
    			this.first('.copy').on('click', async (e: Event) => {
    				const copyButton = e.currentTarget as InputButton
    				const label = copyButton.textContent ?? ''
    				let status = 'success'
    				try {
    					await navigator.clipboard.writeText(content.textContent ?? '')
    				} catch (err) {
    					console.error('Error when trying to use navigator.clipboard.writeText()', err)
    					status = 'error'
    				}
    				copyButton.set('disabled', true)
    				copyButton.set('label', this.getAttribute(`copy-${status}`) ?? label)
    				setTimeout(() => {
    					copyButton.set('disabled', false)
    					copyButton.set('label', label)
    				}, status === 'success' ? 1000 : 3000)
    			})
    
    			// Expand
    			this.first('.overlay').on('click', () => this.set('collapsed', false))
    			this.self.sync(toggleAttribute('collapsed'))
    		}
    	}
    }
    CodeBlock.define()