Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/backpex/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ defmodule Backpex.Field do
type: :string,
required: true
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]},
default: false
],
class: [
type: {:or, [:string, {:fun, 1}]},
doc: """
Expand Down
2 changes: 2 additions & 0 deletions lib/backpex/fields/belongs_to.ex
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ defmodule Backpex.Fields.BelongsTo do
field={@form[@owner_key]}
options={@options}
prompt={@prompt}
readonly={@readonly}
disabled={@readonly}
translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
help_text={Backpex.Field.help_text(@field_options, assigns)}
phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
Expand Down
1 change: 1 addition & 0 deletions lib/backpex/fields/boolean.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ defmodule Backpex.Fields.Boolean do
<BackpexForm.input
type="toggle"
field={@form[@name]}
disabled={@readonly}
translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
help_text={Backpex.Field.help_text(@field_options, assigns)}
phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
Expand Down
2 changes: 2 additions & 0 deletions lib/backpex/fields/currency.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ defmodule Backpex.Fields.Currency do
<BackpexForm.currency_input
type="text"
field={@form[@name]}
readonly={@readonly}
disabled={@readonly}
translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
help_text={Backpex.Field.help_text(@field_options, assigns)}
phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/date.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ defmodule Backpex.Fields.Date do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/date_time.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ defmodule Backpex.Fields.DateTime do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/email.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ defmodule Backpex.Fields.Email do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
12 changes: 10 additions & 2 deletions lib/backpex/fields/has_many.ex
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ defmodule Backpex.Fields.HasMany do
<Layout.input_label as="span" text={@field_options[:label]} />
</:label>

<Backpex.HTML.CoreComponents.dropdown id={"has-many-dropdown-#{@name}"} class="w-full">
<Backpex.HTML.CoreComponents.dropdown id={"has-many-dropdown-#{@name}"} class="w-full" readonly={@readonly}>
<:trigger
class={[
"input block h-fit w-full p-2",
Expand All @@ -154,12 +154,13 @@ defmodule Backpex.Fields.HasMany do
aria_labelledby={Map.get(assigns, :aria_labelledby)}
>
<div class="flex h-full w-full flex-wrap items-center gap-1 px-2">
<p :if={@selected == []} class="p-0.5 text-sm">{@prompt}</p>
<p :if={@selected == []} class={["p-0.5 text-sm", @readonly && "text-base-content/40"]}>{@prompt}</p>
<.badge
:for={{label, value} <- @selected}
live_resource={@live_resource}
label={label}
value={value}
readonly={@readonly}
name={@name}
/>
</div>
Expand Down Expand Up @@ -302,10 +303,17 @@ defmodule Backpex.Fields.HasMany do
end

attr :live_resource, :atom, required: true
attr :readonly, :boolean, default: false
attr :name, :string, required: true
attr :label, :string, required: true
attr :value, :string, required: true

defp badge(%{readonly: true} = assigns) do
~H"""
<span class="badge badge-sm badge-soft">{@label}</span>
"""
end

defp badge(assigns) do
~H"""
<div class="badge badge-sm badge-soft badge-primary pointer-events-auto pr-0">
Expand Down
12 changes: 9 additions & 3 deletions lib/backpex/fields/has_many_through.ex
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ defmodule Backpex.Fields.HasManyThrough do
>
{label}
</th>
<th>
<th :if={not @readonly}>
<span class="sr-only">{Backpex.__("Actions", @live_resource)}</span>
</th>
</tr>
Expand Down Expand Up @@ -289,7 +289,7 @@ defmodule Backpex.Fields.HasManyThrough do
{assigns}
/>
</td>
<td>
<td :if={not @readonly}>
<div class="flex items-center space-x-2">
<button
class="cursor-pointer"
Expand Down Expand Up @@ -360,7 +360,13 @@ defmodule Backpex.Fields.HasManyThrough do
</div>
</.modal>

<button type="button" class="btn btn-sm btn-outline btn-primary" phx-click="new-relational" phx-target={@myself}>
<button
disabled={@readonly}
type="button"
class="btn btn-sm btn-outline btn-primary"
phx-click="new-relational"
phx-target={@myself}
>
{@relational_title}
</button>

Expand Down
4 changes: 3 additions & 1 deletion lib/backpex/fields/inline_crud.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,15 @@ defmodule Backpex.Fields.InlineCRUD do
<div class={if f_nested.index == 0, do: "mt-5", else: nil}>
<label for={"#{@name}-checkbox-delete-#{f_nested.index}"}>
<input
:if={not @readonly}
id={"#{@name}-checkbox-delete-#{f_nested.index}"}
type="checkbox"
name={"change[#{@name}_delete][]"}
value={f_nested.index}
class="hidden"
/>

<div class="btn btn-outline btn-error">
<div class={["btn btn-outline btn-error", @readonly && "btn-disabled"]}>
<span class="sr-only">{Backpex.__("Delete", @live_resource)}</span>
<Backpex.HTML.CoreComponents.icon name="hero-trash" class="size-5" />
</div>
Expand All @@ -199,6 +200,7 @@ defmodule Backpex.Fields.InlineCRUD do
<input type="hidden" name={"change[#{@name}_delete][]"} tabindex="-1" aria-hidden="true" />
</div>
<input
disabled={@readonly}
name={"change[#{@name}_order][]"}
type="checkbox"
aria-label={Backpex.__("Add entry", @live_resource)}
Expand Down
1 change: 1 addition & 0 deletions lib/backpex/fields/multi_select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ defmodule Backpex.Fields.MultiSelect do
</:label>
<Form.multi_select
field={@form[@name]}
readonly={@readonly}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined attribute "readonly" for component Backpex.HTML.Form.multi_select/1

prompt={@prompt}
not_found_text={@not_found_text}
options={@options}
Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/number.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ defmodule Backpex.Fields.Number do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
2 changes: 2 additions & 0 deletions lib/backpex/fields/select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ defmodule Backpex.Fields.Select do
field={@form[@name]}
options={@options}
prompt={@prompt}
readonly={@readonly}
disabled={@readonly}
translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
help_text={Backpex.Field.help_text(@field_options, assigns)}
phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/text.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ defmodule Backpex.Fields.Text do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/textarea.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ defmodule Backpex.Fields.Textarea do
doc: "Number of visible text lines for the control.",
type: :non_neg_integer,
default: 2
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
4 changes: 0 additions & 4 deletions lib/backpex/fields/time.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ defmodule Backpex.Fields.Time do
throttle: [
doc: "Timeout value (in milliseconds) or function that receives the assigns.",
type: {:or, [:pos_integer, {:fun, 1}]}
],
readonly: [
doc: "Sets the field to readonly. Also see the [panels](/guides/fields/readonly.md) guide.",
type: {:or, [:boolean, {:fun, 1}]}
]
]

Expand Down
14 changes: 10 additions & 4 deletions lib/backpex/fields/upload.ex
Original file line number Diff line number Diff line change
Expand Up @@ -661,22 +661,28 @@ defmodule Backpex.Fields.Upload do
<div
id={"#{@name}-drop-target"}
class="w-full max-w-lg"
phx-hook="BackpexDragHover"
phx-hook={not @readonly && "BackpexDragHover"}
phx-drop-target={if @uploads_allowed, do: @upload.ref}
>
<div class={[
"rounded-field flex justify-center border-2 border-dashed px-6 pt-5 pb-6",
@errors == [] && "border-base-content/25",
@errors != [] && "border-error bg-error/10"
@errors != [] && "border-error bg-error/10",
@readonly && "bg-base-200 cursor-not-allowed"
]}>
<div class="flex flex-col items-center space-y-1 text-center">
<Backpex.HTML.CoreComponents.icon name="hero-document-arrow-up" class="text-base-content/50 h-8 w-8" />
<div class="flex text-sm">
<label>
<a class="link link-hover link-primary font-medium">
<a class={not @readonly && "link link-hover link-primary font-medium"}>
{Backpex.__("Upload a file", @live_resource)}
</a>
<.live_file_input :if={@uploads_allowed} upload={@upload} phx-target="#form-component" class="hidden" />
<.live_file_input
:if={@uploads_allowed and not @readonly}
upload={@upload}
phx-target="#form-component"
class="hidden"
/>
</label>
<input
type="hidden"
Expand Down
2 changes: 2 additions & 0 deletions lib/backpex/fields/url.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ defmodule Backpex.Fields.URL do
phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
phx-throttle={Backpex.Field.throttle(@field_options, assigns)}
aria-labelledby={Map.get(assigns, :aria_labelledby)}
readonly={@readonly}
disabled={@readonly}
/>
</Layout.field_container>
</div>
Expand Down
22 changes: 20 additions & 2 deletions lib/backpex/html/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ defmodule Backpex.HTML.CoreComponents do
</.dropdown>
"""
attr :id, :string, required: true, doc: "unique identifier for the dropdown"
attr :readonly, :boolean, default: false, doc: "whether the dropdown is readonly"
attr :class, :any, default: nil, doc: "additional classes for the outer container element"

slot :trigger, doc: "the trigger element to be used to toggle the dropdown menu" do
Expand All @@ -64,21 +65,38 @@ defmodule Backpex.HTML.CoreComponents do
_trigger -> nil
end)

trigger_class = (assigns.trigger && assigns.trigger[:class]) || ""

trigger_class =
if assigns.readonly do
["cursor-not-allowed bg-base-200"] ++
(trigger_class
|> Enum.join(" ")
|> String.split()
|> List.delete("bg-transparent")
|> List.delete("input"))
else
trigger_class
end
Comment on lines +68 to +80
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks complicated. Can't we just add additional classes in the markup in case the dropdown is readonly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is why. This is how the dropdown is rendered with the code above:

Screenshot_20251223_173629

Whereas this code:

    trigger_class =
      if assigns.readonly do
        ["cursor-not-allowed bg-base-200"] ++ trigger_class
      else
        trigger_class
      end

results in this, and the field can be even highlighted on mouse click (although the dropdown does not open):

Screenshot_20251223_173556


assigns = assign(assigns, trigger_class: trigger_class)

~H"""
<div id={@id} class={["dropdown", @class]} {@rest}>
<div id={@id} class={[not @readonly && "dropdown", @class]} {@rest}>
<div
id={"#{@id}-trigger"}
role="button"
tabindex="0"
aria-haspopup="true"
aria-label={@trigger && @trigger[:aria_label]}
aria-labelledby={@trigger && Map.get(@trigger, :aria_labelledby)}
class={@trigger && @trigger[:class]}
class={@trigger_class}
>
{render_slot(@trigger)}
</div>

<div
:if={not @readonly}
id={"#{@id}-menu"}
tabindex="-1"
aria-labelledby={"#{@id}-trigger"}
Expand Down
13 changes: 11 additions & 2 deletions lib/backpex/html/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ defmodule Backpex.HTML.Form do
@doc type: :component

attr :prompt, :string, required: true, doc: "string that will be shown when no option is selected"
attr :readonly, :boolean, default: false, doc: "whether the dropdown is readonly"
attr :help_text, :string, default: nil, doc: "help text to be displayed below input"
attr :not_found_text, :string, required: true, doc: "string that will be shown when there are no options"
attr :options, :list, required: true, doc: "a list of options for the select"
Expand All @@ -309,7 +310,7 @@ defmodule Backpex.HTML.Form do

~H"""
<div>
<.dropdown id={"multi-select-#{@field.id}"} class="w-full">
<.dropdown id={"multi-select-#{@field.id}"} class="w-full" readonly={@readonly}>
<:trigger
aria_label={@prompt}
aria_labelledby={Map.get(assigns, :aria_labelledby)}
Expand All @@ -320,13 +321,14 @@ defmodule Backpex.HTML.Form do
]}
>
<div class="flex h-full w-full flex-wrap items-center gap-1 px-2">
<p :if={@selected == []} class="p-0.5 text-sm">{@prompt}</p>
<p :if={@selected == []} class={["p-0.5 text-sm", @readonly && "text-base-content/40"]}>{@prompt}</p>
<.multi_select_badge
:for={{label, value} <- @selected}
live_resource={@live_resource}
label={label}
value={value}
event_target={@event_target}
readonly={@readonly}
/>
</div>
</:trigger>
Expand Down Expand Up @@ -384,10 +386,17 @@ defmodule Backpex.HTML.Form do
end

attr :live_resource, :atom, required: true
attr :readonly, :boolean, default: false
attr :label, :string, required: true
attr :value, :any, required: true
attr :event_target, :any, required: true

defp multi_select_badge(%{readonly: true} = assigns) do
~H"""
<span class="badge badge-sm badge-soft">{@label}</span>
"""
end

defp multi_select_badge(assigns) do
~H"""
<div class="badge badge-sm badge-soft badge-primary pointer-events-auto pr-0">
Expand Down
9 changes: 8 additions & 1 deletion lib/backpex/html/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,14 @@ defmodule Backpex.HTML.Resource do
|> assign(:field, field)
|> assign(:field_options, field_options)
|> assign(:type, :form)
|> assign(:readonly, Backpex.Field.readonly?(field_options, assigns))

# this is needed to apply `:readonly` to individual fields in `Backpex.Fields.InlineCRUD`
assigns =
if assigns[:readonly] do
assigns
else
assign(assigns, :readonly, Backpex.Field.readonly?(field_options, assigns))
end

~H"""
<.live_component
Expand Down