├── index.html ├── .codebeatignore ├── .codecov.yml ├── Tests ├── LinuxMain.swift └── BootstrapTests │ ├── XCTestManifests.swift │ └── BootstrapTests.swift ├── .gitignore ├── .swiftlint.yml ├── .github └── main.workflow ├── Sources └── Bootstrap │ ├── Models │ └── ColorKeys.swift │ ├── Extensions │ └── LeafTagConfig.swift │ └── Tags │ ├── FormFile.swift │ ├── FormRadio.swift │ ├── FormCheckbox.swift │ ├── TextArea.swift │ ├── Input.swift │ ├── Badge.swift │ ├── ButtonToolbar.swift │ ├── Button.swift │ ├── Breadcrumb.swift │ ├── ButtonGroup.swift │ └── Alert.swift ├── Package.swift ├── LICENSE ├── .circleci └── config.yml └── README.md /index.html: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.codebeatignore: -------------------------------------------------------------------------------- 1 | Public/** 2 | Resources/Assets/** -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "0...100" 3 | ignore: 4 | - "Tests" -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import BootstrapTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += BootstrapTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Packages 2 | Package.pins 3 | Package.resolved 4 | .build 5 | .idea 6 | xcuserdata 7 | *.xcodeproj 8 | Config/secrets/ 9 | .DS_Store 10 | node_modules/ 11 | bower_components/ 12 | .swift-version 13 | -------------------------------------------------------------------------------- /Tests/BootstrapTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(BootstrapTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | function_body_length: 4 | warning: 60 5 | variable_name: 6 | min_length: 7 | warning: 2 8 | line_length: 80 9 | disabled_rules: 10 | - opening_brace 11 | colon: 12 | flexible_right_spacing: true 13 | -------------------------------------------------------------------------------- /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "Auto docs" { 2 | on = "release" 3 | resolves = ["Jazzy docs"] 4 | } 5 | 6 | action "Jazzy docs" { 7 | uses = "nodes-vapor/github-actions/actions/jazzy-docs@master" 8 | secrets = [ 9 | "GITHUB_TOKEN", 10 | "GH_USER", 11 | "GH_EMAIL", 12 | ] 13 | env = { 14 | TARGET = "Bootstrap" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/BootstrapTests/BootstrapTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Bootstrap 3 | 4 | final class BootstrapTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | XCTAssertEqual(true, true) 10 | } 11 | 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Models/ColorKeys.swift: -------------------------------------------------------------------------------- 1 | /// Bootstrap Color Definitions 2 | enum ColorKeys: String { 3 | /// Bootstrap Primary Color 4 | case primary 5 | /// Bootstrap Secondary Color 6 | case secondary 7 | /// Bootstrap Success Color 8 | case success 9 | /// Bootstrap Danger Color 10 | case danger 11 | /// Bootstrap Warning Color 12 | case warning 13 | /// Bootstrap Info Color 14 | case info 15 | /// Bootstrap Light Color 16 | case light 17 | /// Bootstrap Dark Color 18 | case dark 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Extensions/LeafTagConfig.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | 3 | extension LeafTagConfig { 4 | public mutating func useBootstrapLeafTags() { 5 | use([ 6 | "bs:button": ButtonTag(), 7 | "bs:buttonGroup": ButtonGroupTag(), 8 | "bs:buttonToolbar": ButtonToolbarTag(), 9 | "bs:alert": AlertTag(), 10 | "bs:input": InputTag(), 11 | "bs:badge": BadgeTag(), 12 | "bs:breadCrumb": BreadCrumbTag(), 13 | "bs:breadCrumbItem": BreadCrumbItemTag(), 14 | "bs:textArea": TextAreaTag() 15 | ]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Bootstrap", 6 | products: [ 7 | .library(name: "Bootstrap", targets: ["Bootstrap"]) 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/nodes-vapor/sugar.git", from: "4.0.0-beta.2"), 11 | .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc"), 12 | .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), 13 | ], 14 | targets: [ 15 | .target(name: "Bootstrap", dependencies: ["Leaf", "Vapor", "Sugar"]), 16 | .testTarget(name: "BootstrapTests", dependencies: ["Bootstrap"]) 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/FormFile.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import TemplateKit 3 | 4 | public final class FormFile: TagRenderer { 5 | 6 | public func render(tag: TagContext) throws -> Future { 7 | var classes = "" 8 | var attributes = "" 9 | 10 | try tag.requireNoBody() 11 | 12 | for index in 0...1 { 13 | if 14 | let param = tag.parameters[safe: index]?.string, 15 | !param.isEmpty 16 | { 17 | switch index { 18 | case 0: classes = param 19 | case 1: attributes = param 20 | default: () 21 | } 22 | } 23 | } 24 | 25 | let c = "form-control \(classes)" 26 | let button = "" 27 | return Future.map(on: tag) { return .string(button) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/FormRadio.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import TemplateKit 3 | 4 | public final class FormRadio: TagRenderer { 5 | 6 | public func render(tag: TagContext) throws -> Future { 7 | var classes = "" 8 | var attributes = "" 9 | 10 | try tag.requireNoBody() 11 | 12 | for index in 0...1 { 13 | if 14 | let param = tag.parameters[safe: index]?.string, 15 | !param.isEmpty 16 | { 17 | switch index { 18 | case 0: classes = param 19 | case 1: attributes = param 20 | default: () 21 | } 22 | } 23 | } 24 | 25 | let c = "form-control \(classes)" 26 | let button = "" 27 | return Future.map(on: tag) { return .string(button) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/FormCheckbox.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import TemplateKit 3 | 4 | public final class FormCheckbox: TagRenderer { 5 | 6 | public func render(tag: TagContext) throws -> Future { 7 | var classes = "" 8 | var attributes = "" 9 | 10 | try tag.requireNoBody() 11 | 12 | for index in 0...1 { 13 | if 14 | let param = tag.parameters[safe: index]?.string, 15 | !param.isEmpty 16 | { 17 | switch index { 18 | case 0: classes = param 19 | case 1: attributes = param 20 | default: () 21 | } 22 | } 23 | } 24 | 25 | let c = "form-control \(classes)" 26 | let button = "" 27 | return Future.map(on: tag) { return .string(button) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nodes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/TextArea.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | public final class TextAreaTag: TagRenderer { 6 | private static let paramCount: Int = 3 7 | 8 | public func render(tag: TagContext) throws -> Future { 9 | try tag.requireNoBody() 10 | try tag.requireParameterCount(upTo: TextAreaTag.paramCount) 11 | 12 | var classes = "form-control" 13 | var attributes = "rows='3'" 14 | var value = "" 15 | 16 | for index in 0...2 { 17 | if 18 | let param = tag.parameters[safe: index]?.string, 19 | !param.isEmpty 20 | { 21 | switch index { 22 | case 0: classes = param 23 | case 1: attributes = param 24 | case 2: value = param 25 | default: break 26 | } 27 | } 28 | } 29 | 30 | let html = "" 31 | return tag.future(.string(html)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/Input.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | public final class InputTag: TagRenderer { 6 | enum Keys: String { 7 | case text 8 | case email 9 | case password 10 | case hidden 11 | } 12 | 13 | public func render(tag: TagContext) throws -> Future { 14 | var inputType = Keys.text.rawValue 15 | var classes = "" 16 | var attributes = "" 17 | 18 | try tag.requireNoBody() 19 | 20 | for index in 0...2 { 21 | if 22 | let param = tag.parameters[safe: index]?.string, 23 | !param.isEmpty 24 | { 25 | switch index { 26 | case 0: inputType = param 27 | case 1: classes = param 28 | case 2: attributes = param 29 | default: break 30 | } 31 | } 32 | } 33 | 34 | guard let parsedType = Keys(rawValue: inputType) else { 35 | throw tag.error(reason: "Wrong argument given: \(inputType)") 36 | } 37 | 38 | let c = "form-control \(classes)" 39 | let button = "" 40 | return tag.future(.string(button)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/Badge.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | public final class BadgeTag: TagRenderer { 6 | public func render(tag: TagContext) throws -> Future { 7 | let body = try tag.requireBody() 8 | 9 | var style = ColorKeys.primary.rawValue 10 | var classes = "" 11 | var attributes = "" 12 | 13 | for index in 0...2 { 14 | if 15 | let param = tag.parameters[safe: index]?.string, 16 | !param.isEmpty 17 | { 18 | switch index { 19 | case 0: style = param 20 | case 1: classes = param 21 | case 2: attributes = param 22 | default: break 23 | } 24 | } 25 | } 26 | 27 | guard let parsedStyle = ColorKeys(rawValue: style) else { 28 | throw tag.error(reason: "Wrong argument given: \(style)") 29 | } 30 | 31 | return tag.serializer.serialize(ast: body).map(to: TemplateData.self) { body in 32 | let c = "badge badge-\(parsedStyle) \(classes)" 33 | let b = String(data: body.data, encoding: .utf8) ?? "" 34 | 35 | let badge = "\(b)" 36 | return .string(badge) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/ButtonToolbar.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | /// Button Toolbar 6 | public final class ButtonToolbarTag: TagRenderer { 7 | private static let paramCount: Int = 2 8 | 9 | public func render(tag: TagContext) throws -> Future { 10 | let body = try tag.requireBody() 11 | 12 | try tag.requireParameterCount(upTo: ButtonToolbarTag.paramCount) 13 | 14 | var classes = "" 15 | var aria = UUID().uuidString 16 | 17 | for index in 0...1 { 18 | if let param = tag.parameters[safe: index]?.string, 19 | param.isEmpty == false { 20 | 21 | switch index { 22 | case 0: classes = param 23 | case 1: aria = param 24 | default: break 25 | } 26 | } 27 | } 28 | 29 | return tag.serializer.serialize(ast: body).map(to: TemplateData.self) { er in 30 | let body = String(data: er.data, encoding: .utf8) ?? "" 31 | 32 | guard body.count > 0 else { 33 | throw tag.error(reason: "Body Data Expected") 34 | } 35 | 36 | var group = "" 41 | 42 | return .string(group) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/Button.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | public final class ButtonTag: TagRenderer { 6 | /// A button style can be any of `ColorKeys` or "link". 7 | public enum Keys: String { 8 | case link 9 | } 10 | 11 | public func render(tag: TagContext) throws -> Future { 12 | let body = try tag.requireBody() 13 | 14 | var style = ColorKeys.primary.rawValue 15 | var classes = "" 16 | var attributes = "" 17 | 18 | for index in 0...2 { 19 | if 20 | let param = tag.parameters[safe: index]?.string, 21 | !param.isEmpty 22 | { 23 | switch index { 24 | case 0: style = param 25 | case 1: classes = param 26 | case 2: attributes = param 27 | default: break 28 | } 29 | } 30 | } 31 | 32 | guard ColorKeys(rawValue: style) != nil || Keys(rawValue: style) != nil else { 33 | throw tag.error(reason: "Wrong argument given: \(style)") 34 | } 35 | 36 | return tag.serializer.serialize(ast: body).map(to: TemplateData.self) { body in 37 | let c = "btn btn-\(style) \(classes)" 38 | let b = String(data: body.data, encoding: .utf8) ?? "" 39 | 40 | let button = "" 41 | return .string(button) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | MacOS: 4 | macos: 5 | xcode: "9.3.0" 6 | steps: 7 | - checkout 8 | - restore_cache: 9 | keys: 10 | - v1-spm-deps-{{ checksum "Package.swift" }} 11 | - run: 12 | name: Install CMySQL and CTLS 13 | command: | 14 | brew tap vapor/homebrew-tap 15 | brew install cmysql 16 | brew install ctls 17 | brew install libressl 18 | - run: 19 | name: Build and Run Tests 20 | no_output_timeout: 1800 21 | command: | 22 | swift package generate-xcodeproj --enable-code-coverage 23 | xcodebuild -scheme Bootstrap-Package -enableCodeCoverage YES test | xcpretty 24 | - run: 25 | name: Report coverage to Codecov 26 | command: | 27 | bash <(curl -s https://codecov.io/bash) 28 | - save_cache: 29 | key: v1-spm-deps-{{ checksum "Package.swift" }} 30 | paths: 31 | - .build 32 | Linux: 33 | docker: 34 | - image: nodesvapor/vapor-ci:swift-4.1 35 | steps: 36 | - checkout 37 | - restore_cache: 38 | keys: 39 | - v2-spm-deps-{{ checksum "Package.swift" }} 40 | - run: 41 | name: Copy Package file 42 | command: cp Package.swift res 43 | - run: 44 | name: Build and Run Tests 45 | no_output_timeout: 1800 46 | command: | 47 | swift test -Xswiftc -DNOJSON 48 | - run: 49 | name: Restoring Package file 50 | command: mv res Package.swift 51 | - save_cache: 52 | key: v2-spm-deps-{{ checksum "Package.swift" }} 53 | paths: 54 | - .build 55 | workflows: 56 | version: 2 57 | build-and-test: 58 | jobs: 59 | - MacOS 60 | - Linux 61 | experimental: 62 | notify: 63 | branches: 64 | only: 65 | - master 66 | - develop -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/Breadcrumb.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import Sugar 3 | import TemplateKit 4 | 5 | public final class BreadCrumbTag: TagRenderer { 6 | public func render(tag: TagContext) throws -> Future { 7 | let body = try tag.requireBody() 8 | var classes = "" 9 | var attributes = "" 10 | 11 | for index in 0...1 { 12 | if 13 | let param = tag.parameters[safe: index]?.string, 14 | !param.isEmpty 15 | { 16 | switch index { 17 | case 0: classes = param 18 | case 1: attributes = param 19 | default: break 20 | } 21 | } 22 | } 23 | 24 | return tag.serializer.serialize(ast: body).map { body in 25 | let b = String(data: body.data, encoding: .utf8) ?? "" 26 | 27 | let breadcrumb = """ 28 |
29 | 32 |
33 | """ 34 | return .string(breadcrumb) 35 | } 36 | } 37 | } 38 | 39 | public final class BreadCrumbItemTag: TagRenderer { 40 | public func render(tag: TagContext) throws -> Future { 41 | let body = try tag.requireBody() 42 | var classes = "" 43 | var attributes = "" 44 | 45 | for index in 0...2 { 46 | if 47 | let param = tag.parameters[safe: index]?.string, 48 | !param.isEmpty 49 | { 50 | switch index { 51 | case 0: classes = param 52 | case 1: attributes = param 53 | default: break 54 | } 55 | } 56 | } 57 | 58 | return tag.serializer.serialize(ast: body).map { body in 59 | let c = "breadcrumb-item \(classes)" 60 | let b = String(data: body.data, encoding: .utf8) ?? "" 61 | let breadcrumbItem = "
  • \(b)
  • " 62 | return .string(breadcrumbItem) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/ButtonGroup.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import TemplateKit 3 | 4 | /// Bootstrap Button Group Tag 5 | public final class ButtonGroupTag: TagRenderer { 6 | enum GroupKeys: String { 7 | case standard = "btn-group" 8 | case vertical = "btn-group-vertical" 9 | } 10 | 11 | private static let paramCount: Int = 3 12 | 13 | public func render(tag: TagContext) throws -> Future { 14 | let body = try tag.requireBody() 15 | 16 | try tag.requireParameterCount(upTo: ButtonGroupTag.paramCount) 17 | 18 | var group = GroupKeys.standard 19 | var classes: String? 20 | var aria = UUID().uuidString 21 | 22 | if tag.parameters.count > 0 { 23 | guard let param = tag.parameters[0].bool else { 24 | throw tag.error( 25 | reason: "Wrong type given (expected a bool): \(type(of: tag.parameters[0]))" 26 | ) 27 | } 28 | 29 | if param { 30 | group = .vertical 31 | } 32 | } 33 | 34 | if tag.parameters.count > 1 { 35 | guard let param = tag.parameters[1].string else { 36 | throw tag.error( 37 | reason: "Wrong type given (expected a string): \(type(of: tag.parameters[1]))" 38 | ) 39 | } 40 | 41 | if param.count > 0 { 42 | classes = param 43 | } 44 | } 45 | 46 | if tag.parameters.count > 2 { 47 | guard let param = tag.parameters[2].string else { 48 | throw tag.error( 49 | reason: "Wrong type given (expected a string): \(type(of: tag.parameters[2]))" 50 | ) 51 | } 52 | 53 | if param.count > 0 { 54 | aria = param 55 | } 56 | } 57 | 58 | return tag.serializer.serialize(ast: body).map(to: TemplateData.self) { er in 59 | let body = String(data: er.data, encoding: .utf8) ?? "" 60 | 61 | /// Button Group isn't useful without a body 62 | guard body.count > 0 else { 63 | throw tag.error(reason: "Body Data Expected") 64 | } 65 | 66 | var group = "
    \(body)
    " 71 | 72 | return .string(group) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Bootstrap/Tags/Alert.swift: -------------------------------------------------------------------------------- 1 | import Leaf 2 | import TemplateKit 3 | 4 | /// Bootstrap Alert Tag 5 | /// 6 | public final class AlertTag: TagRenderer { 7 | /** 8 | Render 9 | 10 | - Important: This is the important read for this func 11 | - Parameters: 12 | - tag : TagContext 13 | - Returns: Future 14 | */ 15 | public func render(tag: TagContext) throws -> Future { 16 | var style = ColorKeys.primary.rawValue 17 | var classes: String? 18 | var attributes: String? 19 | 20 | if tag.parameters.count > 0 { 21 | guard let param = tag.parameters[0].string else { 22 | throw tag.error( 23 | reason: "Wrong type given (expected a string): \(type(of: tag.parameters[0]))" 24 | ) 25 | } 26 | 27 | if param.count > 0 { 28 | style = param 29 | } 30 | } 31 | 32 | if tag.parameters.count > 1 { 33 | guard let param = tag.parameters[1].string else { 34 | throw tag.error( 35 | reason: "Wrong type given (expected a string): \(type(of: tag.parameters[1]))" 36 | ) 37 | } 38 | 39 | if param.count > 0 { 40 | classes = param 41 | } 42 | } 43 | 44 | if tag.parameters.count > 2 { 45 | guard let param = tag.parameters[2].string else { 46 | throw tag.error( 47 | reason: "Wrong type given (expected a string): \(type(of: tag.parameters[2]))" 48 | ) 49 | } 50 | 51 | if param.count > 0 { 52 | attributes = param 53 | } 54 | } 55 | 56 | guard let parsedStyle = ColorKeys(rawValue: style) else { 57 | throw tag.error(reason: "Wrong argument given: \(style)") 58 | } 59 | 60 | guard let body = tag.body else { 61 | throw tag.error(reason: "Wrong body given: \(String(describing: tag.body))") 62 | } 63 | 64 | return tag.serializer.serialize(ast: body).map(to: TemplateData.self) { er in 65 | let body = String(data: er.data, encoding: .utf8) ?? "" 66 | 67 | var alert = "" 73 | 74 | return .string(alert) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bootstrap 🍃 2 | 3 | [![Swift Version](https://img.shields.io/badge/Swift-4.1-brightgreen.svg)](http://swift.org) 4 | [![Vapor Version](https://img.shields.io/badge/Vapor-3-30B6FC.svg)](http://vapor.codes) 5 | [![Circle CI](https://circleci.com/gh/nodes-vapor/bootstrap/tree/master.svg?style=shield)](https://circleci.com/gh/nodes-vapor/bootstrap) 6 | [![codebeat badge](https://codebeat.co/badges/40b8811e-2949-427a-a2a7-437209475f7d)](https://codebeat.co/projects/github-com-nodes-vapor-bootstrap-master) 7 | [![codecov](https://codecov.io/gh/nodes-vapor/bootstrap/branch/master/graph/badge.svg)](https://codecov.io/gh/nodes-vapor/bootstrap) 8 | [![Readme Score](http://readme-score-api.herokuapp.com/score.svg?url=https://github.com/nodes-vapor/bootstrap)](http://clayallsopp.github.io/readme-score?url=https://github.com/nodes-vapor/bootstrap) 9 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nodes-vapor/bootstrap/master/LICENSE) 10 | 11 | This package wraps Bootstrap elements into convenient Leaf-Tags. 12 | 13 | 14 | # Installation 15 | 16 | Add `Bootstrap` to the package dependencies (in your `Package.swift` file): 17 | 18 | ```swift 19 | dependencies: [ 20 | ..., 21 | .package(url: "https://github.com/nodes-vapor/bootstrap.git", from: "4.0.0-beta") 22 | ] 23 | ``` 24 | 25 | as well as to your target (e.g. "App"): 26 | 27 | ```swift 28 | targets: [ 29 | ... 30 | .target( 31 | name: "App", 32 | dependencies: [... "Bootstrap" ...] 33 | ), 34 | ... 35 | ] 36 | ``` 37 | 38 | ## Getting started 🚀 39 | 40 | First import Bootstrap and Leaf inside your `configure.swift` 41 | 42 | ```swift 43 | import Bootstrap 44 | import Leaf 45 | ``` 46 | 47 | ### Adding the Leaf tags 48 | 49 | In order to render the Bootstrap elements, you will need to add the Bootstrap Leaf tags: 50 | 51 | ```swift 52 | public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws { 53 | services.register { _ -> LeafTagConfig in 54 | var tags = LeafTagConfig.default() 55 | tags.useBootstrapLeafTags() 56 | return tags 57 | } 58 | } 59 | ``` 60 | 61 | ## Supported tags 62 | 63 | - [Alert](#alert) 64 | - [Badge](#badge) 65 | - [Button](#button) 66 | - [Button Group](#button-group) 67 | - [Button Toolbar](#button-toolbar) 68 | - [Input](#input) 69 | - [Breadcrumb](#breadcrumb) 70 | - [Textarea](#textarea) 71 | 72 | ### Alert 73 | 74 | ``` 75 | #bs:alert() { alert text } 76 | ``` 77 | 78 | ### Badge 79 | 80 | ``` 81 | #bs:badge(type?, classExtras?, attributes?) { badge text } 82 | ``` 83 | 84 | ### Button 85 | 86 | ``` 87 | #bs:button(type?, classExtras?, attributes?) { btn text } 88 | ``` 89 | 90 | ### Button Group 91 | 92 | ``` 93 | #bs:buttonGroup(isVertical, classExtras?, Aria?) { } 94 | ``` 95 | 96 | ``` 97 | #bs:buttonGroup(false, "btn-group-sm") { 98 | #bs:button() { First Option } 99 | #bs:button("danger") { Second Option} 100 | #bs:button() { Third Option} 101 | } 102 | 103 | ``` 104 | 105 | ### Button Toolbar 106 | 107 | ``` 108 | #bs:buttonToolbar(classExtras?, Aria?) { } 109 | ``` 110 | 111 | ``` 112 | #bs:buttonToolbar() { 113 | #bs:button() { First Option } 114 | #bs:button("danger") { Second Option} 115 | #bs:button() { Third Option} 116 | } 117 | ``` 118 | 119 | ### Input 120 | 121 | ``` 122 | #bs:input(type?, classExtras?, attributes?) 123 | ``` 124 | 125 | ### Breadcrumb 126 | 127 | ``` 128 | #bs:breadcrumb(classExtras?, attributes?) { 129 | #bs:breadcrumbItem(classExtras?, attributes?) { Home } 130 | #bs:breadcrumbItem(classExtras?, attributes?) { Profile } 131 | } 132 | ``` 133 | 134 | ### Textarea 135 | 136 | ``` 137 | #bs:textArea(classExtras?, attributes?, value?) 138 | ``` 139 | 140 | ## 🏆 Credits 141 | 142 | This package is developed and maintained by the Vapor team at [Nodes](https://www.nodesagency.com). The package owner for this project is [Martin](http://github.com/martinlasek). 143 | 144 | 145 | ## 📄 License 146 | 147 | This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 148 | 149 | 150 | ## Docs 151 | [Read the docs](https://nodes-vapor.github.io/bootstrap-actions/docs/) 152 | 153 | --------------------------------------------------------------------------------