Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move code from surface #1

Merged
merged 5 commits into from
Feb 22, 2024
Merged
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
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Used by "mix format"
[
import_deps: [:phoenix, :surface],
plugins: [Phoenix.LiveView.HTMLFormatter],
line_length: 115,
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ jobs:
fail-fast: false
matrix:
include:
- elixir: '1.13.4'
otp: '24.0'
old_deps: true
- elixir: '1.13.4'
otp: '25.0'
- elixir: '1.14.0'
Expand All @@ -32,6 +35,9 @@ jobs:
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- name: Use old lock file
if: matrix.old_deps
run: mv mix.lock.old mix.lock
- name: Deps and _build cache
uses: actions/cache@v4
id: deps-cache
Expand Down
22 changes: 22 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# MIT License

Copyright (c) 2019 Marlus Saraiva

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
108 changes: 108 additions & 0 deletions lib/surface/components/form.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
defmodule Surface.Components.Form do
@moduledoc """
Defines a **form** that lets the user submit information.

Provides a wrapper for `Phoenix.HTML.Form.form_for/3`. Additionally,
adds the form instance that is returned by `form_for/3` into the context,
making it available to any child input.

All options passed via `opts` will be sent to `form_for/3`, `for`
and `action` can be set directly and will override anything in `opts`.

"""

use Surface.Component

import Surface.Components.Utils, only: [opts_to_phx_opts: 1]
import Surface.Components.Form.Utils, only: [props_to_opts: 2, props_to_attr_opts: 2]

@doc """
The ID of the form attribute.
If an ID is given, all form inputs will also be prefixed by the given ID.
Required to enable form recovery following crashes or disconnects.
"""
prop id, :string

@doc "Atom or changeset to inform the form data"
prop for, :any, required: true

@doc "URL to where the form is submitted"
prop action, :string, default: "#"

@doc "The server side parameter in which all parameters will be gathered."
prop as, :atom

@doc "Method to be used when submitting the form."
prop method, :string

@doc "When true, sets enctype to \"multipart/form-data\". Required when uploading files."
prop multipart, :boolean, default: false

@doc """
For \"post\" requests, the form tag will automatically include an input
tag with name _csrf_token. When set to false, this is disabled.
"""
prop csrf_token, :any

@doc """
Trigger a standard form submit on DOM patch to the URL specified in the form's standard action
attribute.
This is useful to perform pre-final validation of a LiveView form submit before posting to a
controller route for operations that require Plug session mutation.
"""
prop trigger_action, :boolean

@doc "Keyword list of errors for the form."
prop errors, :keyword

@doc "Keyword list with options to be passed down to `Phoenix.HTML.Tag.tag/2`"
prop opts, :keyword, default: []

@doc "Class or classes to apply to the form"
prop class, :css_class

@doc "Triggered when the form is changed"
prop change, :event

@doc "Triggered when the form is submitted"
prop submit, :event

@doc """
Triggered when the form is being recovered.
Use this event to enable specialized recovery when extra recovery handling
on the server is required.
"""
prop auto_recover, :event

@doc "The content of the `<form>`"
slot default, arg: %{form: :form}

def render(assigns) do
attr_opts = props_to_attr_opts(assigns, class: get_config(:default_class))

form_opts =
assigns
|> props_to_opts([:as, :method, :multipart, :csrf_token, :errors, :trigger_action])
|> opts_to_phx_opts()

event_opts =
event_to_opts(assigns.change, :"phx-change") ++
event_to_opts(assigns.submit, :"phx-submit") ++
event_to_opts(assigns.auto_recover, :"phx-auto-recover")

opts =
assigns.opts
|> Keyword.merge(attr_opts)
|> Keyword.merge(form_opts)
|> Keyword.merge(event_opts)

assigns = assign(assigns, opts: opts)

~F"""
<!-- XXX -->
<.form :let={form} for={@for} action={@action} {...@opts}>
<#slot {@default, form: form} context_put={__MODULE__, form: form}/>
</.form>
"""
end
end
48 changes: 48 additions & 0 deletions lib/surface/components/form/checkbox.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule Surface.Components.Form.Checkbox do
@moduledoc """
Defines a checkbox.

Provides a wrapper for Phoenix.HTML.Form's `checkbox/3` function.

All options passed via `opts` will be sent to `checkbox/3`, `value` and
`class` can be set directly and will override anything in `opts`.


## Examples

```
<Checkbox form="user" field="color" opts={autofocus: "autofocus"} />
```
"""

use Surface.Components.Form.Input

import Phoenix.HTML.Form, only: [checkbox: 3]
import Surface.Components.Utils, only: [events_to_opts: 1]
import Surface.Components.Form.Utils

@doc "The value to be sent when the checkbox is checked. Defaults to \"true\""
prop checked_value, :any, default: true

@doc "Controls if this function will generate a hidden input to submit the unchecked value or not, defaults to \"true\"."
prop hidden_input, :boolean, default: true

@doc "The value to be sent when the checkbox is unchecked, defaults to \"false\"."
prop unchecked_value, :any, default: false

def render(assigns) do
helper_opts = props_to_opts(assigns, [:checked_value, :hidden_input, :unchecked_value, :value])
attr_opts = props_to_attr_opts(assigns, class: get_default_class())
event_opts = events_to_opts(assigns)

opts =
assigns.opts
|> Keyword.merge(helper_opts)
|> Keyword.merge(attr_opts)
|> Keyword.merge(event_opts)

assigns = assign(assigns, :opts, opts)

~F[{checkbox(@form, @field, @opts)}]
end
end
40 changes: 40 additions & 0 deletions lib/surface/components/form/color_input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Surface.Components.Form.ColorInput do
@moduledoc """
An input field that let the user specify a **color**, either with a
text field or a color picker interface.

Provides a wrapper for Phoenix.HTML.Form's `color_input/3` function.

All options passed via `opts` will be sent to `color_input/3`, `value` and
`class` can be set directly and will override anything in `opts`.


## Examples

```
<ColorInput form="user" field="color" opts={autofocus: "autofocus"} />
```
"""

use Surface.Components.Form.Input

import Phoenix.HTML.Form, only: [color_input: 3]
import Surface.Components.Utils, only: [events_to_opts: 1]
import Surface.Components.Form.Utils

def render(assigns) do
helper_opts = props_to_opts(assigns)
attr_opts = props_to_attr_opts(assigns, [:value, class: get_default_class()])
event_opts = events_to_opts(assigns)

opts =
assigns.opts
|> Keyword.merge(helper_opts)
|> Keyword.merge(attr_opts)
|> Keyword.merge(event_opts)

assigns = assign(assigns, :opts, opts)

~F[{color_input(@form, @field, @opts)}]
end
end
40 changes: 40 additions & 0 deletions lib/surface/components/form/date_input.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Surface.Components.Form.DateInput do
@moduledoc """
An input field that let the user enter a **date**, either with a text field
or a date picker interface.

Provides a wrapper for Phoenix.HTML.Form's `date_input/3` function.

All options passed via `opts` will be sent to `date_input/3`, `value` and
`class` can be set directly and will override anything in `opts`.


## Examples

```
<DateInput form="user" field="birthday" opts={autofocus: "autofocus"} />
```
"""

use Surface.Components.Form.Input

import Phoenix.HTML.Form, only: [date_input: 3]
import Surface.Components.Utils, only: [events_to_opts: 1]
import Surface.Components.Form.Utils

def render(assigns) do
helper_opts = props_to_opts(assigns)
attr_opts = props_to_attr_opts(assigns, [:value, class: get_default_class()])
event_opts = events_to_opts(assigns)

opts =
assigns.opts
|> Keyword.merge(helper_opts)
|> Keyword.merge(attr_opts)
|> Keyword.merge(event_opts)

assigns = assign(assigns, opts: opts)

~F[{date_input(@form, @field, @opts)}]
end
end
79 changes: 79 additions & 0 deletions lib/surface/components/form/date_select.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule Surface.Components.Form.DateSelect do
@moduledoc """
Generates select tags for date.

Provides a wrapper for Phoenix.HTML.Form's `date_select/3` function.

All options passed via `opts` will be sent to `date_select/3`,
`value`, `default`, `year`, `month`, `day` and `builder`
can be set directly and will override anything in `opts`.

## Examples

```
<DateSelect form="user" field="born_at" />

<Form for={:user}>
<DateSelect field={:born_at} />
</Form>
```
"""

use Surface.Component

import Phoenix.HTML.Form, only: [date_select: 3]
import Surface.Components.Form.Utils

@doc "The form identifier"
prop form, :form, from_context: {Surface.Components.Form, :form}

@doc "The id prefix for underlying select fields"
prop id, :string

@doc "The name prefix for underlying select fields"
prop name, :string

@doc "The field name"
prop field, :any, from_context: {Surface.Components.Form.Field, :field}

@doc "Value to pre-populate the select"
prop value, :any

@doc "Default value to use when none was given in 'value' and none is available in the form data"
prop default, :any

@doc "Options passed to the underlying 'year' select"
prop year, :keyword

@doc "Options passed to the underlying 'month' select"
prop month, :keyword

@doc "Options passed to the underlying 'day' select"
prop day, :keyword

@doc """
Specify how the select can be build. It must be a function that receives a builder
that should be invoked with the select name and a set of options.
"""
prop builder, :fun

@doc "Options list"
prop opts, :keyword, default: []

def render(assigns) do
helper_opts =
assigns
|> props_to_opts([:value, :default, :year, :month, :day, :builder])
|> parse_css_class_for(:year)
|> parse_css_class_for(:month)
|> parse_css_class_for(:day)

opts =
assigns.opts
|> Keyword.merge(helper_opts)

assigns = assign(assigns, opts: opts)

~F[{date_select(@form, @field, @opts)}]
end
end
Loading
Loading