Octantis
View SourceOctantis is an implementation of Polaris Design System in Elixir for Phoenix LiveView. If you are building a ShopAdmin for your Shopify App (Shopify calls this embedded app home at times), and want to conform to the Shopify perscribed design system, then you will want to use Octantis.
Octantis provides Polaris LiveView components that you can drop into your LiveView Shop admin.
Implementations
Octantis components are wrappers around two different implementations: React and Web Components.
React
The OctantisWeb.Components.Polaris components largely match the html produced by Polaris React. This implementation is meant to read like the react implementations as much as possible.
<.card>
<.text as="h2" variant="bodyMd">
Welcome to The Littlest Marble Shop
</.text>
</.card>Web Components
The OctantisWeb.Components.PolarisWC components are a light wrapper around the new Polaris Web Components. Some niceties have been added around responsive attributes with the ~s sigil and type checking. Events are forwared through the OctantisEventProxy hook.
<.s_section>
<.s_heading>
Welcome to The Littlest Marble Shop
</.s_heading>
</.s_section>σ Octantis
σ Octantis is the current southern pole star in opposition to Polaris the current northen pole star.
Other Resource
- Elixir ShopifyAPI is most of what you need to interact with Shopify APIs. Auth, Rest, Graphql, Webhooks and so on.
- Elixir Shopify App is a template for building an App with Elixir ShopifyAPI. Currently it lacks a LiveView ShopAdmin as a default, but there is some work towards enabling that.
- Polaris Design System
- Shopify AppBridge provides some functionality for the ShopAdmin, noteably toasts and navigation menues.
Installation
Octantis is available in Hex, the package can be installed
by adding octantis to your list of dependencies in mix.exs:
def deps do
[
{:octantis, "~> 0.2.0"}
]
endInstall Hooks
In assets/js/app.js add
import * as octantisHooks from "octantis"
let Hooks = { ...octantisHooks }
let liveSocket = new LiveSocket("/live_view_path", Socket, {hooks: Hooks, bindingPrefix: "data-phx-"})Note that the bindingPrefix of "data-phx-". AppBridge and Polaris have a tendancy to remove attributes that do not have the data- prefix.
Setup A LiveView Shop Admin
We recommend creating a new liveview endpoint with its own routes, templates, and JS just for your Shop Admin.
In OctantisAppWeb.Endpoint:
socket "/shop_admin_live", Phoenix.LiveView.Socket, websocket: [connect_info: []]In OctantisAppAppWeb.Router:
pipeline :shop_admin do
plug ShopifyAPI.Plugs.AdminAuthenticator
plug ShopifyAPI.Plugs.PutShopifyContentHeaders
end
live_session :live_shop_admin,
layout: {OctantisAppAppWeb.ShopAdminLive.Layouts, :app},
root_layout: {OctantisAppAppWeb.ShopAdminLive.Layouts, :root},
session: {OctantisAppAppWeb.ShopAdmin.Hooks.AssignScope, :build_session, []} do
scope "/live_shop_admin", OctantisAppAppWeb do
pipe_through :browser
pipe_through :shop_admin
live "/", ShopAdmin.DashboardLive.Index, :live
live "/settings", ShopAdmin.SettingsLive.Index, :index
end
endIn assets/js/shop_admin.js:
// JS specific to LiveView in ShopAdmin
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import * as baseHooks from "./hooks"
import * as octantisHooks from "octantis"
let Hooks = { ...baseHooks, ...octantisHooks }
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/shop_admin_live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}, bindingPrefix: "data-phx-"})
// Show progress bar on live navigation and form submits
window.addEventListener("phx:page-loading-start", _info => shopify.loading(true))
window.addEventListener("phx:page-loading-stop", _info => shopify.loading(false))
window.addEventListener("phx:page-loading-stop", info => {
/*
When navigating, AppBridge detects changes within a LiveView,
however, it does not detect between LiveView navigation, even when it is
correctly patching within a live_session. This re-emits a navigation event
that AppBridge will detect.
*/
const destination = new URL(info.detail.to).pathname
if (info.detail.kind == "initial" && destination != window.location.pathname) {
history.pushState(null, '', destination);
} else {
history.replaceState(null, '', destination);
}
})
// connect if there are any LiveViews on the page
liveSocket.connect()
window.liveSocket = liveSocket
window.shopifyIdToken = shopify.idToken();In lib/octantis_app_web/live/shop_admin/layouts/root.html.heex
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} />
<meta name="shopify-api-key" content={OctantisApp.Config.api_key()} />
<%!-- Polaris CSS --%>
<OctantisWeb.Components.Head.stylesheet />
<%!-- AppBridge JS --%>
<OctantisWeb.Components.Head.javascript />
<script defer data-phx-track-static type="text/javascript" src={~p"/assets/shop_admin.js"}>
</script>
</head>
<body>
{@inner_content}
</body>
</html>In lib/cotantis_app_web/live/shop_admin/layouts/app.html.heex
<.ui_nav_menu>
<:link name="Home" url="/live_shop_admin/" />
<:link name="Settings" url="/live_shop_admin/settings" />
</.ui_nav_menu>
<main role="main" id="root">
<.toast kind={:info} flash={@flash} id="toastinfo" />
<.toast kind={:error} flash={@flash} id="toasterror" />
<.s_query_container container_name="Page">
<.s_page heading={@page_heading} id="PageRoot">
{@inner_content}
</.s_page>
<.s_box padding="small"><%!-- spacer --%></.s_box>
</.s_query_container>
</main>Local Setup
Run tests
mix check
Run Storybook
mix phx.server
Navigate to http://localhost:4040/storybook