redbastie / swift
A SwiftUI-inspired spin on Laravel Livewire.
Requires
- doctrine/dbal: ^2.10
- laravel/framework: ^8.0
- livewire/livewire: ^2.0
README
Please check out my new package here: https://github.com/bastinald/malzahar
It's a much better implementation of what this package was trying to do.
Laravel Swift
Say goodbye to HTML, CSS, and Javascript! Laravel Swift is a SwiftUI-inspired spin on Laravel Livewire. It utilizes Laravel, Livewire, Bootstrap, Font Awesome, & more under the hood.
This package is my vision for the future of PHP development. It was created with backend/CRUD apps in mind, but will work for any type of application. It also comes with useful features like automatic routing and migrations, to speed you up and lower code abstractions even more.
In order to best utilize Swift, you should be familiar with the following:
Requirements:
- A web server that can run Laravel 8
- NPM
Links:
- Support: GitHub Issues
- Contribute: GitHub Pulls
- Donate: PayPal
Installation
This package was designed to work with clean Laravel 8 installs. All Swift apps come with basic auth scaffolding and user CRUD by default.
Install Laravel:
laravel new app
Configure the database in your .env
file:
DB_DATABASE=app
DB_USERNAME=root
DB_PASSWORD=
Require Swift via composer:
composer require redbastie/swift
Run the installation command:
php artisan install:swift
Now you can visit your app URL and login using user@example.com:password
, which was created via the DatabaseSeeder
class.
Making Components
Generate a basic component:
php artisan make:swift ComponentName
This will create a component inside of the app/Http/Livewire
directory.
Generate a basic full page component:
php artisan make:page PageName
This will create a full page component inside of the app/Http/Livewire
directory.
Generate CRUD scaffolding for a new model:
php artisan make:crud ModelName
This will create the model, factory, nav item, and CRUD components.
Generate CRUD for a new model with a shared trait for form fields & rules:
php artisan make:crudtrait ModelName
This will create the model, factory, nav item, CRUD components and trait.
Generate a new Swift model:
php artisan make:swiftmodel ModelName
This will create the model and factory.
Using Components
Full page components should specify $routeUri
& $pageTitle
properties:
public $routeUri = '/tao';
public $routeName = 'tao'; // optional
public $routeMiddleware = 'auth'; // optional
public $pageTitle = 'The Tao';
Swift components must implement a view
method:
public function view()
{
return S::div(
S::livewire('layouts.navbar'),
S::container(
S::paragraph('Stop trying to control.')
)
);
}
Using child components in the view
:
S::livewire('layouts.navbar'),
Available Components
Alert
S::alert('Something happened!')->success(),
Methods: dismissable
, fade
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
Badge
S::badge('New')->primary(),
Methods: primary
, secondary
, success
, info
, danger
, warning
, light
, dark
, pill
Blockquote
S::blockquote('Be like water.')->footer('The Tao'),
Methods: footer
Breadcrumb
S::breadcrumb(
S::breadcrumbItem('Home'),
S::breadcrumbItem('Page'),
),
Methods: none
Breadcrumb Item
S::breadcrumbItem('Home')->active(Route::is('home')),
Methods: active
Button
S::button('Do Something')->primary()->click('doSomething'),
Methods: submit
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
, outlinePrimary
, outlineSecondary
, outlineSuccess
, outlineInfo
, outlineDanger
, outlineWarning
, outlineLight
, outlineDark
, link
, active
, sm
, lg
, block
, disabled
Button Group
S::buttonGroup(
S::button('Do Something')->primary()->click('doSomething'),
S::button('Something Else')->secondary()->click('somethingElse'),
),
Methods: none
Button Toolbar
S::buttonToolbar(
S::buttonGroup(
S::button('Do Something')->primary()->click('doSomething'),
S::button('Something Else')->secondary()->click('somethingElse'),
),
),
Methods: none
Card
S::card()
->header('Hello World')
->body('The whole world belongs to you')
->footer('The Tao'),
Methods: header
, image
, body
, footer
Checkbox
S::checkbox('agree')->label('Agree to TOS')->modelDefer(),
Methods: label
, checkboxLabel
, help
, switch
, inline
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
Code
S::code('$hello = $world;'),
Methods: none
Col (Grid)
S::col('Be like water.')->md(2),
Methods: xs
, auto
, sm
, smAuto
, md
, mdAuto
, lg
, lgAuto
, xl
, xlAuto
, offset
, offsetSm
, offsetMd
, offsetLg
, offsetXl
Container
S::container(
S::paragraph('In work, do what you enjoy.'),
),
Methods: none
Div
S::div('The best athlete wants his opponent at his best.'),
Methods: none
Dropdown
S::dropdown()
->toggle(
S::button('Dropdown')->primary()->dropdownToggle()
)
->items(
S::button('Do Something')->dropdownItem(),
S::button('Something Else')->dropdownItem(),
),
Methods: toggle
, items
, right
, smRight
, mdRight
, lgRight
, xlRight
, left
, smLeft
, mdLeft
, lgLeft
, xlLeft
Each (Loop)
S::each(['Red', 'Green', 'Blue'], function ($color, $key) {
return S::div($key . ': ' . $color);
})->empty(
S::paragraph('No colors to display.')
),
Methods: empty
File
S::file('avatar')->label('Avatar')->placeholder('Choose avatar')->modelDefer(),
Methods: label
, placeholder
, help
, model
, modelDebounce
, modelDefer
, modelLazy
Form
S::form(
S::input('first_name')->label('First Name')->modelDefer(),
S::input('last_name')->label('Last Name')->modelDefer(),
S::button('Submit')->submit()->primary(),
)->submitPrevent('submitForm'),
Methods: inline
, submit
, submitPrevent
, submitSelf
, submitStop
Form Group
S::formGroup(
S::label('First Name'),
S::input('first_name')->modelDefer(),
),
Methods: label
Form Row
S::formRow(
S::col(S::input('first')->placeholder('First')->model())->md(),
S::col(S::input('last')->placeholder('Last')->model())->md(),
),
Methods: none
Heading
S::heading('Laravel Swift')->size(2),
Methods: size
Horizontal Rule
S::horizontalRule(),
Methods: none
Icon (Font Awesome)
S::icon('pastafarianism'),
Methods: solid
, regular
, light
, duotone
, brand
, fw
, xs
, sm
, lg
, x
, spin
, pulse
If (Conditional)
// $color = 'Green';
S::if($color == 'Red', function () {
return S::paragraph('The color is red.');
})->elseif($color == 'Green', function () {
return S::paragraph('The color is green.');
})->else(function () {
return S::paragraph('The color is blue.');
}),
Methods: elseif
, else
Iframe
S::iframe('http://maps.google.com/maps?q=pizza+pizza+oshawa&z=10&output=embed')->width('100%')->height(300),
Methods: width
, height
Image
S::image('https://i.imgur.com/zplGJnj.png')->alt('Time to kick it!'),
Methods: alt
, fluid
, thumbnail
Input
S::input('email')->type('email')->label('Email Address')->modelDefer(),
Methods: type
, label
, help
, placeholder
, sm
, lg
, disabled
, readonly
, keydown
, model
, modelDebounce
, modelDefer
, modelLazy
Input Group
S::inputGroup(
S::input('first_name')->placeholder('First Name')->modelDefer(),
S::input('last_name')->placeholder('First Name')->modelDefer(),
)->prepend(
S::icon('user'),
),
Methods: label
, prepend
, append
, sm
, lg
Label
S::label('Email Address'),
Methods: for
Linebreak
S::linebreak(),
Methods: none
Link
S::link('Go To Reddit')->href('https://reddit.com'),
Methods: href
, target
, active
, disabled
, stretched
List
S::list(
S::listItem('Broccoli'),
S::listItem('Carrot'),
S::listItem('Lettuce'),
),
Methods: ordered
, unstyled
, inline
List Item
S::listItem('Carrot'),
Methods: none
List Group
S::listGroup(
S::listGroupItem('Broccoli'),
S::listGroupItem('Carrot'),
S::listGroupItem('Lettuce'),
),
Methods: flush
, horizontal
List Group Item
S::listGroupItem('Broccoli'),
Methods: primary
, secondary
, success
, info
, danger
, warning
, light
, dark
, action
, active
, disabled
Livewire (Component)
S::livewire('home', ['hello' => 'world']),
Methods: none
Modal
S::modal('create-modal')->heading('Create Contact')
->body(
S::input('first_name')->label('First Name')->modelDefer(),
S::input('last_name')->label('Last Name')->modelDefer(),
)
->footer(
S::button('Cancel')->secondary()->click('$emit', 'hideModal', 'create-modal'),
S::button('Save')->primary()->click('save')
)
Methods: heading
, body
, footer
, fade
, sm
, lg
, xl
Navbar
S::navbar(
S::navbarBrand(config('app.name'))->href('/'),
S::navbarToggler(),
S::navbarCollapse(
S::navbarNav(
S::navItem(S::navLink('Login')->href(route('login'))),
S::navItem(S::navLink('Register')->href(route('register'))),
),
),
)->expandMd()->light(),
Methods: expand
, expandSm
, expandMd
, expandLg
, expandXl
, light
, dark
Navbar Brand
S::navbarBrand(config('app.name'))->href('/'),
Methods: href
, active
, disabled
, stretched
Navbar Collapse
S::navbarCollapse(
S::navbarNav(
S::navItem(S::navLink('Login')->href(route('login'))),
S::navItem(S::navLink('Register')->href(route('register'))),
),
),
Methods: none
Navbar Nav
S::navbarNav(
S::navItem(S::navLink('Login')->href(route('login'))),
S::navItem(S::navLink('Register')->href(route('register'))),
),
Methods: none
Navbar Toggler
S::navbarToggler(),
Methods: none
Nav
S::nav(
S::navItem(S::navLink('First Tab')->active()),
S::navItem(S::navLink('Second Tab')),
)->tabs(),
Methods: tabs
, pills
, fill
, justified
,
Nav Dropdown
S::navDropdown()
->toggle(
S::navLink(auth()->user()->name)->dropdownToggle(),
)
->items(
S::link('Profile')->dropdownItem()->href('/profile'),
S::button('Logout')->dropdownItem()->click('logout'),
),
Methods: toggle
, items
, right
, smRight
, mdRight
, lgRight
, xlRight
, left
, smLeft
, mdLeft
, lgLeft
, xlLeft
Nav Link
S::navLink('Home')->href('/'),
Methods: href
, active
, disabled
, stretched
Pagination
// $users = User::query()->paginate();
S::pagination($users),
Methods: none
Paragraph
S::paragraph('Close your eyes. Count to one. That is how long forever feels.'),
Methods: none
Pre
S::pre(json_encode(['Red', 'Green', 'Blue'], JSON_PRETTY_PRINT)),
Methods: none
Progress Bar
S::progressBar(25)->label('Completed'),
Methods: label
, striped
, animated
Radio
S::radio('gender')->options(['Male', 'Female'])->modelDefer(),
Methods: options
, label
, help
, inline
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
Row (Grid)
S::row(
S::col('Hello')->xs(),
S::col('World')->xs(),
),
Methods: noGutters
Select
S::select('gender')->options(['Male', 'Female'])->modelDefer(),
Methods: options
, label
, placeholder
, help
, sm
, lg
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
Span
S::span('Do your work, then step back.'),
Methods: none
Table
S::table(
S::tableBody(
S::tableRow(
S::tableData('Name'),
S::tableData('Email'),
),
),
),
Methods: responsive
, dark
, striped
, bordered
, borderless
, hover
, sm
Table Body
S::tableBody(
S::tableRow(
S::tableData('Name'),
S::tableData('Email'),
),
),
Methods: none
Table Data
S::tableData('Name'),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
Table Head
S::tableHead(
S::tableRow(
S::tableHeader('Name'),
S::tableHeader('Email'),
),
),
Methods: light
, dark
Table Header
S::tableHeader('Name'),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
Table Row
S::tableRow(
S::tableHeader('Name'),
S::tableHeader('Email'),
),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
Textarea
S::textarea('bio')->label('Biography')->modelDefer(),
Methods: rows
, label
, help
, placeholder
, sm
, lg
, disabled
, readonly
, keydown
, model
, modelDebounce
, modelDefer
, modelLazy
View
S::view('my-view-name', ['hello' => 'world']),
Methods: none
Component Utilities
Bootstrap
All components have access to Bootstrap utilities. Their methods are named similar to the corresponding Bootstrap CSS classes.
For example, setting a background color:
S::div('Hello world!')->bgDanger(),
Adjusting paddings, margins, etc:
S::div('Hello world!')->bgDanger()->p(2)->mb(3),
A couple of Bootstrap Javascript utilities are available as well, like collapse:
S::button('Collapse Paragraph')->info()->collapseToggle('para'),
S::paragraph('In work, do what you enjoy.')->collapse('para'),
Showing/hiding modals:
S::button('Show Modal')->primary()->click('$emit', 'showModal', 'the-modal'),
S::modal('the-modal')->heading('The Modal')
->body(S::paragraph('Stop trying to control.'))
->footer(S::button('Close')->secondary()->click('$emit', 'hideModal', 'the-modal'),
Showing toasts:
$this->emit('toastError', 'Oh no! There was a problem.');
Livewire
Livewire actions are mapped using the corresponding wire:
conventions.
For example, setting click
actions:
S::button('Delete')->danger()->click('deleteItem'),
In this case, you would have a deleteItem
function inside your component.
click
actions with one or more parameters:
S::link('Update')->clickPrevent('updateItem', $item->id),
S::button('Close')->secondary()->click('$emit', 'hideModal', 'the-modal'),
Modelling input elements:
S::input('name')->label('Name')->modelLazy(),
S::textarea('desc')->label('Description')->modelDefer(),
The input name is automatically wired via the specified model methods. You may access this data via the $this->model
property array inside of your component. For example, $this->model['name']
.
loading
and polling
:
S::span('Loading...')->loading(),
S::span(now())->poll(),
Digging Deeper
Take a look at the package Traits for a complete list of all utility methods along with their parameters.
Automation
Automatic Routes
When creating a full page Swift component, simply declare a $routeUri
property in order to auto-route the component:
class Login extends SwiftComponent
{
public $routeUri = '/login';
You can view a list of your routes via the route:list
artisan command.
Automatic Migrations
In order to use automatic migrations, simply specify a migration
method in your model:
class Lead extends Model
{
use SwiftModel;
public function migration(Blueprint $table)
{
$table->id();
$table->string('name');
$table->timestamps();
}
Now run the automatic migration command:
php artisan migrate:auto
The package uses Doctrine DBAL in order to diff the existing model table and make the necessary changes to it. If the table does not exist, it will create it.
You can also pass --fresh
and/or --seed
to the migrate:auto
command in order to get fresh migrations and/or run your seeders afterwards:
php artisan migrate:auto --fresh --seed
If your app contains traditional migrations in the database/migrations
folder, they will be handled before the automatic migrations.
Automatic Fillables
Models containing the SwiftModel
trait have automatic fillables, meaning their $fillable
property is automatically generated via the model table column names.
Config, Views, & Translations
The config, views, & translation files can be published using the vendor:publish
command.