style.scss 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. .alert-config {
  2. display: flex;
  3. flex-direction: column;
  4. // ── 목록 패널 ────────────────────────────────────
  5. &__list-panel {
  6. display: flex;
  7. flex-direction: column;
  8. gap: 0;
  9. }
  10. // ── 30/70 레이아웃 ────────────────────────────────
  11. &__layout {
  12. display: grid;
  13. grid-template-columns: minmax(0, 500px) auto 1fr;
  14. gap: 24px;
  15. align-items: start;
  16. }
  17. // ── 좌측 패널 (미리보기) ─────────────────────────
  18. &__preview-panel {
  19. position: sticky;
  20. top: 16px;
  21. display: flex;
  22. flex-direction: column;
  23. gap: 16px;
  24. max-height: calc(100vh - 48px);
  25. overflow-y: auto;
  26. }
  27. // ── 우측 패널 (폼) ───────────────────────────────
  28. &__form-panel {
  29. display: flex;
  30. flex-direction: column;
  31. gap: 0;
  32. }
  33. // ── 섹션 (제목 좌 / 콘텐츠 우) ──────────────────
  34. &__section {
  35. display: grid;
  36. grid-template-columns: 140px 1fr;
  37. gap: 24px;
  38. padding: 24px 0;
  39. border-bottom: 1px solid hsl(var(--border));
  40. &:first-of-type {
  41. padding-top: 0;
  42. }
  43. }
  44. &__section-title {
  45. font-size: 0.875rem;
  46. font-weight: 600;
  47. color: hsl(var(--foreground));
  48. padding-top: 2px;
  49. margin: 0;
  50. }
  51. &__section-body {
  52. display: flex;
  53. flex-direction: column;
  54. gap: 17px;
  55. max-width: 520px;
  56. }
  57. // ── fieldset (미리보기 패널용) ────────────────────
  58. &__fieldset {
  59. border: 1px solid hsl(var(--border));
  60. border-radius: var(--radius);
  61. padding: 16px 20px 20px;
  62. margin: 0;
  63. & + & {
  64. margin-top: 0;
  65. }
  66. }
  67. &__legend {
  68. font-size: 1rem;
  69. font-weight: 600;
  70. color: hsl(var(--foreground));
  71. padding: 0 8px;
  72. margin-left: -4px;
  73. }
  74. // ── 미리보기 테스트 폼 ───────────────────────────
  75. &__preview-form {
  76. display: flex;
  77. flex-direction: column;
  78. gap: 12px;
  79. }
  80. &__preview-btn {
  81. margin-top: 12px;
  82. width: 100%;
  83. justify-content: center;
  84. }
  85. // ── 위젯 미리보기 ────────────────────────────────
  86. &__widget {
  87. border: 1px solid hsl(var(--border));
  88. border-radius: var(--radius);
  89. overflow: hidden;
  90. background: #1a1a2e;
  91. }
  92. &__widget-label {
  93. padding: 8px 14px;
  94. font-size: 0.8125rem;
  95. font-weight: 500;
  96. color: hsl(var(--muted));
  97. background:#0e0e1a;
  98. }
  99. &__widget-frame {
  100. display: block;
  101. width: 100%;
  102. height: clamp(200px, 30vw, 450px);
  103. border: none;
  104. background: #1a1a2e;
  105. position: relative;
  106. overflow: hidden;
  107. container-type: inline-size;
  108. }
  109. &__widget-empty {
  110. display: flex;
  111. align-items: center;
  112. justify-content: center;
  113. height: 200px;
  114. color: hsl(var(--muted-foreground));
  115. font-size: 0.875rem;
  116. }
  117. // ── 툴바 ─────────────────────────────────────────
  118. &__toolbar {
  119. display: flex;
  120. align-items: center;
  121. justify-content: space-between;
  122. gap: 8px;
  123. margin-bottom: 12px;
  124. flex-wrap: wrap;
  125. }
  126. &__toolbar-left {
  127. display: flex;
  128. align-items: baseline;
  129. gap: 6px;
  130. }
  131. &__toolbar-right {
  132. display: flex;
  133. align-items: center;
  134. gap: 6px;
  135. flex-wrap: wrap;
  136. }
  137. &__count {
  138. font-size: 0.8125rem;
  139. color: hsl(var(--muted-foreground));
  140. }
  141. &__per-page {
  142. padding: 9px 10px;
  143. border: 1px solid hsl(var(--border));
  144. border-radius: calc(var(--radius) - 2px);
  145. background: hsl(var(--background));
  146. color: hsl(var(--foreground));
  147. font-size: 0.875rem;
  148. cursor: pointer;
  149. }
  150. // ── 버튼 ─────────────────────────────────────────
  151. &__btn {
  152. display: inline-flex;
  153. align-items: center;
  154. gap: 6px;
  155. padding: 8px 16px;
  156. border: 1px solid hsl(var(--border));
  157. border-radius: calc(var(--radius) - 2px);
  158. background: hsl(var(--background));
  159. color: hsl(var(--foreground));
  160. font-size: 0.875rem;
  161. font-weight: 500;
  162. cursor: pointer;
  163. white-space: nowrap;
  164. transition: background-color 0.15s, color 0.15s;
  165. justify-content: center;
  166. &:not([class*="--"]):hover:not(:disabled) {
  167. background: hsl(var(--accent));
  168. color: hsl(var(--accent-foreground));
  169. }
  170. &:disabled {
  171. opacity: 0.5;
  172. cursor: not-allowed;
  173. }
  174. &--sm {
  175. padding: 4px 10px;
  176. font-size: 0.75rem;
  177. }
  178. &--primary {
  179. background: hsl(var(--primary));
  180. color: hsl(var(--primary-foreground));
  181. border-color: hsl(var(--primary));
  182. &:hover:not(:disabled) {
  183. background: hsl(var(--primary) / 0.9);
  184. color: hsl(var(--primary-foreground));
  185. }
  186. }
  187. &--danger {
  188. color: hsl(var(--destructive));
  189. border-color: hsl(var(--destructive) / 0.5);
  190. &:hover:not(:disabled) {
  191. background: hsl(var(--destructive) / 0.1);
  192. }
  193. }
  194. }
  195. // ── 테이블 ───────────────────────────────────────
  196. &__table-wrap {
  197. border: 1px solid hsl(var(--border));
  198. border-radius: var(--radius);
  199. overflow: hidden;
  200. }
  201. &__table {
  202. width: 100%;
  203. border-collapse: collapse;
  204. font-size: 0.8125rem;
  205. th, td {
  206. padding: 10px 12px;
  207. text-align: left;
  208. border-bottom: 1px solid hsl(var(--border));
  209. }
  210. th {
  211. font-weight: 500;
  212. color: hsl(var(--muted-foreground));
  213. font-size: 0.75rem;
  214. background: hsl(var(--muted) / 0.4);
  215. }
  216. tbody tr:last-child td {
  217. border-bottom: none;
  218. }
  219. tbody tr {
  220. transition: background 0.15s;
  221. }
  222. tbody tr:hover {
  223. background: hsl(var(--muted) / 0.5);
  224. }
  225. }
  226. &__th--check,
  227. &__td--check {
  228. width: 40px;
  229. text-align: center;
  230. }
  231. &__td--message {
  232. max-width: 200px;
  233. overflow: hidden;
  234. text-overflow: ellipsis;
  235. white-space: nowrap;
  236. }
  237. &__td--media {
  238. position: relative;
  239. > div {
  240. display: flex;
  241. align-items: center;
  242. gap: 4px;
  243. width: 100%;
  244. flex-grow: 1;
  245. }
  246. }
  247. &__media-thumb {
  248. display: inline-flex;
  249. align-items: center;
  250. justify-content: center;
  251. flex: 1 1 auto;
  252. max-width: 96px;
  253. border: 1px solid hsl(var(--border));
  254. border-radius: calc(var(--radius) - 2px);
  255. background: hsl(var(--background));
  256. cursor: pointer;
  257. overflow: hidden;
  258. padding: 0;
  259. transition: border-color 0.15s;
  260. &:hover {
  261. border-color: hsl(var(--primary));
  262. }
  263. }
  264. &__media-thumb-img {
  265. width: 100%;
  266. height: 100%;
  267. object-fit: contain;
  268. }
  269. &__media-btn {
  270. display: inline-flex;
  271. align-items: center;
  272. justify-content: center;
  273. flex-shrink: 0;
  274. width: 28px;
  275. height: 28px;
  276. border: 1px solid hsl(var(--border));
  277. border-radius: calc(var(--radius) - 2px);
  278. background: hsl(var(--background));
  279. color: hsl(var(--muted-foreground));
  280. font-size: 0.75rem;
  281. cursor: pointer;
  282. transition: color 0.15s, border-color 0.15s;
  283. &:hover {
  284. color: hsl(var(--foreground));
  285. border-color: hsl(var(--foreground));
  286. }
  287. &--image:hover {
  288. color: hsl(var(--primary));
  289. border-color: hsl(var(--primary));
  290. }
  291. &--sound:hover {
  292. color: hsl(var(--primary));
  293. border-color: hsl(var(--primary));
  294. }
  295. &--playing {
  296. color: hsl(var(--primary));
  297. border-color: hsl(var(--primary));
  298. background: hsl(var(--primary) / 0.1);
  299. animation: alert-pulse 1.5s ease-in-out infinite;
  300. }
  301. }
  302. &__media-dialog {
  303. max-width: 90vw;
  304. width: auto;
  305. padding: 16px;
  306. }
  307. &__media-dialog-img {
  308. display: block;
  309. max-width: 100%;
  310. max-height: 80vh;
  311. margin: 0 auto;
  312. border-radius: calc(var(--radius) - 2px);
  313. object-fit: contain;
  314. }
  315. &__row--checked {
  316. opacity: 0.5;
  317. td {
  318. text-decoration: line-through;
  319. }
  320. }
  321. &__row--editing {
  322. background: hsl(var(--primary) / 0.08);
  323. }
  324. // 뱃지
  325. &__match-badge {
  326. display: inline-flex;
  327. align-items: center;
  328. padding: 2px 8px;
  329. border-radius: calc(var(--radius) - 2px);
  330. font-size: 0.6875rem;
  331. font-weight: 500;
  332. white-space: nowrap;
  333. &--min {
  334. background: hsl(var(--primary) / 0.1);
  335. color: hsl(var(--primary));
  336. }
  337. &--exact {
  338. background: hsl(217 91% 60% / 0.1);
  339. color: hsl(217 91% 60%);
  340. }
  341. }
  342. // 활성 토글 스위치
  343. &__active-toggle {
  344. position: relative;
  345. display: inline-flex;
  346. align-items: center;
  347. width: 42px;
  348. height: 22px;
  349. padding: 2px;
  350. border: 1px solid hsl(var(--border));
  351. border-radius: 999px;
  352. background: hsl(var(--muted));
  353. cursor: pointer;
  354. transition: background 0.18s, border-color 0.18s;
  355. &-knob {
  356. width: 16px;
  357. height: 16px;
  358. border-radius: 50%;
  359. background: #fff;
  360. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  361. transition: transform 0.18s;
  362. }
  363. &--on {
  364. background: hsl(142 71% 45%);
  365. border-color: hsl(142 71% 45%);
  366. .alert-config__active-toggle-knob {
  367. transform: translateX(20px);
  368. }
  369. }
  370. &--busy {
  371. opacity: 0.7;
  372. cursor: progress;
  373. }
  374. &:disabled {
  375. cursor: not-allowed;
  376. &:not(.alert-config__active-toggle--busy) {
  377. opacity: 0.6;
  378. }
  379. }
  380. &:not(:disabled):hover {
  381. border-color: hsl(142 71% 45%);
  382. }
  383. }
  384. &__empty {
  385. padding: 48px 16px;
  386. text-align: center;
  387. color: hsl(var(--muted-foreground));
  388. font-size: 0.875rem;
  389. }
  390. // ── 페이징 ───────────────────────────────────────
  391. &__pagination {
  392. display: flex;
  393. align-items: center;
  394. justify-content: center;
  395. gap: 4px;
  396. margin-top: 12px;
  397. }
  398. &__page-btn {
  399. min-width: 28px;
  400. height: 28px;
  401. display: inline-flex;
  402. align-items: center;
  403. justify-content: center;
  404. border: 1px solid hsl(var(--border));
  405. border-radius: calc(var(--radius) - 2px);
  406. background: hsl(var(--background));
  407. color: hsl(var(--foreground));
  408. font-size: 0.75rem;
  409. cursor: pointer;
  410. transition: background-color 0.15s;
  411. &:hover:not(:disabled) {
  412. background: hsl(var(--accent));
  413. }
  414. &:disabled {
  415. opacity: 0.5;
  416. cursor: not-allowed;
  417. }
  418. &--active {
  419. background: hsl(var(--primary));
  420. color: hsl(var(--primary-foreground));
  421. border-color: hsl(var(--primary));
  422. }
  423. }
  424. // ── 폼 필드 ──────────────────────────────────────
  425. &__field {
  426. display: flex;
  427. flex-direction: column;
  428. gap: 6px;
  429. }
  430. &__field-label {
  431. font-size: 0.875rem;
  432. font-weight: 500;
  433. color: hsl(var(--foreground));
  434. }
  435. &__field-hint {
  436. font-size: 0.8125rem;
  437. color: hsl(var(--muted-foreground));
  438. }
  439. &__field-row {
  440. display: grid;
  441. grid-template-columns: 1fr 1fr;
  442. gap: 10px;
  443. }
  444. &__input {
  445. padding: 8px 12px;
  446. border: 1px solid hsl(var(--border));
  447. border-radius: calc(var(--radius) - 2px);
  448. background: hsl(var(--background));
  449. color: hsl(var(--foreground));
  450. font-size: 0.875rem;
  451. width: 100%;
  452. &::placeholder {
  453. color: hsl(var(--muted-foreground));
  454. }
  455. }
  456. &__select {
  457. padding: 8px 12px;
  458. border: 1px solid hsl(var(--border));
  459. border-radius: calc(var(--radius) - 2px);
  460. background: hsl(var(--background));
  461. color: hsl(var(--foreground));
  462. font-size: 0.875rem;
  463. width: 100%;
  464. cursor: pointer;
  465. }
  466. &__radio-group {
  467. display: flex;
  468. gap: 16px;
  469. height: 100%;
  470. justify-content: center;
  471. border: 1px solid hsl(var(--border));
  472. border-radius: calc(var(--radius) - 2px);
  473. }
  474. &__radio-label {
  475. display: inline-flex;
  476. align-items: center;
  477. gap: 6px;
  478. font-size: 0.875rem;
  479. color: hsl(var(--foreground));
  480. cursor: pointer;
  481. }
  482. &__checkbox-label {
  483. display: inline-flex;
  484. align-items: center;
  485. gap: 8px;
  486. font-size: 0.875rem;
  487. color: hsl(var(--foreground));
  488. cursor: pointer;
  489. }
  490. // ── 폰트 그룹 ────────────────────────────────────
  491. &__font-group {
  492. &:not(:first-child) {
  493. margin-top: 12px;
  494. padding-top: 12px;
  495. border-top: 1px solid hsl(var(--border));
  496. }
  497. }
  498. &__font-group-title {
  499. font-size: 0.875rem;
  500. font-weight: 500;
  501. color: hsl(var(--muted-foreground));
  502. margin-bottom: 10px;
  503. }
  504. &__font-grid {
  505. display: grid;
  506. grid-template-columns: 1fr 80px 1fr;
  507. gap: 12px;
  508. align-items: start;
  509. }
  510. // ── 색상 입력 ─────────────────────────────────────
  511. &__color-wrap {
  512. display: flex;
  513. gap: 8px;
  514. align-items: center;
  515. }
  516. &__color-input {
  517. width: 36px;
  518. height: 36px;
  519. padding: 2px;
  520. border: 1px solid hsl(var(--border));
  521. border-radius: calc(var(--radius) - 2px);
  522. background: hsl(var(--background));
  523. cursor: pointer;
  524. flex-shrink: 0;
  525. &::-webkit-color-swatch-wrapper {
  526. padding: 0;
  527. }
  528. &::-webkit-color-swatch {
  529. border: none;
  530. border-radius: calc(var(--radius) - 4px);
  531. }
  532. }
  533. &__color-text {
  534. flex: 1;
  535. min-width: 0;
  536. }
  537. // ── 미디어 업로드 ─────────────────────────────────
  538. &__media-upload {
  539. display: flex;
  540. flex-direction: column;
  541. gap: 8px;
  542. margin-bottom: 16px;
  543. &:last-of-type {
  544. margin-bottom: 0;
  545. }
  546. }
  547. &__media-preview {
  548. display: flex;
  549. align-items: start;
  550. gap: 8px;
  551. padding: 8px;
  552. border: 1px solid hsl(var(--border));
  553. border-radius: calc(var(--radius) - 2px);
  554. background: hsl(var(--muted) / 0.3);
  555. img {
  556. max-width: 140px;
  557. max-height: 80px;
  558. object-fit: contain;
  559. border-radius: calc(var(--radius) - 4px);
  560. }
  561. audio {
  562. max-width: 280px;
  563. height: 36px;
  564. }
  565. &--audio {
  566. flex-direction: row;
  567. }
  568. }
  569. &__media-remove {
  570. display: inline-flex;
  571. align-items: center;
  572. justify-content: center;
  573. width: 28px;
  574. height: 28px;
  575. border: 1px solid hsl(var(--destructive) / 0.5);
  576. border-radius: calc(var(--radius) - 2px);
  577. background: none;
  578. color: hsl(var(--destructive));
  579. font-size: 0.75rem;
  580. cursor: pointer;
  581. flex-shrink: 0;
  582. margin-left: auto;
  583. transition: background 0.15s;
  584. &:hover {
  585. background: hsl(var(--destructive) / 0.1);
  586. }
  587. }
  588. &__file-hidden {
  589. display: none;
  590. }
  591. // ── 폼 하단 ──────────────────────────────────────
  592. &__form-footer {
  593. display: flex;
  594. justify-content: center;
  595. gap: 8px;
  596. padding-top: 20px;
  597. margin-top: 4px;
  598. }
  599. }
  600. // ── 반응형 ────────────────────────────────────────────
  601. @media (max-width: 1380px) {
  602. .alert-config {
  603. &__layout {
  604. grid-template-columns: minmax(0, 500px) auto 1fr;
  605. gap: 24px;
  606. align-items: start;
  607. }
  608. &__section {
  609. grid-template-columns: 1fr;
  610. gap: 5px;
  611. }
  612. &__section-title {
  613. margin-bottom: 9px;
  614. }
  615. }
  616. }
  617. @media (max-width: 1120px) {
  618. .alert-config {
  619. &__layout {
  620. grid-template-columns: 2fr auto 3fr;
  621. }
  622. }
  623. }
  624. @media (max-width: 768px) {
  625. .alert-config {
  626. &__layout {
  627. grid-template-columns: 1fr;
  628. }
  629. &__preview-panel {
  630. position: static;
  631. max-height: none;
  632. }
  633. &__section {
  634. grid-template-columns: 1fr;
  635. gap: 8px;
  636. }
  637. &__section-title {
  638. margin-bottom: 12px;
  639. }
  640. &__section-body {
  641. max-width: none;
  642. }
  643. &__field-row {
  644. grid-template-columns: 1fr;
  645. }
  646. &__font-grid {
  647. grid-template-columns: 1fr;
  648. }
  649. &__media-upload {
  650. padding-left: 0;
  651. }
  652. &__toolbar {
  653. flex-direction: column;
  654. align-items: flex-start;
  655. }
  656. &__table-wrap {
  657. overflow-x: auto;
  658. }
  659. &__table {
  660. min-width: 892px;
  661. }
  662. &__media-thumb {
  663. width: 32px;
  664. height: 32px;
  665. }
  666. }
  667. }
  668. @keyframes alert-pulse {
  669. 0%, 100% {
  670. opacity: 1;
  671. }
  672. 50% {
  673. opacity: 0.5;
  674. }
  675. }