mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
♻️ Improve and add radio buttons to the DS
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
[app.main.ui.ds.controls.combobox :refer [combobox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.controls.select :refer [select*]]
|
||||
[app.main.ui.ds.controls.switch :refer [switch*]]
|
||||
[app.main.ui.ds.controls.utilities.hint-message :refer [hint-message*]]
|
||||
@@ -63,6 +64,7 @@
|
||||
:Select select*
|
||||
:Switch switch*
|
||||
:Checkbox checkbox*
|
||||
:RadioButtons radio-buttons*
|
||||
:Combobox combobox*
|
||||
:Text text*
|
||||
:TabSwitcher tab-switcher*
|
||||
|
||||
101
frontend/src/app/main/ui/ds/controls/radio_buttons.cljs
Normal file
101
frontend/src/app/main/ui/ds/controls/radio_buttons.cljs
Normal file
@@ -0,0 +1,101 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.controls.radio-buttons
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon-list]]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]
|
||||
[rumext.v2.util :as mfu]))
|
||||
|
||||
(def ^:private schema:radio-button
|
||||
[:map
|
||||
[:id :string]
|
||||
[:icon {:optional true}
|
||||
[:and :string [:fn #(contains? icon-list %)]]]
|
||||
[:label :string]
|
||||
[:value :string]
|
||||
[:disabled {:optional true} :boolean]])
|
||||
|
||||
(def ^:private schema:radio-buttons
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:expanded {:optional true} :boolean]
|
||||
[:name {:optional true} :string]
|
||||
[:selected {:optional true} :string]
|
||||
[:options [:vector {:min 2} schema:radio-button]]
|
||||
[:on-change {:optional true} fn?]])
|
||||
|
||||
(mf/defc radio-buttons*
|
||||
{::mf/schema schema:radio-buttons}
|
||||
[{:keys [class expanded name selected options on-change] :rest props}]
|
||||
(let [options (if (array? options)
|
||||
(mfu/bean options)
|
||||
options)
|
||||
|
||||
handle-click
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
label (dom/get-parent-with-data target "label")]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(dom/click label))))
|
||||
|
||||
handle-change
|
||||
(mf/use-fn
|
||||
(mf/deps selected on-change)
|
||||
(fn [event]
|
||||
(let [input (dom/get-target event)
|
||||
value (dom/get-target-val event)]
|
||||
(when (fn? on-change)
|
||||
(on-change value event))
|
||||
(dom/blur! input))))
|
||||
|
||||
props
|
||||
(mf/spread-props props {:key (dm/str name "-" selected)
|
||||
:class [class (stl/css-case :wrapper true
|
||||
:expanded expanded)]})]
|
||||
|
||||
[:> :div props
|
||||
(for [[idx {:keys [id value label icon disabled]}] (d/enumerate options)]
|
||||
(let [checked? (= selected value)]
|
||||
[:label {:key idx
|
||||
:html-for id
|
||||
:data-label true
|
||||
:data-testid id
|
||||
:class (stl/css-case :label true
|
||||
:expanded expanded)}
|
||||
|
||||
(if (some? icon)
|
||||
[:> icon-button* {:variant "secondary"
|
||||
:on-click handle-click
|
||||
:aria-pressed checked?
|
||||
:aria-label label
|
||||
:icon icon
|
||||
:disabled disabled}]
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click handle-click
|
||||
:aria-pressed checked?
|
||||
:class (stl/css-case :button true
|
||||
:expanded expanded)
|
||||
:disabled disabled}
|
||||
label])
|
||||
|
||||
[:input {:id id
|
||||
:class (stl/css :input)
|
||||
:on-change handle-change
|
||||
:type "radio"
|
||||
:name name
|
||||
:disabled disabled
|
||||
:value value
|
||||
:default-checked checked?}]]))]))
|
||||
87
frontend/src/app/main/ui/ds/controls/radio_buttons.mdx
Normal file
87
frontend/src/app/main/ui/ds/controls/radio_buttons.mdx
Normal file
@@ -0,0 +1,87 @@
|
||||
{ /* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Copyright (c) KALEIDOS INC */ }
|
||||
|
||||
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
|
||||
import * as RadioButtons from "./radio_buttons.stories";
|
||||
|
||||
<Meta title="Controls/Radio Buttons" />
|
||||
|
||||
# Radio Buttons
|
||||
|
||||
The `radio-buttons*` component allows users to switch between two or more options that are mutually exclusive.
|
||||
|
||||
## Variants
|
||||
|
||||
Radio buttons with text only. The label will be the text of the button.
|
||||
|
||||
<Canvas of={RadioButtons.Default} />
|
||||
|
||||
```clj
|
||||
[:> radio-buttons* {:selected "left"
|
||||
:on-change handle-change
|
||||
:name "alignment"
|
||||
:expanded false
|
||||
:options [{:id "align-left"
|
||||
:label "Left"
|
||||
:value "left"}
|
||||
{:id "align-center"
|
||||
:label "Center"
|
||||
:value "center"}
|
||||
{:id "align-right"
|
||||
:label "Right"
|
||||
:value "right"}]}]
|
||||
```
|
||||
|
||||
Radio buttons with icons only. In this case, the label will act as the tooltip of each button.
|
||||
|
||||
<Canvas of={RadioButtons.WithIcons} />
|
||||
|
||||
```clj
|
||||
(ns app.main.ui.foo
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]))
|
||||
|
||||
[:> radio-buttons* {:selected "left"
|
||||
:on-change handle-change
|
||||
:name "alignment"
|
||||
:expanded false
|
||||
:options [{:id "align-left"
|
||||
:icon i/text-align-left
|
||||
:label "Left align"
|
||||
:value "left"}
|
||||
{:id "align-center"
|
||||
:icon i/text-align-center
|
||||
:label "Center align"
|
||||
:value "center"}
|
||||
{:id "align-right"
|
||||
:icon i/text-align-right
|
||||
:label "Right align"
|
||||
:value "right"}]}]
|
||||
```
|
||||
|
||||
In both cases, the selected value must match the value of the selected option.
|
||||
|
||||
## Anatomy
|
||||
|
||||
Under the hood, each option is represented by
|
||||
- a button, which is the visible and clickable element. It may be either an icon button or a text button.
|
||||
- a radio input, which is not visible but retains the current state of the option.
|
||||
|
||||
A radio group is defined by giving each of radio buttons in the group the same name. Once a radio group is established,
|
||||
selecting any radio button in that group automatically deselects any currently-selected radio button in the same group.
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### When to Use
|
||||
|
||||
- For multiple choice settings that take effect immediately.
|
||||
- In preference panels and configuration screens.
|
||||
|
||||
### When Not to Use
|
||||
|
||||
- For boolean settings (use switch or checkbox instead).
|
||||
- For actions that require confirmation (use buttons instead).
|
||||
- For temporary states that need explicit "Apply" action.
|
||||
40
frontend/src/app/main/ui/ds/controls/radio_buttons.scss
Normal file
40
frontend/src/app/main/ui/ds/controls/radio_buttons.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/spacing.scss" as *;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
gap: var(--sp-xs);
|
||||
|
||||
&.expanded {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
&.expanded {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
&.expanded {
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { RadioButtons } = Components;
|
||||
|
||||
const options = [
|
||||
{id: "left", label: "Left", value: "left" },
|
||||
{id: "center", label: "Center", value: "center" },
|
||||
{id: "right", label: "Right", value: "right" },
|
||||
];
|
||||
|
||||
const optionsIcon = [
|
||||
{id: "left", label: "Left align", value: "left", icon: "text-align-left" },
|
||||
{id: "center", label: "Center align", value: "center", icon: "text-align-center" },
|
||||
{id: "right", label: "Right align", value: "right", icon: "text-align-right" },
|
||||
];
|
||||
|
||||
export default {
|
||||
title: "Controls/Radio Buttons",
|
||||
component: RadioButtons,
|
||||
argTypes: {
|
||||
name: {
|
||||
control: { type: "text" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
selected: {
|
||||
control: { type: "select" },
|
||||
options: ["left", "center", "right"],
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
expanded: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is checked",
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
description: "Whether the checkbox is disabled",
|
||||
},
|
||||
},
|
||||
args: {
|
||||
name: "alignment",
|
||||
selected: "left",
|
||||
expanded: false,
|
||||
options: options,
|
||||
disabled: false,
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
exclude: ["options", "on-change"],
|
||||
},
|
||||
},
|
||||
render: ({ ...args }) => <RadioButtons {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const WithIcons = {
|
||||
args: {
|
||||
options: optionsIcon,
|
||||
},
|
||||
};
|
||||
@@ -17,13 +17,13 @@
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.checkbox :refer [checkbox*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.empty-state :refer [empty-state*]]
|
||||
[app.util.dom :as dom]
|
||||
@@ -582,41 +582,44 @@
|
||||
:options animation-opts
|
||||
:on-change change-animation-type}]]]
|
||||
|
||||
;; Direction
|
||||
;; Way
|
||||
(when (ctsi/has-way? interaction)
|
||||
[:div {:class (stl/css :interaction-row)}
|
||||
[:div {:class (stl/css :interaction-row-radio)}
|
||||
[:& radio-buttons {:selected (d/name way)
|
||||
:on-change change-way
|
||||
:name "animation-way"}
|
||||
[:& radio-button {:value "in"
|
||||
:id "animation-way-in"}]
|
||||
[:& radio-button {:id "animation-way-out"
|
||||
:value "out"}]]]])
|
||||
[:> radio-buttons* {:selected (d/name way)
|
||||
:on-change change-way
|
||||
:name "animation-way"
|
||||
:expanded true
|
||||
:options [{:id "animation-way-in"
|
||||
:label (tr "workspace.options.interaction-animation-direction-in")
|
||||
:value "in"}
|
||||
{:id "animation-way-out"
|
||||
:label (tr "workspace.options.interaction-animation-direction-out")
|
||||
:value "out"}]}]]])
|
||||
|
||||
;; Direction
|
||||
(when (ctsi/has-direction? interaction)
|
||||
[:div {:class (stl/css :interaction-row)}
|
||||
[:div {:class (stl/css :interaction-row-radio)}
|
||||
[:& radio-buttons {:selected (d/name direction)
|
||||
:on-change change-direction
|
||||
:name "animation-direction"}
|
||||
[:& radio-button {:icon i/row
|
||||
:icon-class (stl/css :right)
|
||||
:value "right"
|
||||
:id "animation-right"}]
|
||||
[:& radio-button {:icon i/row-reverse
|
||||
:icon-class (stl/css :left)
|
||||
:id "animation-left"
|
||||
:value "left"}]
|
||||
[:& radio-button {:icon i/column
|
||||
:icon-class (stl/css :down)
|
||||
:id "animation-down"
|
||||
:value "down"}]
|
||||
[:& radio-button {:icon i/column-reverse
|
||||
:icon-class (stl/css :up)
|
||||
:id "animation-up"
|
||||
:value "up"}]]]])
|
||||
[:> radio-buttons* {:selected (d/name direction)
|
||||
:on-change change-direction
|
||||
:name "animation-direction"
|
||||
:options [{:id "animation-right"
|
||||
:icon i/row
|
||||
:label (tr "workspace.options.interaction-animation-direction-right")
|
||||
:value "right"}
|
||||
{:id "animation-left"
|
||||
:icon i/row-reverse
|
||||
:label (tr "workspace.options.interaction-animation-direction-left")
|
||||
:value "left"}
|
||||
{:id "animation-down"
|
||||
:icon i/column
|
||||
:label (tr "workspace.options.interaction-animation-direction-down")
|
||||
:value "down"}
|
||||
{:id "animation-up"
|
||||
:icon i/column-reverse
|
||||
:label (tr "workspace.options.interaction-animation-direction-up")
|
||||
:value "up"}]}]]])
|
||||
|
||||
;; Duration
|
||||
(when (ctsi/has-duration? interaction)
|
||||
|
||||
@@ -6161,6 +6161,30 @@ msgstr "After delay"
|
||||
msgid "workspace.options.interaction-animation"
|
||||
msgstr "Animation"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:609
|
||||
msgid "workspace.options.interaction-animation-direction-right"
|
||||
msgstr "Right"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:613
|
||||
msgid "workspace.options.interaction-animation-direction-left"
|
||||
msgstr "Left"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:617
|
||||
msgid "workspace.options.interaction-animation-direction-down"
|
||||
msgstr "Down"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:621
|
||||
msgid "workspace.options.interaction-animation-direction-up"
|
||||
msgstr "Up"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:595
|
||||
msgid "workspace.options.interaction-animation-direction-in"
|
||||
msgstr "In"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:599
|
||||
msgid "workspace.options.interaction-animation-direction-out"
|
||||
msgstr "Out"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:413
|
||||
msgid "workspace.options.interaction-animation-dissolve"
|
||||
msgstr "Dissolve"
|
||||
|
||||
@@ -6114,6 +6114,30 @@ msgstr "Tiempo"
|
||||
msgid "workspace.options.interaction-animation"
|
||||
msgstr "Animación"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:609
|
||||
msgid "workspace.options.interaction-animation-direction-right"
|
||||
msgstr "Derecha"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:613
|
||||
msgid "workspace.options.interaction-animation-direction-left"
|
||||
msgstr "Izquierda"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:617
|
||||
msgid "workspace.options.interaction-animation-direction-down"
|
||||
msgstr "Abajo"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:621
|
||||
msgid "workspace.options.interaction-animation-direction-up"
|
||||
msgstr "Arriba"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:595
|
||||
msgid "workspace.options.interaction-animation-direction-in"
|
||||
msgstr "Dentro"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:599
|
||||
msgid "workspace.options.interaction-animation-direction-out"
|
||||
msgstr "Fuera"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs:413
|
||||
msgid "workspace.options.interaction-animation-dissolve"
|
||||
msgstr "Disolver"
|
||||
|
||||
Reference in New Issue
Block a user