Custom Modal Dialogs

htmx๋Š” Bootstrap์ด๋‚˜ UIKit๊ณผ ๊ฐ™์€ CSS ํ”„๋ ˆ์ž„์›Œํฌ์— ๋‚ด์žฅ๋œ ๋‹ค์ด์–ผ๋กœ๊ทธ์™€ ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ, htmx๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋‹ฌ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ๋Š” ๋ชจ๋‹ฌ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๋งŒ๋“œ๋Š” ํ•œ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ๊ฐ„๋‹จํžˆ ์˜ˆ์‹œ๋กœ ๋ณด์—ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ตœ์ข… ๊ฒฐ๊ณผ์˜ ๋ฐ๋ชจ๋ฅผ ๋ณด๋ ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•˜์„ธ์š”:

<button class="btn primary" hx-get="/modal" hx-target="body" hx-swap="beforeend">Open a Modal</button>

#์ „๋ฐ˜์ ์ธ ๊ณ„ํš

์šฐ๋ฆฌ๋Š” ์„œ๋ฒ„์—์„œ ์›๊ฒฉ ์ฝ˜ํ…์ธ ๋ฅผ ๋กœ๋“œํ•œ ๋‹ค์Œ, ์ด๋ฅผ ๋ชจ๋‹ฌ ๋‹ค์ด์–ผ๋กœ๊ทธ์— ํ‘œ์‹œํ•˜๋Š” ๋ฒ„ํŠผ์„ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋‹ฌ ์ฝ˜ํ…์ธ ๋Š” <body> ์š”์†Œ์˜ ๋์— #modal์ด๋ผ๋Š” ์ด๋ฆ„์˜ div์— ์ถ”๊ฐ€๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด ๋ฐ๋ชจ์—์„œ๋Š” CSS๋กœ ๋ฉ‹์ง„ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ •์˜ํ•œ ๋‹ค์Œ, ์‚ฌ์šฉ์ž๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ๋ชจ๋‹ฌ์„ DOM์—์„œ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด Hyperscript๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. htmx์™€ ํ•จ๊ป˜ Hyperscript๋ฅผ ๋ฐ˜๋“œ์‹œ ์‚ฌ์šฉํ•  ํ•„์š”๋Š” ์—†์ง€๋งŒ, ๋‘ ๊ฐ€์ง€๋Š” ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ ๋น„๋™๊ธฐ ๋ฐ ์ด๋ฒคํŠธ ์ค‘์‹ฌ์˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐ JavaScript๋ณด๋‹ค ํ›จ์”ฌ ๋” ํŽธ๋ฆฌํ•˜๋ฏ€๋กœ ์ด ์˜ˆ์‹œ์—์„œ๋Š” Hyperscript๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

#Main Page HTML

<button class="btn primary" hx-get="/modal" hx-target="body" hx-swap="beforeend">Open a Modal</button>
<div id="modal" _="on closeModal add .closing then wait for animationend then remove me">
	<div class="modal-underlay" _="on click trigger closeModal"></div>
	<div class="modal-content">
		<h1>Modal Dialog</h1>
		This is the modal content.
		You can put anything here, like text, or a form, or an image.
		<br>
		<br>
		<button class="btn danger" _="on click trigger closeModal">Close</button>
	</div>
</div>

#Custom Stylesheet

/***** MODAL DIALOG ****/
#modal {
	/* Underlay covers entire screen. */
	position: fixed;
	top:0px;
	bottom: 0px;
	left:0px;
	right:0px;
	background-color:rgba(0,0,0,0.5);
	z-index:1000;

	/* Flexbox centers the .modal-content vertically and horizontally */
	display:flex;
	flex-direction:column;
	align-items:center;

	/* Animate when opening */
	animation-name: fadeIn;
	animation-duration:150ms;
	animation-timing-function: ease;
}

#modal > .modal-underlay {
	/* underlay takes up the entire viewport. This is only
	required if you want to click to dismiss the popup */
	position: absolute;
	z-index: -1;
	top:0px;
	bottom:0px;
	left: 0px;
	right: 0px;
}

#modal > .modal-content {
	/* Position visible dialog near the top of the window */
	margin-top:10vh;

	/* Sizing for visible dialog */
	width:80%;
	max-width:600px;

	/* Display properties for visible dialog*/
	border:solid 1px #999;
	border-radius:8px;
	box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.3);
	background-color:white;
	padding:20px;

	/* Animate when opening */
	animation-name:zoomIn;
	animation-duration:150ms;
	animation-timing-function: ease;
}

#modal.closing {
	/* Animate when closing */
	animation-name: fadeOut;
	animation-duration:150ms;
	animation-timing-function: ease;
}

#modal.closing > .modal-content {
	/* Animate when closing */
	animation-name: zoomOut;
	animation-duration:150ms;
	animation-timing-function: ease;
}

@keyframes fadeIn {
	0% {opacity: 0;}
	100% {opacity: 1;}
}

@keyframes fadeOut {
	0% {opacity: 1;}
	100% {opacity: 0;}
}

@keyframes zoomIn {
	0% {transform: scale(0.9);}
	100% {transform: scale(1);}
}

@keyframes zoomOut {
	0% {transform: scale(1);}
	100% {transform: scale(0.9);}
}