diff --git a/src/ui/accordion.cljc b/src/ui/accordion.cljc index aee9fc7..3d615b4 100644 --- a/src/ui/accordion.cljc +++ b/src/ui/accordion.cljc @@ -3,9 +3,8 @@ (defn accordion-class-list "Generate a vector of CSS class strings for an accordion item." - [{:keys [open]}] - (cond-> ["accordion"] - open (conj "accordion--open"))) + [_opts] + ["accordion"]) (defn accordion-classes "Generate CSS class string for an accordion." @@ -13,40 +12,40 @@ (str/join " " (accordion-class-list opts))) (defn accordion - "Render an accordion (collapsible) item. + "Render an accordion (collapsible) item using native
/. Props: :title - trigger text - :open - boolean, whether expanded + :open - boolean, whether initially expanded :class - additional CSS classes :attrs - additional HTML attributes" [{:keys [title open class attrs] :as _props} & children] #?(:squint - (let [classes (cond-> (accordion-classes {:open open}) + (let [classes (cond-> (accordion-classes {}) class (str " " class)) - base-attrs (merge {:class classes} attrs)] - (into [:div base-attrs - [:div {:class "accordion-trigger"} title]] - (when open - [[:div {:class "accordion-content"} - (into [:div] children)]]))) + base-attrs (cond-> (merge {:class classes} attrs) + open (assoc :open true))] + (into [:details base-attrs + [:summary {:class "accordion-trigger"} title] + [:div {:class "accordion-content"}]] + children)) :cljs - (let [cls (accordion-class-list {:open open}) + (let [cls (accordion-class-list {}) classes (cond-> cls class (conj class)) - base-attrs (merge {:class classes} attrs)] - (into [:div base-attrs - [:div {:class ["accordion-trigger"]} title]] - (when open - [[:div {:class ["accordion-content"]} - (into [:div] children)]]))) + base-attrs (cond-> (merge {:class classes} attrs) + open (assoc :open true))] + (into [:details base-attrs + [:summary {:class ["accordion-trigger"]} title] + [:div {:class ["accordion-content"]}]] + children)) :clj - (let [classes (cond-> (accordion-classes {:open open}) + (let [classes (cond-> (accordion-classes {}) class (str " " class)) - base-attrs (merge {:class classes} attrs)] - (into [:div base-attrs - [:div {:class "accordion-trigger"} title]] - (when open - [[:div {:class "accordion-content"} - (into [:div] children)]]))))) + base-attrs (cond-> (merge {:class classes} attrs) + open (assoc :open true))] + (into [:details base-attrs + [:summary {:class "accordion-trigger"} title] + [:div {:class "accordion-content"}]] + children)))) diff --git a/src/ui/accordion.css b/src/ui/accordion.css index 4c1de29..46daf0a 100644 --- a/src/ui/accordion.css +++ b/src/ui/accordion.css @@ -31,6 +31,14 @@ user-select: none; color: var(--fg-0); 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 { @@ -49,11 +57,11 @@ transition: transform 150ms ease; } -.accordion--open > .accordion-trigger { +.accordion[open] > .accordion-trigger { border-bottom: var(--border-0); } -.accordion--open > .accordion-trigger::after { +.accordion[open] > .accordion-trigger::after { transform: rotate(180deg); } diff --git a/src/ui/card.css b/src/ui/card.css index 247f56b..dc30409 100644 --- a/src/ui/card.css +++ b/src/ui/card.css @@ -1,18 +1,20 @@ .card { + display: flex; + flex-direction: column; + gap: var(--size-3); background: var(--bg-1); color: var(--fg-0); border: var(--border-0); border-radius: var(--radius-md); box-shadow: var(--shadow-0); overflow: hidden; + padding: var(--size-6); } .card-header { display: flex; flex-direction: column; gap: var(--size-1); - padding: var(--size-6); - padding-bottom: 0; } .card-header h1, @@ -31,13 +33,16 @@ } .card-body { - padding: var(--size-6); } .card-footer { display: flex; justify-content: flex-end; 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); } diff --git a/test/ui/accordion_test.clj b/test/ui/accordion_test.clj index 958ad32..8bc3987 100644 --- a/test/ui/accordion_test.clj +++ b/test/ui/accordion_test.clj @@ -3,29 +3,37 @@ [ui.accordion :as accordion])) (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 {:open false})))) - - (testing "open accordion" - (is (= ["accordion" "accordion--open"] (accordion/accordion-class-list {:open true}))))) + (is (= ["accordion"] (accordion/accordion-class-list {:open true}))))) (deftest accordion-classes-test (testing "space-joined output" (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 - (testing "closed accordion renders trigger only" + (testing "closed accordion uses details/summary" (let [result (accordion/accordion {:title "Question?"} "Answer.")] - (is (= :div (first result))) + (is (= :details (first result))) (is (= "accordion" (get-in result [1 :class]))) - ;; trigger is present - (is (= "accordion-trigger" (get-in result [2 1 :class]))))) - - (testing "open accordion includes content" - (let [result (accordion/accordion {:title "Q?" :open true} "A.")] - (is (= "accordion accordion--open" (get-in result [1 :class]))) - ;; should contain accordion-content div + (is (nil? (get-in result [1 :open]))) + ;; summary trigger is present + (is (= :summary (get-in result [2 0]))) + (is (= "accordion-trigger" (get-in result [2 1 :class]))) + ;; content div is always present (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))))))