` wrapper for columns.
4 |
5 |
6 | https://getbootstrap.com/docs/5.0/layout/grid/
7 | """
8 | use Surface.Component
9 |
10 | @width_base_values ~w(1 2 3 4 5 6 7 8 9 10 11 12 auto)
11 |
12 | @doc "Css classes to propagate down to column div."
13 | prop class, :css_class, default: []
14 |
15 | @doc "Width for extra small viewports"
16 | prop width, :string, values: @width_base_values ++ ["base"]
17 |
18 | @doc "Width for small viewports"
19 | prop sm_width, :string, values: @width_base_values
20 |
21 | @doc "Width for medium viewports"
22 | prop md_width, :string, values: @width_base_values
23 |
24 | @doc "Width for large viewports"
25 | prop lg_width, :string, values: @width_base_values
26 |
27 | @doc "Width for extra large viewports"
28 | prop xl_width, :string, values: @width_base_values
29 |
30 | @doc "Width for extra extra large viewports"
31 | prop xxl_width, :string, values: @width_base_values
32 |
33 | slot default
34 |
35 | def render(assigns) do
36 | ~F"""
37 |
46 | <#slot />
47 |
48 | """
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/container.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Container do
2 | @moduledoc """
3 | A container class, lets you set breakpoints to adjust to mobile views etc.
4 |
5 | https://getbootstrap.com/docs/5.0/layout/containers/
6 | """
7 | use Surface.Component
8 | use SurfaceBootstrap.AriaBase
9 |
10 | @doc "Css classes to propagate down to button group."
11 | prop class, :css_class, default: []
12 |
13 | @doc """
14 | Container breakpoint, look at documentation from moduledoc for explanations.
15 | Unknown value or no value set defaults to 'normal' which translates to no
16 | breakpoint modifier (plain class "container").
17 | """
18 | prop breakpoint, :string, values: ~w(normal small medium large xl xxl fluid)
19 |
20 | slot default
21 |
22 | def render(assigns) do
23 | ~F"""
24 |
25 | <#slot />
26 |
27 | """
28 | end
29 |
30 | defp get_class(breakpoint) do
31 | case breakpoint do
32 | "small" ->
33 | "container-sm"
34 |
35 | "medium" ->
36 | "container-md"
37 |
38 | "large" ->
39 | "container-lg"
40 |
41 | "xl" ->
42 | "container-xl"
43 |
44 | "xll" ->
45 | "container-xxl"
46 |
47 | "fluid" ->
48 | "container-fluid"
49 |
50 | _ ->
51 | "container"
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/dropdown.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.DropDown do
2 | @moduledoc """
3 | The dropdown component.
4 |
5 | https://getbootstrap.com/docs/5.0/components/dropdowns/
6 |
7 | The `@wrapper` property changes the container wrapper for this component
8 | and is meant to be used to change which context the dropdown is used in.
9 |
10 | The values for `@wrapper` are:
11 | - default -- Gives a `
`
12 | - btn_group -- Gives a `
` (defaults to this automatically if `split == true`)
13 | - nav_item -- Gives a `
` (to be used when rendering a dropdown in a NavBar)
14 | - raw -- Special case that gives a `` with inner container `#Raw>
35 |
36 | """
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/date_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.DateInput do
2 | @moduledoc """
3 | The date input element as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#date_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.InputBase
10 |
11 | alias Surface.Components.Form.DateInput
12 |
13 | @doc "Largest date allowed, as enforced by client browser. Not validated by Elixir."
14 | prop max, :string
15 |
16 | @doc "Earliest date allowed, as enforced by client browser. Not validated by Elixir."
17 | prop min, :string
18 |
19 | @doc """
20 | Floating label?
21 | https://getbootstrap.com/docs/5.0/forms/floating-labels/
22 | """
23 | prop floating_label, :boolean
24 |
25 | def render(assigns) do
26 | ~F"""
27 |
28 | {raw(optional_div(assigns))}
29 |
30 |
37 |
38 | {help_text(assigns)}
39 | <#Raw :if={!@in_group}>
#Raw>
40 |
41 | """
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/date_time_local_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.DateTimeLocalInput do
2 | @moduledoc """
3 | The local datetime input element as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#datetime_local_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.InputBase
10 |
11 | alias Surface.Components.Form.DateTimeLocalInput
12 |
13 | @doc "Largest date allowed, as enforced by client browser. Not validated by Elixir."
14 | prop max, :string
15 |
16 | @doc "Earliest date allowed, as enforced by client browser. Not validated by Elixir."
17 | prop min, :string
18 |
19 | @doc """
20 | Floating label?
21 | https://getbootstrap.com/docs/5.0/forms/floating-labels/
22 | """
23 | prop floating_label, :boolean
24 |
25 | def render(assigns) do
26 | ~F"""
27 |
28 | {raw(optional_div(assigns))}
29 |
30 |
37 |
38 | {help_text(assigns)}
39 | <#Raw :if={!@in_group}>#Raw>
40 |
41 | """
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/email_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.EmailInput do
2 | @moduledoc """
3 | The email input element as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#email_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.TextInputBase
10 |
11 | alias Surface.Components.Form.EmailInput
12 |
13 | @doc "Max length of field, as enforced by client browser. Not validated by Elixir."
14 | prop maxlength, :integer
15 |
16 | @doc "Minimum length of field, as enforced by client browser. Not validated by Elixir."
17 | prop minlength, :integer
18 |
19 | def render(assigns) do
20 | ~F"""
21 |
22 | {raw(optional_div(assigns))}
23 |
24 |
31 |
32 |
33 | {help_text(assigns)}
34 | <#Raw :if={!@in_group}>#Raw>
35 |
36 | """
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/file_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.FileInput do
2 | @moduledoc """
3 | The file input component as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#file_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.InputBase
10 |
11 | alias Surface.Components.Form.FileInput
12 |
13 | def render(assigns) do
14 | ~F"""
15 |
16 | {raw(optional_div(assigns))}
17 |
18 |
25 |
26 | {help_text(assigns)}
27 | <#Raw :if={!@in_group}> #Raw>
28 |
29 | """
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/input_base.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.InputBase do
2 | defmacro __using__(_) do
3 | quote do
4 | import SurfaceBootstrap.Form.InputBase
5 |
6 | alias SurfaceBootstrap.Form.BootstrapErrorTag
7 | alias Surface.Components.Raw
8 | alias Surface.Components.Form.{FieldContext, Label}
9 |
10 | @doc "The the field on the changeset"
11 | prop field, :atom, required: true
12 |
13 | @doc "Pre populated value"
14 | prop value, :any
15 |
16 | @doc "The string label of the field"
17 | prop label, :string
18 |
19 | @doc "Class to apply to input"
20 | prop class, :css_class, default: []
21 |
22 | @doc "Any opts you want to pass on to internal `input` from `Phoenix.HTML.Form`"
23 | prop opts, :keyword, default: []
24 |
25 | @doc "Disable input"
26 | prop disabled, :boolean
27 |
28 | @doc """
29 | Help text, will be replaced by error text if changeset gets errors.
30 | Should not be used on inputs inside `InputGroup`
31 | """
32 | prop help_text, :string
33 |
34 | @doc "Triggered when the component loses focus"
35 | prop blur, :event, default: nil
36 |
37 | @doc "Triggered when the component receives focus"
38 | prop focus, :event
39 |
40 | @doc "Triggered when the component receives click"
41 | prop capture_click, :event
42 |
43 | @doc "Triggered when a button on the keyboard is pressed"
44 | prop keydown, :event
45 |
46 | @doc "Triggered when a button on the keyboard is released"
47 | prop keyup, :event
48 |
49 | @doc "Margin below form control, to create spacing. Defaults to 3. Is ignored if input is inside an `InputGroup`."
50 | prop spacing, :string, default: "3", values: ~w(1 2 3 4 5)
51 |
52 | @doc "Size of the input, defaults to nil(normal)"
53 | prop size, :string, values: ~w(small large)
54 |
55 | @doc "Is input in group? Set to true if used in `InputGroup`, defaults to false"
56 | prop in_group, :boolean, default: false
57 |
58 | def update(assigns, socket) do
59 | socket = assign(socket, assigns)
60 |
61 | case get_in(assigns, [:__context__, {SurfaceBootstrap.InputGroup, :in_group}]) do
62 | true ->
63 | {:ok, assign(socket, :in_group, true)}
64 |
65 | _ ->
66 | {:ok, socket}
67 | end
68 | end
69 |
70 | defp optional_div(%{in_group: true}) do
71 | ""
72 | end
73 |
74 | defp optional_div(%{in_group: in_group} = assigns) do
75 | has_left_right = Map.get(assigns, :show_value) in ["left", "right"]
76 | has_floating_label = assigns[:floating_label] && !has_left_right
77 |
78 | class =
79 | css_class(
80 | "mb-#{assigns[:spacing]}": assigns[:spacing],
81 | "form-floating": has_floating_label,
82 | "input-group": !in_group && has_left_right
83 | )
84 |
85 | ~s()
86 | end
87 |
88 | defp help_text(%{help_text: nil}), do: nil
89 |
90 | defp help_text(%{help_text: text}) do
91 | """
92 |
93 | #{raw(text)}
94 |
95 | """
96 | end
97 | end
98 | end
99 |
100 | import SurfaceBootstrap.Form, only: [field_has_error?: 2, field_has_change?: 2]
101 |
102 | def has_error?(assigns) do
103 | %{__context__: %{{Surface.Components.Form, :form} => form}} = assigns
104 |
105 | field_has_error?(form, assigns.field)
106 | end
107 |
108 | def has_change?(assigns) do
109 | %{__context__: %{{Surface.Components.Form, :form} => form}} = assigns
110 |
111 | field_has_change?(form, assigns.field)
112 | end
113 |
114 | def input_classes(assigns) do
115 | [
116 | "form-control",
117 | form_size(assigns[:size]),
118 | "is-invalid": has_change?(assigns) && has_error?(assigns),
119 | "is-valid": has_change?(assigns) && !has_error?(assigns),
120 | "form-control-plaintext": assigns[:readonly] && assigns[:readonly_plaintext]
121 | ]
122 | end
123 |
124 | def form_size(size) do
125 | case size do
126 | "large" ->
127 | "form-control-lg"
128 |
129 | "small" ->
130 | "form-control-sm"
131 |
132 | _ ->
133 | nil
134 | end
135 | end
136 |
137 | def default_core_input_opts(assigns) do
138 | Map.take(assigns, [
139 | :disabled,
140 | :readonly,
141 | :max,
142 | :min,
143 | :step,
144 | :minlength,
145 | :maxlength,
146 | :rows,
147 | :placeholder
148 | ])
149 | |> Keyword.new()
150 | end
151 |
152 | def default_surface_input_props(assigns) do
153 | Map.take(assigns, [
154 | :blur,
155 | :focus,
156 | :capture_click,
157 | :keydown,
158 | :keyup
159 | ])
160 | |> Keyword.new()
161 | end
162 | end
163 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/input_group.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.InputGroup do
2 | @moduledoc """
3 | Input group
4 |
5 | https://getbootstrap.com/docs/5.0/forms/input-group/
6 | """
7 | use Surface.Component
8 |
9 | @doc "Margin below form group, to create spacing. Defaults to 3"
10 | prop spacing, :string, default: "3", values: ~w(1 2 3 4 5)
11 |
12 | @doc "Size of the input group, defaults to nil(normal)"
13 | prop size, :string, values: ~w(small large)
14 |
15 | @doc "Css classes to propagate down to input group."
16 | prop class, :css_class, default: []
17 |
18 | @doc "Label for input group"
19 | prop label, :string
20 |
21 | slot default, args: [:in_group]
22 |
23 | def render(assigns) do
24 | ~F"""
25 |
26 |
32 |
33 | <#slot />
34 |
35 |
36 | """
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/input_group_text.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.InputGroupText do
2 | @moduledoc """
3 | Input group text
4 |
5 | https://getbootstrap.com/docs/5.0/forms/input-group/
6 | """
7 | use Surface.Component
8 |
9 | slot default
10 |
11 | def render(assigns) do
12 | ~F"""
13 |
14 | <#slot />
15 |
16 | """
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/number_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.NumberInput do
2 | @moduledoc """
3 | The number input element as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#number_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.TextInputBase
10 |
11 | alias Surface.Components.Form.NumberInput
12 |
13 | @doc "Largest number allowed, as enforced by client browser. Not validated by Elixir."
14 | prop max, :integer
15 |
16 | @doc "Smallest number allowed, as enforced by client browser. Not validated by Elixir."
17 | prop min, :integer
18 |
19 | @doc "A stepping interval to use when using up and down arrows to adjust the value, as well as for validation"
20 | prop step, :integer
21 |
22 | def render(assigns) do
23 | ~F"""
24 |
25 | {raw(optional_div(assigns))}
26 |
27 |
34 |
35 | {help_text(assigns)}
36 | <#Raw :if={!@in_group}> #Raw>
37 |
38 | """
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/surface_bootstrap/form/password_input.ex:
--------------------------------------------------------------------------------
1 | defmodule SurfaceBootstrap.Form.PasswordInput do
2 | @moduledoc """
3 | The password field component as defined here:
4 | - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#password_input/3
5 | - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password
6 | """
7 |
8 | use Surface.Component
9 | use SurfaceBootstrap.Form.TextInputBase
10 |
11 | alias Surface.Components.Form.PasswordInput
12 |
13 | @doc "Max length of field, as enforced by client browser. Not validated by Elixir."
14 | prop maxlength, :integer
15 |
16 | @doc "Minimum length of field, as enforced by client browser. Not validated by Elixir."
17 | prop minlength, :integer
18 |
19 | def render(assigns) do
20 | ~F"""
21 |