[WebUI]Tailwind + nextjs(card) โ€“ 2

๐Ÿ‘‰๐Ÿป ์นด๋“œ๋Š” ์›น ์•ฑ์ด๋‚˜ ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ ๋งค์šฐ ๋งŽ์ด ์“ฐ์ด๋Š” ์š”์†Œ์ž…๋‹ˆ๋‹ค.
Cards are a widely used element in web and mobile apps.

๐Ÿ‘‰๐Ÿป ๋ฐ์ดํ„ฐ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•  ๋•Œ ์นด๋“œ ์š”์†Œ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์•„ ํ™”๋ฉด์— ํ‘œํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งค์šฐ ๋งŽ์Šต๋‹ˆ๋‹ค.
When displaying data on the screen, it is very common to present it using card elements.

๐Ÿ‘‰๐Ÿป tailwind๋Š” ์•„๋ž˜์˜ ์›น์‚ฌ์ดํŠธ์˜ ๊ฒ€์ƒ‰์„ ํ†ตํ•ด์„œ ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
You can find detailed information about Tailwind by searching on the website below.

https://tailwindcss.com/docs/installation/using-vite

๐Ÿ‘‰๐Ÿป ์ „์ฒด ์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
You can find the full code on GitHub.

https://github.com/gideonslife01/flm-webui


๐Ÿ“ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ / Typescript

โœ”๏ธ page.tsx๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
page.tsx uses TypeScript.

โœ”๏ธ ๋งŒ์•ฝ์— ์—๋””ํ„ฐ๋กœ ์ฝ”๋“œ ์˜คํ”ˆ์‹œ ํƒœ๊ทธ ๋ถ€๋ถ„์— ๋นจ๊ฐ„ ์ค„์ด ์ƒ๊ธฐ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
If a red line appears under the tag section when opening the code in the editor, install the module as shown below.

โœ”๏ธ vscode๋‚˜ ์—๋””ํ„ฐ๊ฐ€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋‹ค์Œ์ฒ˜๋Ÿผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.
Install the following so that VS Code or your editor can recognize TypeScript.

npm install -D @types/react @types/node

๐Ÿ“ ํƒœ๊ทธ ๊ตฌ์กฐ / Tag structure

โœ”๏ธ ์นด๋“œ ๋ถ€๋ถ„์˜ HTML๊ธฐ๋ณธ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
The basic HTML structure for the card section is as follows.

โœ”๏ธ ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•˜๊ฒŒ๋ด์•ผํ•˜๋Š”๊ฑด ๊ฐ ์š”์†Œ(div,p,button…)๋“ค์˜ ๋ฐฐ์น˜ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.
The key aspect to focus on here is the layout of the individual elements (div, p, button, etc.).

    <main className="min-h-screen flex items-center justify-center bg-gray-50 p-4">

      {/* ์นด๋“œ ์‹œ์ž‘ / Start Card */}
      <div className="w-full max-w-sm bg-white rounded-2xl shadow-lg overflow-hidden">

        {/* ์ด๋ฏธ์ง€ ์˜์—ญ / Image Area */}
        <div className="h-48 bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center">
          <span className="text-5xl">๐Ÿš€</span>
        </div>

        {/* ํ…์ŠคํŠธ ์˜์—ญ / Text area */}
        <div className="p-6">
          <div className="flex items-center justify-between">
            <h2 className="text-xl font-bold text-gray-900">Freelifemakers</h2>
            <span className="px-2 py-1 text-xs font-bold bg-blue-100 text-blue-600 rounded-full">NEW</span>
          </div>
          <p className="mt-2 text-sm text-gray-400">
            Next.js + Tailwind๋กœ ๋งŒ๋“  ์†Œ๊ฐœ ์นด๋“œ์ž…๋‹ˆ๋‹ค.<br></br>
            This is an introductory card built with Next.js and Tailwind.
          </p>

          {/* ๋ฒ„ํŠผ / Button */}
          <div className="mt-6 flex gap-3">
            <button
              onClick={() => setLiked(!liked)}
              className={`flex-1 py-3 rounded-xl font-bold transition-all active:scale-95 ${liked ? 'bg-red-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}
            >
              {liked ? 'โค๏ธ ์ข‹์•„์š”/like' : '๐Ÿค ์ข‹์•„์š”/like'}
            </button>
            <button className="flex-1 py-3 bg-blue-500 text-white rounded-xl font-bold shadow-md hover:bg-blue-600 hover:-translate-y-0.5 transition-all active:scale-95">
              ์‹œ์ž‘ํ•˜๊ธฐ / Start
            </button>
          </div>
          
        </div>
      </div>
      {/* ์นด๋“œ ๋ / card end */}
    </main>

๐Ÿ“ ๊ฐ€๋กœ๋ฐฐ์น˜ / Horizontal layout

โœ”๏ธ ๊ฐ ์š”์†Œ๋“ค์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ธ๋กœ๋กœ ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค.
Each element is arranged vertically by default.

โœ”๏ธ ์š”์†Œ๋“ค์˜ ๋ฐฐ์น˜๋ฅผ ์ฝ”๋“œ๋กœ ์กฐ์ •ํ•˜๋ ค๋ฉด flex๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
Use flex to adjust the layout of elements via code.

โœ”๏ธ ์นด๋“œ๊ฐ€ ์‹œ์ž‘๋˜๋Š” ๋ถ€๋ถ„์— flex๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๊ธฐ๋ณธ ๊ฐ’์œผ๋กœ ๊ฐ ์š”์†Œ๋Š” ์„ธ๋กœ๋กœ ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค.
This is a case where flex is not used at the start of the card. In this instance, the elements are arranged vertically by default.

      {/* ์นด๋“œ ์‹œ์ž‘ / Start Card */}
      <div className="w-full max-w-sm bg-white rounded-2xl shadow-lg overflow-hidden">
              ... ...
      </div>
default

โœ”๏ธ flex๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ๊ฐ€ ๊ฐ€๋กœ ๋ฐฐ์น˜๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

      {/* ์นด๋“œ ์‹œ์ž‘ / Start Card */}
      <div className="w-full flex max-w-sm bg-white rounded-2xl shadow-lg overflow-hidden">
              ... ...
      </div>
Horizontal layout

๐Ÿ“ ์„ธ๋กœ๋ฐฐ์น˜ / Vertical layout

โœ”๏ธ ๊ธฐ๋ณธ๊ฐ’์€ ์„ธ๋กœ ๋ฐฐ์น˜์ง€๋งŒ ์ •๋ ฌ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ flex-col๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

โœ”๏ธ ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฒ„ํŠผ ๋ถ€๋ถ„์— ์•„๋ž˜ ์ฒ˜๋Ÿผ flex-1๋กœ ์ ์šฉํ•˜๊ณ  <div className=”mt-6 flex gap-3″> ์ด๋ ‡๊ฒŒ ์ ์šฉํ•˜๋ฉด ๋ ˆ์ด์•„์›ƒ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โœ”๏ธ flex-1 ๊ฐ€๋กœ ๋ชจ๋“œ์—์„œ ์•„์ดํ…œ์ด ์ฐจ์ง€ํ•˜๋Š” ๊ณต๊ฐ„์ด 1:1 ๋น„์œจ๋กœ ๋ฐฐ์น˜ํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

 <button
       onClick={() => setLiked(!liked)}
       className={`flex-1 py-3 rounded-xl ... ... > ... ... </button>

โœ”๏ธ ๊ทธ๋ž˜์„œ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. w-full์€ ์š”์†Œ๊ฐ€ ์ „์ฒด ๊ณต๊ฐ„์„ ์ฐจ์ง€ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

โœ”๏ธ ๋ฒ„ํŠผ์ด ๋‘๊ฐœ ์žˆ๋‹ค๋ฉด ๊ฐ€๋กœ๋ชจ๋“œ์—์„œ w-full์„ ์ ์šฉํ•˜๋ฉด 1:1๋น„์œจ๋กœ ๋ฐฐ์น˜๋˜๊ณ  ์„ธ๋กœ ๋ชจ๋“œ์—์„œ๋Š” ์ „์ฒด ๊ณต๊ฐ„์„ ์ฐจ์ง€ ํ•ฉ๋‹ˆ๋‹ค.

   <button
        onClick={() => setLiked(!liked)}
        className={`w-full py-3 rounded-xl ... ...> ... ... </button?

โœ”๏ธ ์•„๋ž˜๋Š” flex-col์„ ์ด์šฉํ•ด์„œ ์„ธ๋กœ ๋ฐฐ์น˜๋ฅผ ์ ์šฉํ•œ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.

— ์ฝ”๋“œ / Code

       {/* ๋ฒ„ํŠผ / Button */}
          <div className="mt-6 flex-col gap-3">
            <button
              onClick={() => setLiked(!liked)}
              className={`w-full py-3 rounded-xl font-bold transition-all active:scale-95 ${liked ? 'bg-red-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}
            >
              {liked ? 'โค๏ธ ์ข‹์•„์š”/like' : '๐Ÿค ์ข‹์•„์š”/like'}
            </button>
            <button className="w-full py-3 bg-blue-500 text-white rounded-xl font-bold shadow-md hover:bg-blue-600 hover:-translate-y-0.5 transition-all active:scale-95">
              ์‹œ์ž‘ํ•˜๊ธฐ / Start
            </button>
          </div>

       {/* ๋ฒ„ํŠผ flex / Button flex *}
          <div className="mt-6 flex">
            <button
              onClick={() => setLiked(!liked)}
              className={`w-full py-3  ${liked ? 'bg-red-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}
            >
              {liked ? 'โค๏ธ ์ข‹์•„์š”/like' : '๐Ÿค ์ข‹์•„์š”/like'}
            </button>
            <button className="w-full py-3 bg-blue-500 ">
              ์‹œ์ž‘ํ•˜๊ธฐ / Start
            </button>
          </div>
flex-col
๐Ÿ’ก flex์—์„œ ์„ค์ • ๊ฐ’
 
1.flex 
- ์š”์†Œ ๊ฐ€๋กœ ๋ฐฐ์น˜ / Horizontal element layout

2.flex-col 
-  ์š”์†Œ ์„ธ๋กœ ๋ฐฐ์น˜ / Vertical element arrangement

3.justify-center 
- ๊ฐ€๋กœ ์ค‘์•™ ์ •๋ ฌ / Center-aligned horizontally

4.items-center 
- ์œ„ ์•„๋ž˜ ์ค‘์•™ ์ •๋ ฌ / Vercital center alignment 

5.gap-3 
- ์š”์†Œ ์‚ฌ์ด ๊ฐ„๊ฒฉ 12px / 12px spacing between elements

6.justify-between 
- ์š”์†Œ๋ฅผ ์–‘์ชฝ ๋์œผ๋กœ ๋ฐฐ์น˜ (ex. ์ œ๋ชฉ์€ ์™ผ์ชฝ, ๋ฑƒ์ง€๋Š” ์˜ค๋ฅธ์ชฝ)
Position elements at both ends (e.g., title on the left, badge on the right).

๐Ÿ“ CSS

โœ”๏ธ ์„ธ๋กœ ์ •๋ ฌ(๊ธฐ๋ณธ ๊ฐ’) / Vertical alignment (default)

        
        <style> ...
        /* ์นด๋“œ ๋ณธ์ฒด / Card body */
        .card {
          width: 100%;
          max-width: 384px; /* max-w-sm = 384px */
          background-color: white;
          border-radius: 16px; /* rounded-2xl */
          box-shadow: 0 10px 15px rgba(0,0,0,0.1);
          overflow: hidden;
        } ...
        </style>

โœ”๏ธ ๊ฐ€๋กœ์ •๋ ฌ / Horizontal alignment(flex)

        <style> ...
        .card-actions {
          margin-top: 24px;
          display: flex;
          gap: 12px;
        } ... ...
        </style>

โœ”๏ธ ์„ธ๋กœ์ •๋ ฌ flex / Vertical alignment flex

        <style> ...
         .card-actions {
          display: flex;
          flex-direction: column; /* vertical */
          gap: 12px;
        } ... ...
        </style>

Leave a Reply