-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(component): add Modal component
- Loading branch information
Showing
5 changed files
with
223 additions
and
3 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
defmodule Orange.Component.Modal do | ||
@moduledoc """ | ||
An modal/dialog component which renders an overlay in the middle of the viewport. | ||
## Attributes | ||
- `:open` - Whether the modal is open or not. This attribute is required. | ||
- `:offset_x` - The offset from the left and right edge of the screen. This attribute is required. | ||
- `:offset_y` - The offset from the top and bottom edge of the screen. This attribute is required. | ||
> #### Info {: .info} | ||
> | ||
> When the offset_x/y is too big for the terminal size, the modal will not be rendered. | ||
- `:children` - The content of the modal. This attribute is optional. | ||
## Examples | ||
defmodule Example do | ||
@behaviour Orange.Component | ||
import Orange.Macro | ||
@impl true | ||
def init(_attrs), do: %{state: %{search_value: ""}} | ||
@impl true | ||
def render(state, _attrs, update) do | ||
modal_content = | ||
rect do | ||
"foo" | ||
"bar" | ||
end | ||
rect do | ||
line do | ||
"Displaying modal..." | ||
end | ||
{ | ||
Orange.Component.Modal, | ||
offset_x: 8, | ||
offset_y: 4, | ||
children: modal_content, | ||
open: true | ||
} | ||
end | ||
end | ||
end | ||
![rendered result](assets/modal-example.png) | ||
""" | ||
|
||
@behaviour Orange.Component | ||
|
||
import Orange.Macro | ||
|
||
@impl true | ||
def init(_attrs), do: %{state: nil} | ||
|
||
@impl true | ||
def render(_state, attrs, _update) do | ||
{width, height} = terminal_impl().terminal_size() | ||
|
||
offset_x = attrs[:offset_x] | ||
offset_y = attrs[:offset_y] | ||
|
||
# Plus 2 for the border | ||
if attrs[:open] && width > offset_x * 2 + 2 && height > offset_y * 2 + 2 do | ||
rect position: {:fixed, offset_y, offset_x, offset_y, offset_x}, style: [border: true] do | ||
attrs[:children] | ||
end | ||
end | ||
end | ||
|
||
defp terminal_impl(), do: Application.get_env(:orange, :terminal, Orange.Terminal) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
defmodule Orange.Component.ModalTest do | ||
use ExUnit.Case | ||
import Mox | ||
|
||
alias Orange.Renderer.Buffer | ||
alias Orange.{Terminal, RuntimeTestHelper} | ||
|
||
setup_all do | ||
Mox.defmock(Orange.MockTerminal, for: Terminal) | ||
Application.put_env(:orange, :terminal, Orange.MockTerminal) | ||
|
||
:ok | ||
end | ||
|
||
setup :set_mox_from_context | ||
setup :verify_on_exit! | ||
|
||
test ":open is true" do | ||
RuntimeTestHelper.setup_mock_terminal(Orange.MockTerminal, | ||
terminal_size: {20, 15} | ||
) | ||
|
||
buffer = RuntimeTestHelper.dry_render_once({__MODULE__.Modal, open: true}) | ||
|
||
assert Buffer.to_string(buffer) === """ | ||
Displaying modal...- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
----┌──────────┐---- | ||
----│foo-------│---- | ||
----│bar-------│---- | ||
----│----------│---- | ||
----│----------│---- | ||
----│----------│---- | ||
----└──────────┘---- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
--------------------\ | ||
""" | ||
end | ||
|
||
test ":open is false" do | ||
RuntimeTestHelper.setup_mock_terminal(Orange.MockTerminal, | ||
terminal_size: {20, 15} | ||
) | ||
|
||
buffer = RuntimeTestHelper.dry_render_once({__MODULE__.Modal, open: false}) | ||
|
||
assert Buffer.to_string(buffer) === """ | ||
Displaying modal...- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
--------------------\ | ||
""" | ||
end | ||
|
||
test "offset_x is too big for width" do | ||
RuntimeTestHelper.setup_mock_terminal(Orange.MockTerminal, | ||
terminal_size: {20, 6} | ||
) | ||
|
||
buffer = | ||
RuntimeTestHelper.dry_render_once({__MODULE__.Modal, open: true, offset_x: 10, offset_y: 1}) | ||
|
||
assert Buffer.to_string(buffer) === """ | ||
Displaying modal...- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
--------------------\ | ||
""" | ||
end | ||
|
||
test "offset_y is too big for height" do | ||
RuntimeTestHelper.setup_mock_terminal(Orange.MockTerminal, | ||
terminal_size: {20, 12} | ||
) | ||
|
||
buffer = RuntimeTestHelper.dry_render_once({__MODULE__.Modal, open: true, offset_y: 6}) | ||
|
||
assert Buffer.to_string(buffer) === """ | ||
Displaying modal...- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
-------------------- | ||
--------------------\ | ||
""" | ||
end | ||
|
||
defmodule Modal do | ||
@behaviour Orange.Component | ||
|
||
import Orange.Macro | ||
alias Orange.Component | ||
|
||
@impl true | ||
def init(_attrs), do: %{state: nil} | ||
|
||
@impl true | ||
def render(_state, attrs, _update) do | ||
offset_x = Keyword.get(attrs, :offset_x, 4) | ||
offset_y = Keyword.get(attrs, :offset_y, 4) | ||
|
||
modal_content = | ||
rect do | ||
"foo" | ||
"bar" | ||
end | ||
|
||
rect style: [width: "100%", height: "100%"] do | ||
line do | ||
"Displaying modal..." | ||
end | ||
|
||
{ | ||
Component.Modal, | ||
offset_x: offset_x, offset_y: offset_y, children: modal_content, open: attrs[:open] | ||
} | ||
end | ||
end | ||
end | ||
end |