├── 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 = "\(body)
"
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 |
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 = "\(body)
"
73 |
74 | return .string(alert)
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bootstrap 🍃
2 |
3 | [](http://swift.org)
4 | [](http://vapor.codes)
5 | [](https://circleci.com/gh/nodes-vapor/bootstrap)
6 | [](https://codebeat.co/projects/github-com-nodes-vapor-bootstrap-master)
7 | [](https://codecov.io/gh/nodes-vapor/bootstrap)
8 | [](http://clayallsopp.github.io/readme-score?url=https://github.com/nodes-vapor/bootstrap)
9 | [](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 |
--------------------------------------------------------------------------------