fix(card): reduce internal spacing and bump footer button size
Refactor card to use flex column with gap-based spacing instead of per-section padding, tightening the space between header, body, and footer from size-6 to size-3. Add .card-footer .btn override to render buttons at font-base size.
This commit is contained in:
@@ -3,9 +3,8 @@
|
|||||||
|
|
||||||
(defn accordion-class-list
|
(defn accordion-class-list
|
||||||
"Generate a vector of CSS class strings for an accordion item."
|
"Generate a vector of CSS class strings for an accordion item."
|
||||||
[{:keys [open]}]
|
[_opts]
|
||||||
(cond-> ["accordion"]
|
["accordion"])
|
||||||
open (conj "accordion--open")))
|
|
||||||
|
|
||||||
(defn accordion-classes
|
(defn accordion-classes
|
||||||
"Generate CSS class string for an accordion."
|
"Generate CSS class string for an accordion."
|
||||||
@@ -13,40 +12,40 @@
|
|||||||
(str/join " " (accordion-class-list opts)))
|
(str/join " " (accordion-class-list opts)))
|
||||||
|
|
||||||
(defn accordion
|
(defn accordion
|
||||||
"Render an accordion (collapsible) item.
|
"Render an accordion (collapsible) item using native <details>/<summary>.
|
||||||
|
|
||||||
Props:
|
Props:
|
||||||
:title - trigger text
|
:title - trigger text
|
||||||
:open - boolean, whether expanded
|
:open - boolean, whether initially expanded
|
||||||
:class - additional CSS classes
|
:class - additional CSS classes
|
||||||
:attrs - additional HTML attributes"
|
:attrs - additional HTML attributes"
|
||||||
[{:keys [title open class attrs] :as _props} & children]
|
[{:keys [title open class attrs] :as _props} & children]
|
||||||
#?(:squint
|
#?(:squint
|
||||||
(let [classes (cond-> (accordion-classes {:open open})
|
(let [classes (cond-> (accordion-classes {})
|
||||||
class (str " " class))
|
class (str " " class))
|
||||||
base-attrs (merge {:class classes} attrs)]
|
base-attrs (cond-> (merge {:class classes} attrs)
|
||||||
(into [:div base-attrs
|
open (assoc :open true))]
|
||||||
[:div {:class "accordion-trigger"} title]]
|
(into [:details base-attrs
|
||||||
(when open
|
[:summary {:class "accordion-trigger"} title]
|
||||||
[[:div {:class "accordion-content"}
|
[:div {:class "accordion-content"}]]
|
||||||
(into [:div] children)]])))
|
children))
|
||||||
|
|
||||||
:cljs
|
:cljs
|
||||||
(let [cls (accordion-class-list {:open open})
|
(let [cls (accordion-class-list {})
|
||||||
classes (cond-> cls class (conj class))
|
classes (cond-> cls class (conj class))
|
||||||
base-attrs (merge {:class classes} attrs)]
|
base-attrs (cond-> (merge {:class classes} attrs)
|
||||||
(into [:div base-attrs
|
open (assoc :open true))]
|
||||||
[:div {:class ["accordion-trigger"]} title]]
|
(into [:details base-attrs
|
||||||
(when open
|
[:summary {:class ["accordion-trigger"]} title]
|
||||||
[[:div {:class ["accordion-content"]}
|
[:div {:class ["accordion-content"]}]]
|
||||||
(into [:div] children)]])))
|
children))
|
||||||
|
|
||||||
:clj
|
:clj
|
||||||
(let [classes (cond-> (accordion-classes {:open open})
|
(let [classes (cond-> (accordion-classes {})
|
||||||
class (str " " class))
|
class (str " " class))
|
||||||
base-attrs (merge {:class classes} attrs)]
|
base-attrs (cond-> (merge {:class classes} attrs)
|
||||||
(into [:div base-attrs
|
open (assoc :open true))]
|
||||||
[:div {:class "accordion-trigger"} title]]
|
(into [:details base-attrs
|
||||||
(when open
|
[:summary {:class "accordion-trigger"} title]
|
||||||
[[:div {:class "accordion-content"}
|
[:div {:class "accordion-content"}]]
|
||||||
(into [:div] children)]])))))
|
children))))
|
||||||
|
|||||||
@@ -31,6 +31,14 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--fg-0);
|
color: var(--fg-0);
|
||||||
transition: background-color 150ms ease;
|
transition: background-color 150ms ease;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default marker across browsers */
|
||||||
|
.accordion-trigger::-webkit-details-marker,
|
||||||
|
.accordion-trigger::marker {
|
||||||
|
display: none;
|
||||||
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-trigger:hover {
|
.accordion-trigger:hover {
|
||||||
@@ -49,11 +57,11 @@
|
|||||||
transition: transform 150ms ease;
|
transition: transform 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion--open > .accordion-trigger {
|
.accordion[open] > .accordion-trigger {
|
||||||
border-bottom: var(--border-0);
|
border-bottom: var(--border-0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion--open > .accordion-trigger::after {
|
.accordion[open] > .accordion-trigger::after {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
.card {
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--size-3);
|
||||||
background: var(--bg-1);
|
background: var(--bg-1);
|
||||||
color: var(--fg-0);
|
color: var(--fg-0);
|
||||||
border: var(--border-0);
|
border: var(--border-0);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
box-shadow: var(--shadow-0);
|
box-shadow: var(--shadow-0);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: var(--size-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--size-1);
|
gap: var(--size-1);
|
||||||
padding: var(--size-6);
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header h1,
|
.card-header h1,
|
||||||
@@ -31,13 +33,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: var(--size-6);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: var(--size-2);
|
gap: var(--size-2);
|
||||||
padding: var(--size-6);
|
}
|
||||||
padding-top: 0;
|
|
||||||
|
.card-footer .btn {
|
||||||
|
padding: var(--size-2) var(--size-4);
|
||||||
|
font-size: var(--font-base);
|
||||||
|
line-height: var(--size-5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,29 +3,37 @@
|
|||||||
[ui.accordion :as accordion]))
|
[ui.accordion :as accordion]))
|
||||||
|
|
||||||
(deftest accordion-class-list-test
|
(deftest accordion-class-list-test
|
||||||
(testing "closed accordion"
|
(testing "always returns base class"
|
||||||
(is (= ["accordion"] (accordion/accordion-class-list {})))
|
(is (= ["accordion"] (accordion/accordion-class-list {})))
|
||||||
(is (= ["accordion"] (accordion/accordion-class-list {:open false}))))
|
(is (= ["accordion"] (accordion/accordion-class-list {:open true})))))
|
||||||
|
|
||||||
(testing "open accordion"
|
|
||||||
(is (= ["accordion" "accordion--open"] (accordion/accordion-class-list {:open true})))))
|
|
||||||
|
|
||||||
(deftest accordion-classes-test
|
(deftest accordion-classes-test
|
||||||
(testing "space-joined output"
|
(testing "space-joined output"
|
||||||
(is (= "accordion" (accordion/accordion-classes {})))
|
(is (= "accordion" (accordion/accordion-classes {})))
|
||||||
(is (= "accordion accordion--open" (accordion/accordion-classes {:open true})))))
|
(is (= "accordion" (accordion/accordion-classes {:open true})))))
|
||||||
|
|
||||||
(deftest accordion-component-test
|
(deftest accordion-component-test
|
||||||
(testing "closed accordion renders trigger only"
|
(testing "closed accordion uses details/summary"
|
||||||
(let [result (accordion/accordion {:title "Question?"} "Answer.")]
|
(let [result (accordion/accordion {:title "Question?"} "Answer.")]
|
||||||
(is (= :div (first result)))
|
(is (= :details (first result)))
|
||||||
(is (= "accordion" (get-in result [1 :class])))
|
(is (= "accordion" (get-in result [1 :class])))
|
||||||
;; trigger is present
|
(is (nil? (get-in result [1 :open])))
|
||||||
(is (= "accordion-trigger" (get-in result [2 1 :class])))))
|
;; summary trigger is present
|
||||||
|
(is (= :summary (get-in result [2 0])))
|
||||||
(testing "open accordion includes content"
|
(is (= "accordion-trigger" (get-in result [2 1 :class])))
|
||||||
(let [result (accordion/accordion {:title "Q?" :open true} "A.")]
|
;; content div is always present
|
||||||
(is (= "accordion accordion--open" (get-in result [1 :class])))
|
|
||||||
;; should contain accordion-content div
|
|
||||||
(is (some #(and (vector? %) (= "accordion-content" (get-in % [1 :class])))
|
(is (some #(and (vector? %) (= "accordion-content" (get-in % [1 :class])))
|
||||||
(rest (rest result)))))))
|
(rest (rest result))))))
|
||||||
|
|
||||||
|
(testing "open accordion has open attribute"
|
||||||
|
(let [result (accordion/accordion {:title "Q?" :open true} "A.")]
|
||||||
|
(is (= :details (first result)))
|
||||||
|
(is (true? (get-in result [1 :open])))))
|
||||||
|
|
||||||
|
(testing "children are inside content div"
|
||||||
|
(let [result (accordion/accordion {:title "T"} "Child1" "Child2")]
|
||||||
|
;; find the content div
|
||||||
|
(let [content-div (first (filter #(and (vector? %)
|
||||||
|
(= "accordion-content" (get-in % [1 :class])))
|
||||||
|
(rest (rest result))))]
|
||||||
|
(is (some? content-div))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user