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.
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
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.
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.
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'),
S::alert('Something happened!')->success(),
Methods: dismissable
, fade
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
S::badge('New')->primary(),
Methods: primary
, secondary
, success
, info
, danger
, warning
, light
, dark
, pill
S::blockquote('Be like water.')->footer('The Tao'),
Methods: footer
S::breadcrumb(
S::breadcrumbItem('Home'),
S::breadcrumbItem('Page'),
),
Methods: none
S::breadcrumbItem('Home')->active(Route::is('home')),
Methods: active
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
S::buttonGroup(
S::button('Do Something')->primary()->click('doSomething'),
S::button('Something Else')->secondary()->click('somethingElse'),
),
Methods: none
S::buttonToolbar(
S::buttonGroup(
S::button('Do Something')->primary()->click('doSomething'),
S::button('Something Else')->secondary()->click('somethingElse'),
),
),
Methods: none
S::card()
->header('Hello World')
->body('The whole world belongs to you')
->footer('The Tao'),
Methods: header
, image
, body
, footer
S::checkbox('agree')->label('Agree to TOS')->modelDefer(),
Methods: label
, checkboxLabel
, help
, switch
, inline
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
S::code('$hello = $world;'),
Methods: none
S::col('Be like water.')->md(2),
Methods: xs
, auto
, sm
, smAuto
, md
, mdAuto
, lg
, lgAuto
, xl
, xlAuto
, offset
, offsetSm
, offsetMd
, offsetLg
, offsetXl
S::container(
S::paragraph('In work, do what you enjoy.'),
),
Methods: none
S::div('The best athlete wants his opponent at his best.'),
Methods: none
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
S::each(['Red', 'Green', 'Blue'], function ($color, $key) {
return S::div($key . ': ' . $color);
})->empty(
S::paragraph('No colors to display.')
),
Methods: empty
S::file('avatar')->label('Avatar')->placeholder('Choose avatar')->modelDefer(),
Methods: label
, placeholder
, help
, model
, modelDebounce
, modelDefer
, modelLazy
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
S::formGroup(
S::label('First Name'),
S::input('first_name')->modelDefer(),
),
Methods: label
S::formRow(
S::col(S::input('first')->placeholder('First')->model())->md(),
S::col(S::input('last')->placeholder('Last')->model())->md(),
),
Methods: none
S::heading('Laravel Swift')->size(2),
Methods: size
S::horizontalRule(),
Methods: none
S::icon('pastafarianism'),
Methods: solid
, regular
, light
, duotone
, brand
, fw
, xs
, sm
, lg
, x
, spin
, pulse
// $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
S::iframe('http://maps.google.com/maps?q=pizza+pizza+oshawa&z=10&output=embed')->width('100%')->height(300),
Methods: width
, height
S::image('https://i.imgur.com/zplGJnj.png')->alt('Time to kick it!'),
Methods: alt
, fluid
, thumbnail
S::input('email')->type('email')->label('Email Address')->modelDefer(),
Methods: type
, label
, help
, placeholder
, sm
, lg
, disabled
, readonly
, keydown
, model
, modelDebounce
, modelDefer
, modelLazy
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
S::label('Email Address'),
Methods: for
S::linebreak(),
Methods: none
S::link('Go To Reddit')->href('https://reddit.com'),
Methods: href
, target
, active
, disabled
, stretched
S::list(
S::listItem('Broccoli'),
S::listItem('Carrot'),
S::listItem('Lettuce'),
),
Methods: ordered
, unstyled
, inline
S::listItem('Carrot'),
Methods: none
S::listGroup(
S::listGroupItem('Broccoli'),
S::listGroupItem('Carrot'),
S::listGroupItem('Lettuce'),
),
Methods: flush
, horizontal
S::listGroupItem('Broccoli'),
Methods: primary
, secondary
, success
, info
, danger
, warning
, light
, dark
, action
, active
, disabled
S::livewire('home', ['hello' => 'world']),
Methods: none
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
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
S::navbarBrand(config('app.name'))->href('/'),
Methods: href
, active
, disabled
, stretched
S::navbarCollapse(
S::navbarNav(
S::navItem(S::navLink('Login')->href(route('login'))),
S::navItem(S::navLink('Register')->href(route('register'))),
),
),
Methods: none
S::navbarNav(
S::navItem(S::navLink('Login')->href(route('login'))),
S::navItem(S::navLink('Register')->href(route('register'))),
),
Methods: none
S::navbarToggler(),
Methods: none
S::nav(
S::navItem(S::navLink('First Tab')->active()),
S::navItem(S::navLink('Second Tab')),
)->tabs(),
Methods: tabs
, pills
, fill
, justified
,
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
S::navLink('Home')->href('/'),
Methods: href
, active
, disabled
, stretched
// $users = User::query()->paginate();
S::pagination($users),
Methods: none
S::paragraph('Close your eyes. Count to one. That is how long forever feels.'),
Methods: none
S::pre(json_encode(['Red', 'Green', 'Blue'], JSON_PRETTY_PRINT)),
Methods: none
S::progressBar(25)->label('Completed'),
Methods: label
, striped
, animated
S::radio('gender')->options(['Male', 'Female'])->modelDefer(),
Methods: options
, label
, help
, inline
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
S::row(
S::col('Hello')->xs(),
S::col('World')->xs(),
),
Methods: noGutters
S::select('gender')->options(['Male', 'Female'])->modelDefer(),
Methods: options
, label
, placeholder
, help
, sm
, lg
, disabled
, model
, modelDebounce
, modelDefer
, modelLazy
S::span('Do your work, then step back.'),
Methods: none
S::table(
S::tableBody(
S::tableRow(
S::tableData('Name'),
S::tableData('Email'),
),
),
),
Methods: responsive
, dark
, striped
, bordered
, borderless
, hover
, sm
S::tableBody(
S::tableRow(
S::tableData('Name'),
S::tableData('Email'),
),
),
Methods: none
S::tableData('Name'),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
S::tableHead(
S::tableRow(
S::tableHeader('Name'),
S::tableHeader('Email'),
),
),
Methods: light
, dark
S::tableHeader('Name'),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
S::tableRow(
S::tableHeader('Name'),
S::tableHeader('Email'),
),
Methods: active
, primary
, secondary
, success
, info
, danger
, warning
, light
, dark
S::textarea('bio')->label('Biography')->modelDefer(),
Methods: rows
, label
, help
, placeholder
, sm
, lg
, disabled
, readonly
, keydown
, model
, modelDebounce
, modelDefer
, modelLazy
S::view('my-view-name', ['hello' => 'world']),
Methods: none
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 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(),
Take a look at the package Traits for a complete list of all utility methods along with their parameters.
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.
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.
Models containing the SwiftModel
trait have automatic fillables, meaning their $fillable
property is automatically generated via the model table column names.
The config, views, & translation files can be published using the vendor:publish
command.