diff --git a/demo/.formatter.exs b/demo/.formatter.exs
index cd4b3bc6..7f08245b 100644
--- a/demo/.formatter.exs
+++ b/demo/.formatter.exs
@@ -1,5 +1,5 @@
[
import_deps: [:phoenix],
- inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
- line_length: 80
+ plugins: [Phoenix.LiveView.HTMLFormatter],
+ inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "storybook/**/*.exs"]
]
diff --git a/demo/.gitignore b/demo/.gitignore
index 085507ed..2f07cea1 100644
--- a/demo/.gitignore
+++ b/demo/.gitignore
@@ -19,6 +19,9 @@ erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
+# Temporary files, for example, from tests.
+/tmp/
+
# Ignore package tarball (built via "mix hex.build").
demo-*.tar
diff --git a/demo/README.md b/demo/README.md
index 1cd5e80d..5c399184 100644
--- a/demo/README.md
+++ b/demo/README.md
@@ -3,6 +3,7 @@
To start your Phoenix server:
- Install dependencies with `mix deps.get`
-- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
+- Start Phoenix endpoint with `mix phx.server` or inside IEx with
+ `iex -S mix phx.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
diff --git a/demo/assets/css/app.css b/demo/assets/css/app.css
index 25aa8024..30f36f3a 100644
--- a/demo/assets/css/app.css
+++ b/demo/assets/css/app.css
@@ -1,151 +1,645 @@
-/* This file is for your main application CSS */
-@import "./phoenix.css";
+/*!
+ * Milligram v1.4.1
+ * https://milligram.io
+ *
+ * Copyright (c) 2020 CJ Patoilo
+ * Licensed under the MIT license
+ */
-/* Alerts and form errors used by phx.new */
-.alert {
- padding: 15px;
- margin-bottom: 20px;
- border: 1px solid transparent;
- border-radius: 4px;
+*,
+*:after,
+*:before {
+ box-sizing: inherit;
}
-.alert-info {
- color: #31708f;
- background-color: #d9edf7;
- border-color: #bce8f1;
+html {
+ box-sizing: border-box;
+ font-size: 62.5%;
}
-.alert-warning {
- color: #8a6d3b;
- background-color: #fcf8e3;
- border-color: #faebcc;
+body {
+ color: #606c76;
+ font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
+ font-size: 1.6em;
+ font-weight: 300;
+ letter-spacing: 0.01em;
+ line-height: 1.6;
}
-.alert-danger {
- color: #a94442;
- background-color: #f2dede;
- border-color: #ebccd1;
+blockquote {
+ border-left: 0.3rem solid #d1d1d1;
+ margin-left: 0;
+ margin-right: 0;
+ padding: 1rem 1.5rem;
}
-.alert p {
+blockquote *:last-child {
margin-bottom: 0;
}
-.alert:empty {
- display: none;
+.button,
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ background-color: #9b4dca;
+ border: 0.1rem solid #9b4dca;
+ border-radius: 0.4rem;
+ color: #fff;
+ cursor: pointer;
+ display: inline-block;
+ font-size: 1.1rem;
+ font-weight: 700;
+ height: 3.8rem;
+ letter-spacing: 0.1rem;
+ line-height: 3.8rem;
+ padding: 0 3rem;
+ text-align: center;
+ text-decoration: none;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.button:focus,
+.button:hover,
+button:focus,
+button:hover,
+input[type="button"]:focus,
+input[type="button"]:hover,
+input[type="reset"]:focus,
+input[type="reset"]:hover,
+input[type="submit"]:focus,
+input[type="submit"]:hover {
+ background-color: #606c76;
+ border-color: #606c76;
+ color: #fff;
+ outline: 0;
+}
+
+.button[disabled],
+button[disabled],
+input[type="button"][disabled],
+input[type="reset"][disabled],
+input[type="submit"][disabled] {
+ cursor: default;
+ opacity: 0.5;
+}
+
+.button[disabled]:focus,
+.button[disabled]:hover,
+button[disabled]:focus,
+button[disabled]:hover,
+input[type="button"][disabled]:focus,
+input[type="button"][disabled]:hover,
+input[type="reset"][disabled]:focus,
+input[type="reset"][disabled]:hover,
+input[type="submit"][disabled]:focus,
+input[type="submit"][disabled]:hover {
+ background-color: #9b4dca;
+ border-color: #9b4dca;
+}
+
+.button.button-outline,
+button.button-outline,
+input[type="button"].button-outline,
+input[type="reset"].button-outline,
+input[type="submit"].button-outline {
+ background-color: transparent;
+ color: #9b4dca;
+}
+
+.button.button-outline:focus,
+.button.button-outline:hover,
+button.button-outline:focus,
+button.button-outline:hover,
+input[type="button"].button-outline:focus,
+input[type="button"].button-outline:hover,
+input[type="reset"].button-outline:focus,
+input[type="reset"].button-outline:hover,
+input[type="submit"].button-outline:focus,
+input[type="submit"].button-outline:hover {
+ background-color: transparent;
+ border-color: #606c76;
+ color: #606c76;
+}
+
+.button.button-outline[disabled]:focus,
+.button.button-outline[disabled]:hover,
+button.button-outline[disabled]:focus,
+button.button-outline[disabled]:hover,
+input[type="button"].button-outline[disabled]:focus,
+input[type="button"].button-outline[disabled]:hover,
+input[type="reset"].button-outline[disabled]:focus,
+input[type="reset"].button-outline[disabled]:hover,
+input[type="submit"].button-outline[disabled]:focus,
+input[type="submit"].button-outline[disabled]:hover {
+ border-color: inherit;
+ color: #9b4dca;
+}
+
+.button.button-clear,
+button.button-clear,
+input[type="button"].button-clear,
+input[type="reset"].button-clear,
+input[type="submit"].button-clear {
+ background-color: transparent;
+ border-color: transparent;
+ color: #9b4dca;
+}
+
+.button.button-clear:focus,
+.button.button-clear:hover,
+button.button-clear:focus,
+button.button-clear:hover,
+input[type="button"].button-clear:focus,
+input[type="button"].button-clear:hover,
+input[type="reset"].button-clear:focus,
+input[type="reset"].button-clear:hover,
+input[type="submit"].button-clear:focus,
+input[type="submit"].button-clear:hover {
+ background-color: transparent;
+ border-color: transparent;
+ color: #606c76;
+}
+
+.button.button-clear[disabled]:focus,
+.button.button-clear[disabled]:hover,
+button.button-clear[disabled]:focus,
+button.button-clear[disabled]:hover,
+input[type="button"].button-clear[disabled]:focus,
+input[type="button"].button-clear[disabled]:hover,
+input[type="reset"].button-clear[disabled]:focus,
+input[type="reset"].button-clear[disabled]:hover,
+input[type="submit"].button-clear[disabled]:focus,
+input[type="submit"].button-clear[disabled]:hover {
+ color: #9b4dca;
}
-.invalid-feedback {
- color: #a94442;
+code {
+ background: #f4f5f6;
+ border-radius: 0.4rem;
+ font-size: 86%;
+ margin: 0 0.2rem;
+ padding: 0.2rem 0.5rem;
+ white-space: nowrap;
+}
+
+pre {
+ background: #f4f5f6;
+ border-left: 0.3rem solid #9b4dca;
+ overflow-y: hidden;
+}
+
+pre > code {
+ border-radius: 0;
display: block;
- margin: -1rem 0 2rem;
+ padding: 1rem 1.5rem;
+ white-space: pre;
}
-/* LiveView specific classes for your customization */
-.phx-no-feedback.invalid-feedback,
-.phx-no-feedback .invalid-feedback {
- display: none;
+hr {
+ border: 0;
+ border-top: 0.1rem solid #f4f5f6;
+ margin: 3rem 0;
}
-.phx-click-loading {
- opacity: 0.5;
- transition: opacity 1s ease-out;
+input[type="color"],
+input[type="date"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="email"],
+input[type="month"],
+input[type="number"],
+input[type="password"],
+input[type="search"],
+input[type="tel"],
+input[type="text"],
+input[type="url"],
+input[type="week"],
+input:not([type]),
+textarea,
+select {
+ -webkit-appearance: none;
+ background-color: transparent;
+ border: 0.1rem solid #d1d1d1;
+ border-radius: 0.4rem;
+ box-shadow: none;
+ box-sizing: inherit;
+ height: 3.8rem;
+ padding: 0.6rem 1rem 0.7rem;
+ width: 100%;
+}
+
+input[type="color"]:focus,
+input[type="date"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="email"]:focus,
+input[type="month"]:focus,
+input[type="number"]:focus,
+input[type="password"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="text"]:focus,
+input[type="url"]:focus,
+input[type="week"]:focus,
+input:not([type]):focus,
+textarea:focus,
+select:focus {
+ border-color: #9b4dca;
+ outline: 0;
+}
+
+select {
+ background: url('data:image/svg+xml;utf8,')
+ center right no-repeat;
+ padding-right: 3rem;
+}
+
+select:focus {
+ background-image: url('data:image/svg+xml;utf8,');
+}
+
+select[multiple] {
+ background: none;
+ height: auto;
+}
+
+textarea {
+ min-height: 6.5rem;
+}
+
+label,
+legend {
+ display: block;
+ font-size: 1.6rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+}
+
+fieldset {
+ border-width: 0;
+ padding: 0;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ display: inline;
}
-.phx-loading {
- cursor: wait;
+.label-inline {
+ display: inline-block;
+ font-weight: normal;
+ margin-left: 0.5rem;
}
-.phx-modal {
- opacity: 1 !important;
- position: fixed;
- z-index: 1;
- left: 0;
- top: 0;
+.container {
+ margin: 0 auto;
+ max-width: 112rem;
+ padding: 0 2rem;
+ position: relative;
width: 100%;
- height: 100%;
- overflow: auto;
- background-color: rgba(0, 0, 0, 0.4);
}
-.phx-modal-content {
- background-color: #fefefe;
- margin: 15vh auto;
- padding: 20px;
- border: 1px solid #888;
- width: 80%;
+.row {
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ width: 100%;
}
-.phx-modal-close {
- color: #aaa;
- float: right;
- font-size: 28px;
- font-weight: bold;
+.row.row-no-padding {
+ padding: 0;
}
-.phx-modal-close:hover,
-.phx-modal-close:focus {
- color: black;
- text-decoration: none;
- cursor: pointer;
+.row.row-no-padding > .column {
+ padding: 0;
}
-.fade-in-scale {
- animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys;
+.row.row-wrap {
+ flex-wrap: wrap;
}
-.fade-out-scale {
- animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys;
+.row.row-top {
+ align-items: flex-start;
}
-.fade-in {
- animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys;
+.row.row-bottom {
+ align-items: flex-end;
}
-.fade-out {
- animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
+.row.row-center {
+ align-items: center;
}
-@keyframes fade-in-scale-keys {
- 0% {
- scale: 0.95;
- opacity: 0;
- }
+.row.row-stretch {
+ align-items: stretch;
+}
- 100% {
- scale: 1;
- opacity: 1;
- }
+.row.row-baseline {
+ align-items: baseline;
}
-@keyframes fade-out-scale-keys {
- 0% {
- scale: 1;
- opacity: 1;
- }
+.row .column {
+ display: block;
+ flex: 1 1 auto;
+ margin-left: 0;
+ max-width: 100%;
+ width: 100%;
+}
- 100% {
- scale: 0.95;
- opacity: 0;
- }
+.row .column.column-offset-10 {
+ margin-left: 10%;
}
-@keyframes fade-in-keys {
- 0% {
- opacity: 0;
- }
+.row .column.column-offset-20 {
+ margin-left: 20%;
+}
- 100% {
- opacity: 1;
- }
+.row .column.column-offset-25 {
+ margin-left: 25%;
+}
+
+.row .column.column-offset-33,
+.row .column.column-offset-34 {
+ margin-left: 33.3333%;
+}
+
+.row .column.column-offset-40 {
+ margin-left: 40%;
+}
+
+.row .column.column-offset-50 {
+ margin-left: 50%;
+}
+
+.row .column.column-offset-60 {
+ margin-left: 60%;
+}
+
+.row .column.column-offset-66,
+.row .column.column-offset-67 {
+ margin-left: 66.6666%;
+}
+
+.row .column.column-offset-75 {
+ margin-left: 75%;
+}
+
+.row .column.column-offset-80 {
+ margin-left: 80%;
+}
+
+.row .column.column-offset-90 {
+ margin-left: 90%;
+}
+
+.row .column.column-10 {
+ flex: 0 0 10%;
+ max-width: 10%;
+}
+
+.row .column.column-20 {
+ flex: 0 0 20%;
+ max-width: 20%;
+}
+
+.row .column.column-25 {
+ flex: 0 0 25%;
+ max-width: 25%;
}
-@keyframes fade-out-keys {
- 0% {
- opacity: 1;
+.row .column.column-33,
+.row .column.column-34 {
+ flex: 0 0 33.3333%;
+ max-width: 33.3333%;
+}
+
+.row .column.column-40 {
+ flex: 0 0 40%;
+ max-width: 40%;
+}
+
+.row .column.column-50 {
+ flex: 0 0 50%;
+ max-width: 50%;
+}
+
+.row .column.column-60 {
+ flex: 0 0 60%;
+ max-width: 60%;
+}
+
+.row .column.column-66,
+.row .column.column-67 {
+ flex: 0 0 66.6666%;
+ max-width: 66.6666%;
+}
+
+.row .column.column-75 {
+ flex: 0 0 75%;
+ max-width: 75%;
+}
+
+.row .column.column-80 {
+ flex: 0 0 80%;
+ max-width: 80%;
+}
+
+.row .column.column-90 {
+ flex: 0 0 90%;
+ max-width: 90%;
+}
+
+.row .column .column-top {
+ align-self: flex-start;
+}
+
+.row .column .column-bottom {
+ align-self: flex-end;
+}
+
+.row .column .column-center {
+ align-self: center;
+}
+
+@media (min-width: 40rem) {
+ .row {
+ flex-direction: row;
+ margin-left: -1rem;
+ width: calc(100% + 2rem);
+ }
+ .row .column {
+ margin-bottom: inherit;
+ padding: 0 1rem;
}
+}
+
+a {
+ color: #9b4dca;
+ text-decoration: none;
+}
+
+a:focus,
+a:hover {
+ color: #606c76;
+}
+
+dl,
+ol,
+ul {
+ list-style: none;
+ margin-top: 0;
+ padding-left: 0;
+}
+
+dl dl,
+dl ol,
+dl ul,
+ol dl,
+ol ol,
+ol ul,
+ul dl,
+ul ol,
+ul ul {
+ font-size: 90%;
+ margin: 1.5rem 0 1.5rem 3rem;
+}
+
+ol {
+ list-style: decimal inside;
+}
+
+ul {
+ list-style: circle inside;
+}
+
+.button,
+button,
+dd,
+dt,
+li {
+ margin-bottom: 1rem;
+}
+
+fieldset,
+input,
+select,
+textarea {
+ margin-bottom: 1.5rem;
+}
- 100% {
- opacity: 0;
+blockquote,
+dl,
+figure,
+form,
+ol,
+p,
+pre,
+table,
+ul {
+ margin-bottom: 2.5rem;
+}
+
+table {
+ border-spacing: 0;
+ display: block;
+ overflow-x: auto;
+ text-align: left;
+ width: 100%;
+}
+
+td,
+th {
+ border-bottom: 0.1rem solid #e1e1e1;
+ padding: 1.2rem 1.5rem;
+}
+
+td:first-child,
+th:first-child {
+ padding-left: 0;
+}
+
+td:last-child,
+th:last-child {
+ padding-right: 0;
+}
+
+@media (min-width: 40rem) {
+ table {
+ display: table;
+ overflow-x: initial;
}
}
+
+b,
+strong {
+ font-weight: bold;
+}
+
+p {
+ margin-top: 0;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: 300;
+ letter-spacing: -0.1rem;
+ margin-bottom: 2rem;
+ margin-top: 0;
+}
+
+h1 {
+ font-size: 4.6rem;
+ line-height: 1.2;
+}
+
+h2 {
+ font-size: 3.6rem;
+ line-height: 1.25;
+}
+
+h3 {
+ font-size: 2.8rem;
+ line-height: 1.3;
+}
+
+h4 {
+ font-size: 2.2rem;
+ letter-spacing: -0.08rem;
+ line-height: 1.35;
+}
+
+h5 {
+ font-size: 1.8rem;
+ letter-spacing: -0.05rem;
+ line-height: 1.5;
+}
+
+h6 {
+ font-size: 1.6rem;
+ letter-spacing: 0;
+ line-height: 1.4;
+}
+
+img {
+ max-width: 100%;
+}
+
+.clearfix:after {
+ clear: both;
+ content: " ";
+ display: table;
+}
+
+.float-left {
+ float: left;
+}
+
+.float-right {
+ float: right;
+}
diff --git a/demo/assets/css/phoenix.css b/demo/assets/css/phoenix.css
deleted file mode 100644
index 152f1651..00000000
--- a/demo/assets/css/phoenix.css
+++ /dev/null
@@ -1,781 +0,0 @@
-/* Includes some default style for the starter application.
- * This can be safely deleted to start fresh.
- */
-
-/* Milligram v1.4.1 https://milligram.github.io
- * Copyright (c) 2020 CJ Patoilo Licensed under the MIT license
- */
-
-*,
-*:after,
-*:before {
- box-sizing: inherit;
-}
-
-html {
- box-sizing: border-box;
- font-size: 62.5%;
-}
-
-body {
- color: #000000;
- font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
- font-size: 1.6em;
- font-weight: 300;
- letter-spacing: 0.01em;
- line-height: 1.6;
-}
-
-blockquote {
- border-left: 0.3rem solid #d1d1d1;
- margin-left: 0;
- margin-right: 0;
- padding: 1rem 1.5rem;
-}
-
-blockquote *:last-child {
- margin-bottom: 0;
-}
-
-.button,
-button,
-input[type="button"],
-input[type="reset"],
-input[type="submit"] {
- background-color: #0069d9;
- border: 0.1rem solid #0069d9;
- border-radius: 0.4rem;
- color: #fff;
- cursor: pointer;
- display: inline-block;
- font-size: 1.1rem;
- font-weight: 700;
- height: 3.8rem;
- letter-spacing: 0.1rem;
- line-height: 3.8rem;
- padding: 0 3rem;
- text-align: center;
- text-decoration: none;
- text-transform: uppercase;
- white-space: nowrap;
-}
-
-.button:focus,
-.button:hover,
-button:focus,
-button:hover,
-input[type="button"]:focus,
-input[type="button"]:hover,
-input[type="reset"]:focus,
-input[type="reset"]:hover,
-input[type="submit"]:focus,
-input[type="submit"]:hover {
- background-color: #606c76;
- border-color: #606c76;
- color: #fff;
- outline: 0;
-}
-
-.button[disabled],
-button[disabled],
-input[type="button"][disabled],
-input[type="reset"][disabled],
-input[type="submit"][disabled] {
- cursor: default;
- opacity: 0.5;
-}
-
-.button[disabled]:focus,
-.button[disabled]:hover,
-button[disabled]:focus,
-button[disabled]:hover,
-input[type="button"][disabled]:focus,
-input[type="button"][disabled]:hover,
-input[type="reset"][disabled]:focus,
-input[type="reset"][disabled]:hover,
-input[type="submit"][disabled]:focus,
-input[type="submit"][disabled]:hover {
- background-color: #0069d9;
- border-color: #0069d9;
-}
-
-.button.button-outline,
-button.button-outline,
-input[type="button"].button-outline,
-input[type="reset"].button-outline,
-input[type="submit"].button-outline {
- background-color: transparent;
- color: #0069d9;
-}
-
-.button.button-outline:focus,
-.button.button-outline:hover,
-button.button-outline:focus,
-button.button-outline:hover,
-input[type="button"].button-outline:focus,
-input[type="button"].button-outline:hover,
-input[type="reset"].button-outline:focus,
-input[type="reset"].button-outline:hover,
-input[type="submit"].button-outline:focus,
-input[type="submit"].button-outline:hover {
- background-color: transparent;
- border-color: #606c76;
- color: #606c76;
-}
-
-.button.button-outline[disabled]:focus,
-.button.button-outline[disabled]:hover,
-button.button-outline[disabled]:focus,
-button.button-outline[disabled]:hover,
-input[type="button"].button-outline[disabled]:focus,
-input[type="button"].button-outline[disabled]:hover,
-input[type="reset"].button-outline[disabled]:focus,
-input[type="reset"].button-outline[disabled]:hover,
-input[type="submit"].button-outline[disabled]:focus,
-input[type="submit"].button-outline[disabled]:hover {
- border-color: inherit;
- color: #0069d9;
-}
-
-.button.button-clear,
-button.button-clear,
-input[type="button"].button-clear,
-input[type="reset"].button-clear,
-input[type="submit"].button-clear {
- background-color: transparent;
- border-color: transparent;
- color: #0069d9;
-}
-
-.button.button-clear:focus,
-.button.button-clear:hover,
-button.button-clear:focus,
-button.button-clear:hover,
-input[type="button"].button-clear:focus,
-input[type="button"].button-clear:hover,
-input[type="reset"].button-clear:focus,
-input[type="reset"].button-clear:hover,
-input[type="submit"].button-clear:focus,
-input[type="submit"].button-clear:hover {
- background-color: transparent;
- border-color: transparent;
- color: #606c76;
-}
-
-.button.button-clear[disabled]:focus,
-.button.button-clear[disabled]:hover,
-button.button-clear[disabled]:focus,
-button.button-clear[disabled]:hover,
-input[type="button"].button-clear[disabled]:focus,
-input[type="button"].button-clear[disabled]:hover,
-input[type="reset"].button-clear[disabled]:focus,
-input[type="reset"].button-clear[disabled]:hover,
-input[type="submit"].button-clear[disabled]:focus,
-input[type="submit"].button-clear[disabled]:hover {
- color: #0069d9;
-}
-
-code {
- background: #f4f5f6;
- border-radius: 0.4rem;
- font-size: 86%;
- margin: 0 0.2rem;
- padding: 0.2rem 0.5rem;
- white-space: nowrap;
-}
-
-pre {
- background: #f4f5f6;
- border-left: 0.3rem solid #0069d9;
- overflow-y: hidden;
-}
-
-pre > code {
- border-radius: 0;
- display: block;
- padding: 1rem 1.5rem;
- white-space: pre;
-}
-
-hr {
- border: 0;
- border-top: 0.1rem solid #f4f5f6;
- margin: 3rem 0;
-}
-
-input[type="color"],
-input[type="date"],
-input[type="datetime"],
-input[type="datetime-local"],
-input[type="email"],
-input[type="month"],
-input[type="number"],
-input[type="password"],
-input[type="search"],
-input[type="tel"],
-input[type="text"],
-input[type="url"],
-input[type="week"],
-input:not([type]),
-textarea,
-select {
- -webkit-appearance: none;
- background-color: transparent;
- border: 0.1rem solid #d1d1d1;
- border-radius: 0.4rem;
- box-shadow: none;
- box-sizing: inherit;
- height: 3.8rem;
- padding: 0.6rem 1rem 0.7rem;
- width: 100%;
-}
-
-input[type="color"]:focus,
-input[type="date"]:focus,
-input[type="datetime"]:focus,
-input[type="datetime-local"]:focus,
-input[type="email"]:focus,
-input[type="month"]:focus,
-input[type="number"]:focus,
-input[type="password"]:focus,
-input[type="search"]:focus,
-input[type="tel"]:focus,
-input[type="text"]:focus,
-input[type="url"]:focus,
-input[type="week"]:focus,
-input:not([type]):focus,
-textarea:focus,
-select:focus {
- border-color: #0069d9;
- outline: 0;
-}
-
-select {
- background: url('data:image/svg+xml;utf8,')
- center right no-repeat;
- padding-right: 3rem;
-}
-
-select:focus {
- background-image: url('data:image/svg+xml;utf8,');
-}
-
-select[multiple] {
- background: none;
- height: auto;
-}
-
-textarea {
- min-height: 6.5rem;
-}
-
-label,
-legend {
- display: block;
- font-size: 1.6rem;
- font-weight: 700;
- margin-bottom: 0.5rem;
-}
-
-fieldset {
- border-width: 0;
- padding: 0;
-}
-
-input[type="checkbox"],
-input[type="radio"] {
- display: inline;
-}
-
-.label-inline {
- display: inline-block;
- font-weight: normal;
- margin-left: 0.5rem;
-}
-
-.container {
- margin: 0 auto;
- max-width: 112rem;
- padding: 0 2rem;
- position: relative;
- width: 100%;
-}
-
-.row {
- display: flex;
- flex-direction: column;
- padding: 0;
- width: 100%;
-}
-
-.row.row-no-padding {
- padding: 0;
-}
-
-.row.row-no-padding > .column {
- padding: 0;
-}
-
-.row.row-wrap {
- flex-wrap: wrap;
-}
-
-.row.row-top {
- align-items: flex-start;
-}
-
-.row.row-bottom {
- align-items: flex-end;
-}
-
-.row.row-center {
- align-items: center;
-}
-
-.row.row-stretch {
- align-items: stretch;
-}
-
-.row.row-baseline {
- align-items: baseline;
-}
-
-.row .column {
- display: block;
- flex: 1 1 auto;
- margin-left: 0;
- max-width: 100%;
- width: 100%;
-}
-
-.row .column.column-offset-10 {
- margin-left: 10%;
-}
-
-.row .column.column-offset-20 {
- margin-left: 20%;
-}
-
-.row .column.column-offset-25 {
- margin-left: 25%;
-}
-
-.row .column.column-offset-33,
-.row .column.column-offset-34 {
- margin-left: 33.3333%;
-}
-
-.row .column.column-offset-40 {
- margin-left: 40%;
-}
-
-.row .column.column-offset-50 {
- margin-left: 50%;
-}
-
-.row .column.column-offset-60 {
- margin-left: 60%;
-}
-
-.row .column.column-offset-66,
-.row .column.column-offset-67 {
- margin-left: 66.6666%;
-}
-
-.row .column.column-offset-75 {
- margin-left: 75%;
-}
-
-.row .column.column-offset-80 {
- margin-left: 80%;
-}
-
-.row .column.column-offset-90 {
- margin-left: 90%;
-}
-
-.row .column.column-10 {
- flex: 0 0 10%;
- max-width: 10%;
-}
-
-.row .column.column-20 {
- flex: 0 0 20%;
- max-width: 20%;
-}
-
-.row .column.column-25 {
- flex: 0 0 25%;
- max-width: 25%;
-}
-
-.row .column.column-33,
-.row .column.column-34 {
- flex: 0 0 33.3333%;
- max-width: 33.3333%;
-}
-
-.row .column.column-40 {
- flex: 0 0 40%;
- max-width: 40%;
-}
-
-.row .column.column-50 {
- flex: 0 0 50%;
- max-width: 50%;
-}
-
-.row .column.column-60 {
- flex: 0 0 60%;
- max-width: 60%;
-}
-
-.row .column.column-66,
-.row .column.column-67 {
- flex: 0 0 66.6666%;
- max-width: 66.6666%;
-}
-
-.row .column.column-75 {
- flex: 0 0 75%;
- max-width: 75%;
-}
-
-.row .column.column-80 {
- flex: 0 0 80%;
- max-width: 80%;
-}
-
-.row .column.column-90 {
- flex: 0 0 90%;
- max-width: 90%;
-}
-
-.row .column .column-top {
- align-self: flex-start;
-}
-
-.row .column .column-bottom {
- align-self: flex-end;
-}
-
-.row .column .column-center {
- align-self: center;
-}
-
-@media (min-width: 40rem) {
- .row {
- flex-direction: row;
- margin-left: -1rem;
- width: calc(100% + 2rem);
- }
-
- .row .column {
- margin-bottom: inherit;
- padding: 0 1rem;
- }
-}
-
-a {
- color: #0069d9;
- text-decoration: none;
-}
-
-a:focus,
-a:hover {
- color: #606c76;
-}
-
-dl,
-ol,
-ul {
- list-style: none;
- margin-top: 0;
- padding-left: 0;
-}
-
-dl dl,
-dl ol,
-dl ul,
-ol dl,
-ol ol,
-ol ul,
-ul dl,
-ul ol,
-ul ul {
- font-size: 90%;
- margin: 1.5rem 0 1.5rem 3rem;
-}
-
-ol {
- list-style: decimal inside;
-}
-
-ul {
- list-style: circle inside;
-}
-
-.button,
-button,
-dd,
-dt,
-li {
- margin-bottom: 1rem;
-}
-
-fieldset,
-input,
-select,
-textarea {
- margin-bottom: 1.5rem;
-}
-
-blockquote,
-dl,
-figure,
-form,
-ol,
-p,
-pre,
-table,
-ul {
- margin-bottom: 2.5rem;
-}
-
-table {
- border-spacing: 0;
- display: block;
- overflow-x: auto;
- text-align: left;
- width: 100%;
-}
-
-td,
-th {
- border-bottom: 0.1rem solid #e1e1e1;
- padding: 1.2rem 1.5rem;
-}
-
-td:first-child,
-th:first-child {
- padding-left: 0;
-}
-
-td:last-child,
-th:last-child {
- padding-right: 0;
-}
-
-@media (min-width: 40rem) {
- table {
- display: table;
- overflow-x: initial;
- }
-}
-
-b,
-strong {
- font-weight: bold;
-}
-
-p {
- margin-top: 0;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- font-weight: 300;
- letter-spacing: -0.1rem;
- margin-bottom: 2rem;
- margin-top: 0;
-}
-
-h1 {
- font-size: 4.6rem;
- line-height: 1.2;
-}
-
-h2 {
- font-size: 3.6rem;
- line-height: 1.25;
-}
-
-h3 {
- font-size: 2.8rem;
- line-height: 1.3;
-}
-
-h4 {
- font-size: 2.2rem;
- letter-spacing: -0.08rem;
- line-height: 1.35;
-}
-
-h5 {
- font-size: 1.8rem;
- letter-spacing: -0.05rem;
- line-height: 1.5;
-}
-
-h6 {
- font-size: 1.6rem;
- letter-spacing: 0;
- line-height: 1.4;
-}
-
-img {
- max-width: 100%;
-}
-
-.clearfix:after {
- clear: both;
- content: " ";
- display: table;
-}
-
-.float-left {
- float: left;
-}
-
-.float-right {
- float: right;
-}
-
-/* General style */
-h1 {
- font-size: 3.6rem;
- line-height: 1.25;
-}
-
-h2 {
- font-size: 2.8rem;
- line-height: 1.3;
-}
-
-h3 {
- font-size: 2.2rem;
- letter-spacing: -0.08rem;
- line-height: 1.35;
-}
-
-h4 {
- font-size: 1.8rem;
- letter-spacing: -0.05rem;
- line-height: 1.5;
-}
-
-h5 {
- font-size: 1.6rem;
- letter-spacing: 0;
- line-height: 1.4;
-}
-
-h6 {
- font-size: 1.4rem;
- letter-spacing: 0;
- line-height: 1.2;
-}
-
-pre {
- padding: 1em;
-}
-
-.container {
- margin: 0 auto;
- max-width: 80rem;
- padding: 0 2rem;
- position: relative;
- width: 100%;
-}
-
-select {
- width: auto;
-}
-
-/* Phoenix promo and logo */
-.phx-hero {
- text-align: center;
- border-bottom: 1px solid #e3e3e3;
- background: #eee;
- border-radius: 6px;
- padding: 3em 3em 1em;
- margin-bottom: 3rem;
- font-weight: 200;
- font-size: 120%;
-}
-
-.phx-hero input {
- background: #ffffff;
-}
-
-.phx-logo {
- min-width: 300px;
- margin: 1rem;
- display: block;
-}
-
-.phx-logo img {
- width: auto;
- display: block;
-}
-
-/* Headers */
-header {
- width: 100%;
- background: #fdfdfd;
- border-bottom: 1px solid #eaeaea;
- margin-bottom: 2rem;
-}
-
-header section {
- align-items: center;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-}
-
-header section :first-child {
- order: 2;
-}
-
-header section :last-child {
- order: 1;
-}
-
-header nav ul,
-header nav li {
- margin: 0;
- padding: 0;
- display: block;
- text-align: right;
- white-space: nowrap;
-}
-
-header nav ul {
- margin: 1rem;
- margin-top: 0;
-}
-
-header nav a {
- display: block;
-}
-
-@media (min-width: 40rem) {
- /* Small devices (landscape phones, 576px and up) */
- header section {
- flex-direction: row;
- }
-
- header nav ul {
- margin: 1rem;
- }
-
- .phx-logo {
- flex-basis: 527px;
- margin: 2rem 1rem;
- }
-}
diff --git a/demo/assets/css/storybook.css b/demo/assets/css/storybook.css
index 2d2ac9bf..48f1098e 100644
--- a/demo/assets/css/storybook.css
+++ b/demo/assets/css/storybook.css
@@ -1,9 +1,3 @@
-/*
- * This is your custom storybook stylesheet.
- * Put your component styling under ."demo-web" scope.
- * See the https://hexdocs.pm/phx_live_storybook/sandboxing.html guide for more info.
- */
-
.demo-web {
font-family: ui-sans-serif, system-ui;
}
diff --git a/demo/assets/js/app.js b/demo/assets/js/app.js
index cf24fa49..2b351dc1 100644
--- a/demo/assets/js/app.js
+++ b/demo/assets/js/app.js
@@ -1,7 +1,3 @@
-// We import the CSS which is extracted to its own file by esbuild.
-// Remove this line if you add a your own CSS build pipeline (e.g postcss).
-import "../css/app.css";
-
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
@@ -26,6 +22,8 @@ import { Socket } from "phoenix";
import { LiveSocket } from "phoenix_live_view";
import topbar from "../vendor/topbar";
+import "../css/app.css";
+
let csrfToken = document
.querySelector("meta[name='csrf-token']")
.getAttribute("content");
@@ -35,8 +33,8 @@ let liveSocket = new LiveSocket("/live", Socket, {
// Show progress bar on live navigation and form submits
topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
-window.addEventListener("phx:page-loading-start", (info) => topbar.show());
-window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
+window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
+window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());
// connect if there are any LiveViews on the page
liveSocket.connect();
diff --git a/demo/assets/js/storybook.js b/demo/assets/js/storybook.js
index 863cce89..11daed55 100644
--- a/demo/assets/js/storybook.js
+++ b/demo/assets/js/storybook.js
@@ -9,3 +9,26 @@
// (function () {
// window.storybook = { Hooks, Params, Uploaders };
// })();
+
+// If your components require alpinejs, you'll need to start
+// alpine after the DOM is loaded and pass in an onBeforeElUpdated
+//
+// import Alpine from 'alpinejs'
+// window.Alpine = Alpine
+// document.addEventListener('DOMContentLoaded', () => {
+// window.Alpine.start();
+// });
+
+// (function () {
+// window.storybook = {
+// LiveSocketOptions: {
+// dom: {
+// onBeforeElUpdated(from, to) {
+// if (from._x_dataStack) {
+// window.Alpine.clone(from, to)
+// }
+// }
+// }
+// }
+// };
+// })();
diff --git a/demo/assets/vendor/topbar.js b/demo/assets/vendor/topbar.js
index 1f622097..41957274 100644
--- a/demo/assets/vendor/topbar.js
+++ b/demo/assets/vendor/topbar.js
@@ -1,6 +1,6 @@
/**
* @license MIT
- * topbar 1.0.0, 2021-01-06
+ * topbar 2.0.0, 2023-02-04
* https://buunguyen.github.io/topbar
* Copyright (c) 2021 Buu Nguyen
*/
@@ -35,10 +35,11 @@
})();
var canvas,
- progressTimerId,
- fadeTimerId,
currentProgress,
showing,
+ progressTimerId = null,
+ fadeTimerId = null,
+ delayTimerId = null,
addEvent = function (elem, type, handler) {
if (elem.addEventListener) elem.addEventListener(type, handler, false);
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
@@ -95,21 +96,26 @@
for (var key in opts)
if (options.hasOwnProperty(key)) options[key] = opts[key];
},
- show: function () {
+ show: function (delay) {
if (showing) return;
- showing = true;
- if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
- if (!canvas) createCanvas();
- canvas.style.opacity = 1;
- canvas.style.display = "block";
- topbar.progress(0);
- if (options.autoRun) {
- (function loop() {
- progressTimerId = window.requestAnimationFrame(loop);
- topbar.progress(
- "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
- );
- })();
+ if (delay) {
+ if (delayTimerId) return;
+ delayTimerId = setTimeout(() => topbar.show(), delay);
+ } else {
+ showing = true;
+ if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
+ if (!canvas) createCanvas();
+ canvas.style.opacity = 1;
+ canvas.style.display = "block";
+ topbar.progress(0);
+ if (options.autoRun) {
+ (function loop() {
+ progressTimerId = window.requestAnimationFrame(loop);
+ topbar.progress(
+ "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
+ );
+ })();
+ }
}
},
progress: function (to) {
@@ -125,6 +131,8 @@
return currentProgress;
},
hide: function () {
+ clearTimeout(delayTimerId);
+ delayTimerId = null;
if (!showing) return;
showing = false;
if (progressTimerId != null) {
diff --git a/demo/config/config.exs b/demo/config/config.exs
index 80fca9c4..5c44026b 100644
--- a/demo/config/config.exs
+++ b/demo/config/config.exs
@@ -7,20 +7,23 @@
# General application configuration
import Config
+config :demo,
+ generators: [timestamp_type: :utc_datetime]
+
# Configures the endpoint
config :demo, DemoWeb.Endpoint,
url: [host: "localhost"],
+ adapter: Phoenix.Endpoint.Cowboy2Adapter,
render_errors: [
- view: DemoWeb.ErrorView,
- accepts: ~w(html json),
+ formats: [html: DemoWeb.ErrorHTML, json: DemoWeb.ErrorJSON],
layout: false
],
pubsub_server: Demo.PubSub,
- live_view: [signing_salt: "njO9CwWg"]
+ live_view: [signing_salt: "hha5Qn8m"]
# Configure esbuild (the version is required)
config :esbuild,
- version: "0.14.29",
+ version: "0.17.11",
default: [
args:
~w(js/app.js js/storybook.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
diff --git a/demo/config/dev.exs b/demo/config/dev.exs
index 3c698716..8dabcdd3 100644
--- a/demo/config/dev.exs
+++ b/demo/config/dev.exs
@@ -4,8 +4,8 @@ import Config
# debugging and code reloading.
#
# The watchers configuration can be used to run external
-# watchers to your application. For example, we use it
-# with esbuild to bundle .js and .css sources.
+# watchers to your application. For example, we can use it
+# to bundle .js and .css sources.
config :demo, DemoWeb.Endpoint,
# Binding to loopback ipv4 address prevents access from other machines.
# Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
@@ -13,12 +13,9 @@ config :demo, DemoWeb.Endpoint,
check_origin: false,
code_reloader: true,
debug_errors: true,
- secret_key_base:
- "Jc+vuCcFoG0H5DxSRJrLBTm/ulv+34QXD1EfBNdRu9CF+hZvoioZ0FPB2bLrkDtc",
+ secret_key_base: "7I09KWxcUUDVCHxt1eWTxz/zRZ4K9839EiL/iutqCNQORX+I2K62EM4/Y8HH+60G",
watchers: [
- # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
- esbuild:
- {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
+ esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
]
# ## SSL Support
@@ -29,7 +26,6 @@ config :demo, DemoWeb.Endpoint,
#
# mix phx.gen.cert
#
-# Note that this task requires Erlang/OTP 20 or later.
# Run `mix help phx.gen.cert` for more information.
#
# The `http:` config above can be replaced with:
@@ -51,12 +47,14 @@ config :demo, DemoWeb.Endpoint,
patterns: [
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
- ~r"lib/demo_web/(live|views)/.*(ex)$",
- ~r"lib/demo_web/templates/.*(eex)$",
+ ~r"lib/demo_web/(controllers|live|components)/.*(ex|heex)$",
~r"storybook/.*(exs)$"
]
]
+# Enable dev routes for dashboard and mailbox
+config :demo, dev_routes: true
+
# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
@@ -66,3 +64,6 @@ config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime
+
+# Include HEEx debug annotations as HTML comments in rendered markup
+config :phoenix_live_view, :debug_heex_annotations, true
diff --git a/demo/config/prod.exs b/demo/config/prod.exs
index 68e31b45..ff10aa9b 100644
--- a/demo/config/prod.exs
+++ b/demo/config/prod.exs
@@ -1,50 +1,14 @@
import Config
-# For production, don't forget to configure the url host
-# to something meaningful, Phoenix uses this information
-# when generating URLs.
-#
# Note we also include the path to a cache manifest
# containing the digested version of static files. This
-# manifest is generated by the `mix phx.digest` task,
+# manifest is generated by the `mix assets.deploy` task,
# which you should run after static files are built and
# before starting your production server.
-config :demo, DemoWeb.Endpoint,
- cache_static_manifest: "priv/static/cache_manifest.json"
+config :demo, DemoWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
# Do not print debug messages in production
config :logger, level: :info
-# ## SSL Support
-#
-# To get SSL working, you will need to add the `https` key
-# to the previous section and set your `:url` port to 443:
-#
-# config :demo, DemoWeb.Endpoint,
-# ...,
-# url: [host: "example.com", port: 443],
-# https: [
-# ...,
-# port: 443,
-# cipher_suite: :strong,
-# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
-# certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
-# ]
-#
-# The `cipher_suite` is set to `:strong` to support only the
-# latest and more secure SSL ciphers. This means old browsers
-# and clients may not be supported. You can set it to
-# `:compatible` for wider support.
-#
-# `:keyfile` and `:certfile` expect an absolute path to the key
-# and cert in disk or a relative path inside priv, for example
-# "priv/ssl/server.key". For all supported SSL configuration
-# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
-#
-# We also recommend setting `force_ssl` in your endpoint, ensuring
-# no data is ever sent via http, always redirecting to https:
-#
-# config :demo, DemoWeb.Endpoint,
-# force_ssl: [hsts: true]
-#
-# Check `Plug.SSL` for all available options in `force_ssl`.
+# Runtime production configuration, including reading
+# of environment variables, is done on config/runtime.exs.
diff --git a/demo/config/runtime.exs b/demo/config/runtime.exs
index 55349fe2..1c8501ee 100644
--- a/demo/config/runtime.exs
+++ b/demo/config/runtime.exs
@@ -36,6 +36,8 @@ if config_env() == :prod do
host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "4000")
+ config :demo, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
+
config :demo, DemoWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [
@@ -47,4 +49,36 @@ if config_env() == :prod do
port: port
],
secret_key_base: secret_key_base
+
+ # ## SSL Support
+ #
+ # To get SSL working, you will need to add the `https` key
+ # to your endpoint configuration:
+ #
+ # config :demo, DemoWeb.Endpoint,
+ # https: [
+ # ...,
+ # port: 443,
+ # cipher_suite: :strong,
+ # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
+ # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
+ # ]
+ #
+ # The `cipher_suite` is set to `:strong` to support only the
+ # latest and more secure SSL ciphers. This means old browsers
+ # and clients may not be supported. You can set it to
+ # `:compatible` for wider support.
+ #
+ # `:keyfile` and `:certfile` expect an absolute path to the key
+ # and cert in disk or a relative path inside priv, for example
+ # "priv/ssl/server.key". For all supported SSL configuration
+ # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
+ #
+ # We also recommend setting `force_ssl` in your endpoint, ensuring
+ # no data is ever sent via http, always redirecting to https:
+ #
+ # config :demo, DemoWeb.Endpoint,
+ # force_ssl: [hsts: true]
+ #
+ # Check `Plug.SSL` for all available options in `force_ssl`.
end
diff --git a/demo/config/test.exs b/demo/config/test.exs
index df7402d5..90e81ac8 100644
--- a/demo/config/test.exs
+++ b/demo/config/test.exs
@@ -4,12 +4,11 @@ import Config
# you can enable the server option below.
config :demo, DemoWeb.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 4002],
- secret_key_base:
- "bZKkqegn+ndkcxTO9Hrb/Go/rbZy9YP6dku/V1PeEiLM9dZdI4qPoy3NrftWxk2t",
+ secret_key_base: "UwBHokdfX1j1Z/VYF+Cj4RHuS8iaMV2xTzmET+mbzBUF7dVI9wvgo+quplDOVGws",
server: false
# Print only warnings and errors during test
-config :logger, level: :warn
+config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
diff --git a/demo/lib/demo/application.ex b/demo/lib/demo/application.ex
index 3033f5ca..6412e9ae 100644
--- a/demo/lib/demo/application.ex
+++ b/demo/lib/demo/application.ex
@@ -8,14 +8,13 @@ defmodule Demo.Application do
@impl true
def start(_type, _args) do
children = [
- # Start the Telemetry supervisor
DemoWeb.Telemetry,
- # Start the PubSub system
+ {DNSCluster, query: Application.get_env(:demo, :dns_cluster_query) || :ignore},
{Phoenix.PubSub, name: Demo.PubSub},
- # Start the Endpoint (http/https)
- DemoWeb.Endpoint
# Start a worker by calling: Demo.Worker.start_link(arg)
- # {Demo.Worker, arg}
+ # {Demo.Worker, arg},
+ # Start to serve requests, typically the last entry
+ DemoWeb.Endpoint
]
# See https://hexdocs.pm/elixir/Supervisor.html
diff --git a/demo/lib/demo_web.ex b/demo/lib/demo_web.ex
index dfe3db94..3f0a1d53 100644
--- a/demo/lib/demo_web.ex
+++ b/demo/lib/demo_web.ex
@@ -1,53 +1,60 @@
defmodule DemoWeb do
@moduledoc """
The entrypoint for defining your web interface, such
- as controllers, views, channels and so on.
+ as controllers, components, channels, and so on.
This can be used in your application as:
use DemoWeb, :controller
- use DemoWeb, :view
+ use DemoWeb, :html
- The definitions below will be executed for every view,
- controller, etc, so keep them short and clean, focused
+ The definitions below will be executed for every controller,
+ component, etc, so keep them short and clean, focused
on imports, uses and aliases.
Do NOT define functions inside the quoted expressions
- below. Instead, define any helper function in modules
- and import those modules here.
+ below. Instead, define additional modules and import
+ those modules here.
"""
- def controller do
+ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
+
+ def router do
quote do
- use Phoenix.Controller, namespace: DemoWeb
+ use Phoenix.Router, helpers: false
+ # Import common connection and controller functions to use in pipelines
import Plug.Conn
- import DemoWeb.Gettext
- alias DemoWeb.Router.Helpers, as: Routes
+ import Phoenix.Controller
+ import Phoenix.LiveView.Router
+ end
+ end
+
+ def channel do
+ quote do
+ use Phoenix.Channel
end
end
- def view do
+ def controller do
quote do
- use Phoenix.View,
- root: "lib/demo_web/templates",
- namespace: DemoWeb
+ use Phoenix.Controller,
+ formats: [:html, :json],
+ layouts: [html: DemoWeb.Layouts]
- # Import convenience functions from controllers
- import Phoenix.Controller,
- only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
+ import Plug.Conn
+ import DemoWeb.Gettext
- # Include shared imports and aliases for views
- unquote(view_helpers())
+ unquote(verified_routes())
end
end
def live_view do
quote do
use Phoenix.LiveView,
- layout: {DemoWeb.LayoutView, "live.html"}
+ layout: {DemoWeb.Layouts, :app}
- unquote(view_helpers())
+ unquote(html_helpers())
end
end
@@ -55,48 +62,44 @@ defmodule DemoWeb do
quote do
use Phoenix.LiveComponent
- unquote(view_helpers())
+ unquote(html_helpers())
end
end
- def component do
+ def html do
quote do
use Phoenix.Component
- unquote(view_helpers())
- end
- end
-
- def router do
- quote do
- use Phoenix.Router
+ # Import convenience functions from controllers
+ import Phoenix.Controller,
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
- import Plug.Conn
- import Phoenix.Controller
- import Phoenix.LiveView.Router
+ # Include general helpers for rendering HTML
+ unquote(html_helpers())
end
end
- def channel do
+ defp html_helpers do
quote do
- use Phoenix.Channel
+ # HTML escaping functionality
+ import Phoenix.HTML
+ # translation
import DemoWeb.Gettext
+
+ # Shortcut for generating JS commands
+ alias Phoenix.LiveView.JS
+
+ # Routes generation with the ~p sigil
+ unquote(verified_routes())
end
end
- defp view_helpers do
+ def verified_routes do
quote do
- # Use all HTML functionality (forms, tags, etc)
- use Phoenix.HTML
-
- # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
- import Phoenix.LiveView.Helpers
-
- # Import basic rendering functionality (render, render_layout, etc)
- import Phoenix.View
-
- import DemoWeb.Gettext
- alias DemoWeb.Router.Helpers, as: Routes
+ use Phoenix.VerifiedRoutes,
+ endpoint: DemoWeb.Endpoint,
+ router: DemoWeb.Router,
+ statics: DemoWeb.static_paths()
end
end
diff --git a/demo/lib/demo_web/components/layouts.ex b/demo/lib/demo_web/components/layouts.ex
new file mode 100644
index 00000000..58a4271f
--- /dev/null
+++ b/demo/lib/demo_web/components/layouts.ex
@@ -0,0 +1,5 @@
+defmodule DemoWeb.Layouts do
+ use DemoWeb, :html
+
+ embed_templates "layouts/*"
+end
diff --git a/demo/lib/demo_web/components/layouts/app.html.heex b/demo/lib/demo_web/components/layouts/app.html.heex
new file mode 100644
index 00000000..b6d6344b
--- /dev/null
+++ b/demo/lib/demo_web/components/layouts/app.html.heex
@@ -0,0 +1,32 @@
+
+ v<%= Application.spec(:phoenix, :vsn) %>
+
+ We generated your storybook with an example of a page and a component.
+ Explore the generated *.story.exs
+ files in your /storybook
+ directory. When you're ready to add your own, just drop your new story & index files into the same directory and refresh your storybook.
+
+ Here are a few docs you might be interested in: +
+ + <.description_list items={[ + {"Create a new Story", doc_link("Story")}, + {"Display components using Variations", doc_link("Stories.Variation")}, + {"Group components using VariationGroups", doc_link("Stories.VariationGroup")}, + {"Organize the sidebar with Index files", doc_link("Index")} + ]} /> + ++ This should be enough to get you started, but you can use the tabs in the upper-right corner of this page to check out advanced usage guides. +
++ + This and other guides are also available on HexDocs. + +
+