├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .idea
├── .gitignore
├── .name
├── misc.xml
├── uiDesigner.xml
└── vcs.xml
├── ASSETS
├── accordion.gif
├── alerts.gif
├── androidIcon.png
├── badge.png
├── badgeButton.gif
├── breadcrumb.gif
├── buttons.png
├── carousel.gif
├── checkboxes.gif
├── closebutton.gif
├── collapse.gif
├── colorpicker.gif
├── custombuttons.gif
├── darkBackgroundDropdown.gif
├── disabledDropdown.gif
├── dropdowns.png
├── filepicker.gif
├── iconbutton.gif
├── inputs.gif
├── loadingButton.gif
├── loadingButtonText.gif
├── logo.svg
├── modal.gif
├── navbar.gif
├── navbar2.gif
├── offcanvas.gif
├── pagination.gif
├── placeholderDropdown.gif
├── progress.gif
├── radiobuttons.gif
├── range.gif
├── select.gif
├── spinner.gif
├── switches.gif
├── textarea.gif
├── toast.gif
└── tooltip.gif
├── README.md
├── bootstrap
├── build.gradle.kts
└── src
│ └── jsMain
│ └── kotlin
│ └── com
│ └── stevdza
│ └── san
│ └── kotlinbs
│ ├── BootstrapApp.kt
│ ├── components
│ ├── BSAccordion.kt
│ ├── BSAlert.kt
│ ├── BSBadge.kt
│ ├── BSBreadcrumb.kt
│ ├── BSButton.kt
│ ├── BSCarousel.kt
│ ├── BSCloseButton.kt
│ ├── BSCollapse.kt
│ ├── BSDropdown.kt
│ ├── BSIcon.kt
│ ├── BSIconButton.kt
│ ├── BSModal.kt
│ ├── BSNavBar.kt
│ ├── BSPagination.kt
│ ├── BSProgress.kt
│ ├── BSSpinner.kt
│ ├── BSToast.kt
│ ├── BSTooltip.kt
│ ├── Offcanvas.kt
│ ├── SpanText.kt
│ └── SvgElement.kt
│ ├── forms
│ ├── BSCheckbox.kt
│ ├── BSColorPicker.kt
│ ├── BSFileInput.kt
│ ├── BSInput.kt
│ ├── BSRadioButton.kt
│ ├── BSRange.kt
│ ├── BSSelect.kt
│ ├── BSSwitch.kt
│ └── BSTextArea.kt
│ ├── icons
│ └── BSIcons.kt
│ ├── models
│ ├── AccordionItem.kt
│ ├── AlertIcon.kt
│ ├── AlertStyle.kt
│ ├── BSBorderRadius.kt
│ ├── BackgroundStyle.kt
│ ├── BadgeVariant.kt
│ ├── BorderColor.kt
│ ├── BreadcrumbItem.kt
│ ├── CarouselItem.kt
│ ├── DropdownDirection.kt
│ ├── InputSize.kt
│ ├── InputValidation.kt
│ ├── ModalSize.kt
│ ├── PaginationButtons.kt
│ ├── PaginationSize.kt
│ ├── SelectSize.kt
│ ├── SpinnerStyle.kt
│ ├── SpinnerVariant.kt
│ ├── ToastPlacement.kt
│ ├── ToastStyle.kt
│ ├── TooltipDirection.kt
│ ├── button
│ │ ├── ButtonBadge.kt
│ │ ├── ButtonCustomization.kt
│ │ ├── ButtonProperty.kt
│ │ ├── ButtonSize.kt
│ │ ├── ButtonType.kt
│ │ └── ButtonVariant.kt
│ ├── navbar
│ │ ├── NavBarBrand.kt
│ │ ├── NavBarButton.kt
│ │ ├── NavBarExpand.kt
│ │ ├── NavBarInputField.kt
│ │ ├── NavBarOffcanvas.kt
│ │ ├── NavDropdownItem.kt
│ │ ├── NavItem.kt
│ │ └── NavLink.kt
│ └── offcanvas
│ │ ├── OffcanvasBreakpoint.kt
│ │ └── OffcanvasPlacement.kt
│ └── util
│ └── UniqueIdGenerator.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── settings.gradle.kts
└── site
├── .gitignore
├── .kobweb
└── conf.yaml
├── build.gradle.kts
└── src
└── jsMain
├── kotlin
└── com
│ └── stevdza
│ └── san
│ └── kotlinbs
│ ├── MyApp.kt
│ └── pages
│ └── Index.kt
└── resources
└── public
└── favicon.ico
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Temp test kotlin npm install
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout code
11 | uses: actions/checkout@v3
12 |
13 | - name: Setup Java
14 | uses: actions/setup-java@v3
15 | with:
16 | distribution: temurin
17 | java-version: 11
18 |
19 | - name: Setup Gradle
20 | uses: gradle/gradle-build-action@v2
21 |
22 | - name: Make gradlew executable
23 | run: chmod +x gradlew
24 |
25 | - name: Test NPM install is working
26 | run: ./gradlew :kotlinNpmInstall
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # General ignores
2 | .DS_Store
3 | build
4 | out
5 | kotlin-js-store
6 |
7 | # IntelliJ ignores
8 | *.iml
9 | /*.ipr
10 |
11 | /.idea/caches
12 | /.idea/libraries
13 | /.idea/modules.xml
14 | /.idea/workspace.xml
15 | /.idea/gradle.xml
16 | /.idea/navEditor.xml
17 | /.idea/assetWizardSettings.xml
18 | /.idea/artifacts
19 | /.idea/compiler.xml
20 | /.idea/jarRepositories.xml
21 | /.idea/*.iml
22 | /.idea/modules
23 | /.idea/libraries-with-intellij-classes.xml
24 |
25 | # Gradle ignores
26 | .gradle
27 |
28 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | kotlinbs
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ASSETS/accordion.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/accordion.gif
--------------------------------------------------------------------------------
/ASSETS/alerts.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/alerts.gif
--------------------------------------------------------------------------------
/ASSETS/androidIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/androidIcon.png
--------------------------------------------------------------------------------
/ASSETS/badge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/badge.png
--------------------------------------------------------------------------------
/ASSETS/badgeButton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/badgeButton.gif
--------------------------------------------------------------------------------
/ASSETS/breadcrumb.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/breadcrumb.gif
--------------------------------------------------------------------------------
/ASSETS/buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/buttons.png
--------------------------------------------------------------------------------
/ASSETS/carousel.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/carousel.gif
--------------------------------------------------------------------------------
/ASSETS/checkboxes.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/checkboxes.gif
--------------------------------------------------------------------------------
/ASSETS/closebutton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/closebutton.gif
--------------------------------------------------------------------------------
/ASSETS/collapse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/collapse.gif
--------------------------------------------------------------------------------
/ASSETS/colorpicker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/colorpicker.gif
--------------------------------------------------------------------------------
/ASSETS/custombuttons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/custombuttons.gif
--------------------------------------------------------------------------------
/ASSETS/darkBackgroundDropdown.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/darkBackgroundDropdown.gif
--------------------------------------------------------------------------------
/ASSETS/disabledDropdown.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/disabledDropdown.gif
--------------------------------------------------------------------------------
/ASSETS/dropdowns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/dropdowns.png
--------------------------------------------------------------------------------
/ASSETS/filepicker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/filepicker.gif
--------------------------------------------------------------------------------
/ASSETS/iconbutton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/iconbutton.gif
--------------------------------------------------------------------------------
/ASSETS/inputs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/inputs.gif
--------------------------------------------------------------------------------
/ASSETS/loadingButton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/loadingButton.gif
--------------------------------------------------------------------------------
/ASSETS/loadingButtonText.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/loadingButtonText.gif
--------------------------------------------------------------------------------
/ASSETS/logo.svg:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/ASSETS/modal.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/modal.gif
--------------------------------------------------------------------------------
/ASSETS/navbar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/navbar.gif
--------------------------------------------------------------------------------
/ASSETS/navbar2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/navbar2.gif
--------------------------------------------------------------------------------
/ASSETS/offcanvas.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/offcanvas.gif
--------------------------------------------------------------------------------
/ASSETS/pagination.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/pagination.gif
--------------------------------------------------------------------------------
/ASSETS/placeholderDropdown.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/placeholderDropdown.gif
--------------------------------------------------------------------------------
/ASSETS/progress.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/progress.gif
--------------------------------------------------------------------------------
/ASSETS/radiobuttons.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/radiobuttons.gif
--------------------------------------------------------------------------------
/ASSETS/range.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/range.gif
--------------------------------------------------------------------------------
/ASSETS/select.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/select.gif
--------------------------------------------------------------------------------
/ASSETS/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/spinner.gif
--------------------------------------------------------------------------------
/ASSETS/switches.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/switches.gif
--------------------------------------------------------------------------------
/ASSETS/textarea.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/textarea.gif
--------------------------------------------------------------------------------
/ASSETS/toast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/toast.gif
--------------------------------------------------------------------------------
/ASSETS/tooltip.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/ASSETS/tooltip.gif
--------------------------------------------------------------------------------
/bootstrap/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.varabyte.kobweb.gradle.core.util.importCss
2 | import com.varabyte.kobweb.gradle.library.util.configAsKobwebLibrary
3 | import kotlinx.html.script
4 | import kotlinx.html.style
5 |
6 | plugins {
7 | alias(libs.plugins.kotlin.multiplatform)
8 | alias(libs.plugins.kobweb.library)
9 | alias(libs.plugins.kotlin.compose)
10 | `maven-publish`
11 | }
12 |
13 | group = "com.stevdza.san.bootstrap"
14 | version = "0.1.6"
15 |
16 | kotlin {
17 | configAsKobwebLibrary(includeServer = false)
18 |
19 | sourceSets {
20 | commonMain.dependencies {
21 | implementation(libs.compose.runtime)
22 | }
23 |
24 | jsMain.dependencies {
25 | implementation(libs.compose.html.core)
26 | implementation(libs.kobweb.core)
27 | implementation(libs.kobweb.compose)
28 | implementation(libs.kobweb.silk.core)
29 | implementation(npm("bootstrap", "5.3.5"))
30 | }
31 | }
32 | }
33 |
34 | kobweb {
35 | library {
36 | index {
37 | head.add {
38 | script {
39 | src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js"
40 | }
41 | style {
42 | importCss(
43 | url = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css",
44 | layerName = "kotlin-bootstrap"
45 | )
46 | }
47 | style {
48 | importCss(
49 | url = "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css",
50 | layerName = "kotlin-bootstrap"
51 | )
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
58 | publishing {
59 | publications {
60 | register("mavenJsLibrary", MavenPublication::class) {
61 | from(components["kotlin"])
62 | groupId = "com.github.stevdza-san"
63 | artifactId = "KotlinBootstrap"
64 | version = version
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/BootstrapApp.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs
2 |
3 | import com.varabyte.kobweb.silk.init.InitSilk
4 | import com.varabyte.kobweb.silk.init.InitSilkContext
5 | import com.varabyte.kobweb.silk.style.layer.SilkLayer
6 | import com.varabyte.kobweb.silk.style.layer.add
7 |
8 | @InitSilk
9 | fun initBuildScriptLayers(ctx: InitSilkContext) {
10 | // Layer(s) referenced in build.gradle.kts
11 | ctx.stylesheet.cssLayers.add(
12 | "kotlin-bootstrap",
13 | after = SilkLayer.BASE
14 | )
15 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSAccordion.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.AccordionItem
6 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
9 | import com.varabyte.kobweb.compose.ui.modifiers.id
10 | import com.varabyte.kobweb.compose.ui.thenIf
11 | import com.varabyte.kobweb.compose.ui.toAttrs
12 | import org.jetbrains.compose.web.dom.Button
13 | import org.jetbrains.compose.web.dom.Div
14 | import org.jetbrains.compose.web.dom.H2
15 |
16 | /**
17 | * UI element that allows you to create collapsible and expandable sections of content.
18 | * It is often used to display information in a compact and organized manner,
19 | * especially when you have a lot of content to present in a limited space.
20 | * @param id A unique identifier for the parent.
21 | * @param items One or multiple [AccordionItem]'s that represents the content that
22 | * you want to make expandable.
23 | * @param flush If set to true, it will remove some borders and rounded corners
24 | * to render accordions edge-to-edge with their parent container.
25 | * @param alwaysOpen If set to true, it will make accordion items stay open
26 | * when another item is opened.
27 | * */
28 | @Composable
29 | fun BSAccordion(
30 | modifier: Modifier = Modifier,
31 | id: String? = null,
32 | items: List,
33 | flush: Boolean = false,
34 | alwaysOpen: Boolean = false
35 | ) {
36 | val randomId = remember {
37 | id ?: UniqueIdGenerator.generateUniqueId("accordion")
38 | }
39 | Div(
40 | attrs = modifier
41 | .id(randomId)
42 | .classNames("accordion")
43 | .thenIf(
44 | condition = flush,
45 | other = Modifier.classNames("accordion-flush")
46 | )
47 | .toAttrs()
48 | ) {
49 | items.forEachIndexed { index, accordionItem ->
50 | val newRandomId = UniqueIdGenerator.generateUniqueId("$index-accordion")
51 | Div(attrs = Modifier.classNames("accordion-item").toAttrs()) {
52 | H2(
53 | attrs = Modifier
54 | .id("header-${randomId}")
55 | .classNames("accordion-header")
56 | .toAttrs()
57 | ) {
58 | Button(attrs = Modifier
59 | .classNames("accordion-button")
60 | .thenIf(
61 | condition = !accordionItem.defaultOpened,
62 | other = Modifier.classNames("collapsed")
63 | )
64 | .toAttrs {
65 | attr("type", "button")
66 | attr("data-bs-toggle", "collapse")
67 | attr("data-bs-target", "#collapse-${newRandomId}")
68 | attr("aria-expanded", "true")
69 | attr("aria-controls", "collapse-${newRandomId}")
70 | }
71 | ) {
72 | SpanText(text = accordionItem.title)
73 | }
74 | }
75 | Div(
76 | attrs = Modifier
77 | .id("collapse-${newRandomId}")
78 | .classNames("accordion-collapse", "collapse")
79 | .thenIf(
80 | condition = accordionItem.defaultOpened,
81 | other = Modifier.classNames("show")
82 | )
83 | .toAttrs {
84 | attr("aria-labelledby", "header-${randomId}")
85 | if (!alwaysOpen) attr("data-bs-parent", "#${randomId}")
86 | }
87 | ) {
88 | Div(attrs = Modifier.classNames("accordion-body").toAttrs()) {
89 | accordionItem.content()
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSAlert.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.AlertIcon
5 | import com.stevdza.san.kotlinbs.models.AlertStyle
6 | import com.varabyte.kobweb.compose.css.FontWeight
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.attrsModifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
10 | import com.varabyte.kobweb.compose.ui.modifiers.fontWeight
11 | import com.varabyte.kobweb.compose.ui.modifiers.height
12 | import com.varabyte.kobweb.compose.ui.modifiers.width
13 | import com.varabyte.kobweb.compose.ui.thenIf
14 | import com.varabyte.kobweb.compose.ui.toAttrs
15 | import org.jetbrains.compose.web.css.px
16 | import org.jetbrains.compose.web.dom.A
17 | import org.jetbrains.compose.web.dom.Button
18 | import org.jetbrains.compose.web.dom.Div
19 | import org.jetbrains.compose.web.dom.Text
20 |
21 | /**
22 | * UI element that provides a way to display important messages, notifications,
23 | * or alerts to users. It is commonly used to convey information, warnings, errors,
24 | * or success messages in a visually prominent and attention-grabbing manner.
25 | * @param message A simple text that will be displayed inside an Alert dialog.
26 | * @param icon An [AlertIcon] that is used to give a specific context to the message.
27 | * @param dismissible Parameter that indicates whether an Alert can be dismissed or not.
28 | * @param style [AlertStyle] can change the look of the Alert itself.
29 | * */
30 | @Composable
31 | fun BSAlert(
32 | modifier: Modifier = Modifier,
33 | message: String,
34 | icon: AlertIcon? = null,
35 | dismissible: Boolean = false,
36 | style: AlertStyle = AlertStyle.Primary
37 | ) {
38 | Div(attrs = modifier
39 | .classNames(
40 | *style.classes.toTypedArray(),
41 | "d-flex",
42 | "align-items-center"
43 | )
44 | .thenIf(
45 | condition = dismissible,
46 | other = Modifier.classNames("alert-dismissible", "fade", "show")
47 | )
48 | .toAttrs {
49 | attr("role", "alert")
50 | }
51 | ) {
52 | if (icon != null) {
53 | Svg(attrs = Modifier
54 | .width(24.px)
55 | .height(24.px)
56 | .classNames(
57 | "bi",
58 | icon.classes,
59 | "flex-shrink-0",
60 | "me-2"
61 | )
62 | .toAttrs {
63 | attr("fill", "currentColor")
64 | attr("viewBox", "0 0 16 16")
65 | attr("role", "img")
66 | attr("aria-label", icon.label)
67 | }
68 | ) {
69 | Path(attrs = Modifier.toAttrs {
70 | attr("d", icon.path)
71 | })
72 | }
73 | }
74 | Div {
75 | Text(value = message)
76 | }
77 | if (dismissible) {
78 | BSCloseButton(
79 | modifier = Modifier
80 | .attrsModifier {
81 | attr("data-bs-dismiss", "alert")
82 | }
83 | )
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * UI element that provides a way to display important messages, notifications,
90 | * or alerts to users. It is commonly used to convey information, warnings, errors,
91 | * or success messages in a visually prominent and attention-grabbing manner.
92 | * @param message A simple text that will be displayed inside an Alert dialog.
93 | * @param icon An [AlertIcon] that is used to give a specific context to the message.
94 | * @param dismissible Parameter that indicates whether an Alert can be dismissed or not.
95 | * @param style [AlertStyle] can change the look of the Alert itself.
96 | * @param alertLink This parameter allows you to add and mark a text from the message parameter
97 | * as a link [A]. The first string in the [Pair] represents the actual text from the message
98 | * that you want to link. While the second string in the [Pair] represents href/link which
99 | * you want to open when a user clicks on it.
100 | * */
101 | @Composable
102 | fun BSAlert(
103 | modifier: Modifier = Modifier,
104 | message: String,
105 | icon: AlertIcon? = null,
106 | dismissible: Boolean = false,
107 | alertLink: Pair? = null,
108 | style: AlertStyle = AlertStyle.Primary
109 | ) {
110 | Div(attrs = modifier
111 | .classNames(*style.classes.toTypedArray())
112 | .thenIf(
113 | condition = dismissible,
114 | other = Modifier.classNames("alert-dismissible", "fade", "show")
115 | )
116 | .toAttrs {
117 | attr("role", "alert")
118 | }
119 | ) {
120 | if (icon != null) {
121 | Svg(attrs = Modifier
122 | .width(24.px)
123 | .height(24.px)
124 | .classNames(
125 | "bi",
126 | icon.classes,
127 | "flex-shrink-0",
128 | "me-2"
129 | )
130 | .toAttrs {
131 | attr("fill", "currentColor")
132 | attr("viewBox", "0 0 16 16")
133 | attr("role", "img")
134 | attr("aria-label", icon.label)
135 | }
136 | ) {
137 | Path(attrs = Modifier.toAttrs {
138 | attr("d", icon.path)
139 | })
140 | }
141 | }
142 | if (alertLink == null) {
143 | Text(value = message)
144 | } else {
145 | val messageParts = message.split(alertLink.first, ignoreCase = true)
146 | SpanText(text = messageParts.first())
147 | A(
148 | attrs = Modifier.classNames("alert-link").toAttrs(),
149 | href = alertLink.second
150 | ) {
151 | SpanText(text = alertLink.first)
152 | }
153 | SpanText(text = messageParts.last())
154 | }
155 | if (dismissible) {
156 | Button(
157 | attrs = Modifier
158 | .classNames("btn-close")
159 | .toAttrs {
160 | attr("type", "button")
161 | attr("data-bs-dismiss", "alert")
162 | attr("aria-label", "Close")
163 | }
164 | )
165 | }
166 | }
167 | }
168 |
169 | /**
170 | * UI element that provides a way to display important messages, notifications,
171 | * or alerts to users. It is commonly used to convey information, warnings, errors,
172 | * or success messages in a visually prominent and attention-grabbing manner.
173 | * @param message A simple text that will be displayed inside an Alert dialog.
174 | * @param icon An [AlertIcon] that is used to give a specific context to the message.
175 | * @param dismissible Parameter that indicates whether an Alert can be dismissed or not.
176 | * @param bold Allows you to mark a specific text from a message parameter,
177 | * as a Bold.
178 | * @param style [AlertStyle] can change the look of the Alert itself.
179 | * */
180 | @Composable
181 | fun BSAlert(
182 | modifier: Modifier = Modifier,
183 | message: String,
184 | icon: AlertIcon? = null,
185 | dismissible: Boolean = false,
186 | bold: String? = null,
187 | style: AlertStyle = AlertStyle.Primary
188 | ) {
189 | Div(attrs = modifier
190 | .classNames(*style.classes.toTypedArray())
191 | .thenIf(
192 | condition = dismissible,
193 | other = Modifier.classNames("alert-dismissible", "fade", "show")
194 | )
195 | .toAttrs {
196 | attr("role", "alert")
197 | }
198 | ) {
199 | if (icon != null) {
200 | Svg(attrs = Modifier
201 | .width(24.px)
202 | .height(24.px)
203 | .classNames(
204 | "bi",
205 | icon.classes,
206 | "flex-shrink-0",
207 | "me-2"
208 | )
209 | .toAttrs {
210 | attr("fill", "currentColor")
211 | attr("viewBox", "0 0 16 16")
212 | attr("role", "img")
213 | attr("aria-label", icon.label)
214 | }
215 | ) {
216 | Path(attrs = Modifier.toAttrs {
217 | attr("d", icon.path)
218 | })
219 | }
220 | }
221 | if (bold == null) {
222 | Text(value = message)
223 | } else {
224 | val messageParts = message.split(bold, ignoreCase = true)
225 | SpanText(text = messageParts.first())
226 | SpanText(
227 | modifier = Modifier.fontWeight(FontWeight.Bold),
228 | text = bold
229 | )
230 | SpanText(text = messageParts.last())
231 | }
232 | if (dismissible) {
233 | Button(
234 | attrs = Modifier
235 | .classNames("btn-close")
236 | .toAttrs {
237 | attr("type", "button")
238 | attr("data-bs-dismiss", "alert")
239 | attr("aria-label", "Close")
240 | }
241 | )
242 | }
243 | }
244 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSBadge.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.BackgroundStyle
5 | import com.stevdza.san.kotlinbs.models.BadgeVariant
6 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
7 | import com.varabyte.kobweb.compose.css.FontWeight
8 | import com.varabyte.kobweb.compose.ui.Modifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.*
10 | import com.varabyte.kobweb.compose.ui.thenIf
11 | import com.varabyte.kobweb.compose.ui.toAttrs
12 | import org.jetbrains.compose.web.css.px
13 | import org.jetbrains.compose.web.dom.Span
14 |
15 | /**
16 | * Badge is a small element used to highlight or display additional information related
17 | * to content. Badges are typically used to indicate the count of items,
18 | * notifications, statuses, or any other relevant information.
19 | * @param text Text that appears on top of the Badge.
20 | * @param fontFamily Font family of the text.
21 | * @param fontSize Size of the text.
22 | * @param fontWeight Font weight of the text.
23 | * @param style Different style that you can apply on this element.
24 | * @param variant Different variants of this component.
25 | * */
26 | @Composable
27 | fun BSBadge(
28 | modifier: Modifier = Modifier,
29 | text: String,
30 | fontFamily: String? = null,
31 | fontSize: CSSLengthOrPercentageNumericValue? = null,
32 | fontWeight: FontWeight? = null,
33 | style: BackgroundStyle = BackgroundStyle.Primary,
34 | variant: BadgeVariant = BadgeVariant.Regular
35 | ) {
36 | Span(
37 | attrs = modifier
38 | .classNames(style.value)
39 | .thenIf(
40 | condition = variant != BadgeVariant.Regular && variant != BadgeVariant.Straight,
41 | other = variant.classes?.let { Modifier.classNames(*it.toTypedArray()) } ?: Modifier
42 | )
43 | .thenIf(
44 | condition = variant != BadgeVariant.Empty,
45 | other = Modifier.classNames("badge")
46 | )
47 | .thenIf(
48 | condition = variant == BadgeVariant.Straight,
49 | other = Modifier.borderRadius(0.px)
50 | )
51 | .toAttrs()
52 | ) {
53 | if (variant != BadgeVariant.Empty) {
54 | SpanText(
55 | modifier = Modifier
56 | .thenIf(
57 | condition = fontFamily != null,
58 | other = fontFamily?.let { Modifier.fontFamily(it) } ?: Modifier
59 | )
60 | .thenIf(
61 | condition = fontSize != null,
62 | other = fontSize?.let { Modifier.fontSize(it) } ?: Modifier
63 | )
64 | .thenIf(
65 | condition = fontWeight != null,
66 | other = fontWeight?.let { Modifier.fontWeight(it) } ?: Modifier
67 | ),
68 | text = text
69 | )
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSBreadcrumb.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.BreadcrumbItem
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
7 | import com.varabyte.kobweb.compose.ui.thenIf
8 | import com.varabyte.kobweb.compose.ui.toAttrs
9 | import org.jetbrains.compose.web.dom.*
10 |
11 | /**
12 | * This component is used to create a navigation trail that indicates the user's current
13 | * location within a website's hierarchy. It consists of a series of links or text elements
14 | * separated by a separator character (typically a forward slash ("/")).
15 | *
16 | * The purpose of the breadcrumb component is to provide users with a way to understand
17 | * their current position within the website's structure and easily navigate back
18 | * to previous pages or sections.
19 | * @param items One or multiple [BreadcrumbItem]'s that allows you to specify a text
20 | * along with the link(href) which is used for navigation.
21 | * @param currentItem An item that is marked as selected.
22 | * @param divider A symbol which is used to separate [BreadcrumbItem]'s.
23 | * */
24 | @Composable
25 | fun BSBreadcrumb(
26 | modifier: Modifier = Modifier,
27 | items: List,
28 | currentItem: String = items.first().text,
29 | divider: String = "/"
30 | ) {
31 | Nav(attrs = modifier
32 | .toAttrs {
33 | attr("aria-label", "breadcrumb")
34 | attr("style", "--bs-breadcrumb-divider: '$divider';")
35 | }
36 | ) {
37 | Ol(
38 | attrs = Modifier
39 | .classNames("breadcrumb")
40 | .toAttrs()
41 | ) {
42 | items.forEach {
43 | Li(
44 | attrs = Modifier
45 | .classNames("breadcrumb-item")
46 | .thenIf(
47 | condition = currentItem == it.text,
48 | other = Modifier.classNames("active")
49 | )
50 | .toAttrs {
51 | if (currentItem == it.text) attr("aria-current", "page")
52 | }
53 | ) {
54 | if (currentItem != it.text) {
55 | A(href = it.href) {
56 | Text(value = it.text)
57 | }
58 | } else {
59 | Text(value = it.text)
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSButton.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.BSBorderRadius
6 | import com.stevdza.san.kotlinbs.models.button.ButtonBadge
7 | import com.stevdza.san.kotlinbs.models.button.ButtonCustomization
8 | import com.stevdza.san.kotlinbs.models.button.ButtonProperty
9 | import com.stevdza.san.kotlinbs.models.button.ButtonSize
10 | import com.stevdza.san.kotlinbs.models.button.ButtonType
11 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
12 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
13 | import com.varabyte.kobweb.compose.ui.Modifier
14 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
15 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
16 | import com.varabyte.kobweb.compose.ui.modifiers.id
17 | import com.varabyte.kobweb.compose.ui.modifiers.margin
18 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
19 | import com.varabyte.kobweb.compose.ui.styleModifier
20 | import com.varabyte.kobweb.compose.ui.thenIf
21 | import com.varabyte.kobweb.compose.ui.toAttrs
22 | import org.jetbrains.compose.web.attributes.disabled
23 | import org.jetbrains.compose.web.css.px
24 | import org.jetbrains.compose.web.dom.Button
25 | import org.jetbrains.compose.web.dom.Span
26 | import org.jetbrains.compose.web.dom.Text
27 |
28 | /**
29 | * Buttons are used to trigger actions or events when clicked or tapped.
30 | * They are an essential part of any user interface as they provide a way for users
31 | * to interact with and control the application or website.
32 | * @param id A unique identifier of the button.
33 | * @param text Text that will appear on top of the button.
34 | * @param variant This one is used to stylize your button with a different color.
35 | * @param type Determine the behavior of the button when clicked or activated.
36 | * @param size The overall size of the button.
37 | * @param borderRadius The radius level of the button corners.
38 | * @param customization This is the place where you customize the button style all by
39 | * yourself. You will need to add styles of many properties, to handle different states.
40 | * If you have multiple buttons that will use this same custom style, it's usually
41 | * a good idea to create a top level variable that will hold the same [ButtonCustomization],
42 | * properties, so that it can be reused multiple times.
43 | * NOTE: When this parameter is specified, the [size], [variant] and [borderRadius]
44 | * parameters wil not work, because you're in full charge of the button customization.
45 | * @param disabled Whether a button is clickable or not.
46 | * @param loading When set to true, a loading indicator will appear. It's often used
47 | * with the other [loadingText] parameter. When loading is equal to 'true' the [onClick]
48 | * lambda is disabled.
49 | * @param loadingText Here you specify the text that will be shown, when the loading
50 | * state of the button changes to true.
51 | * @param badge Small badge or label, providing additional information or indicating
52 | * a specific status or count associated with the button.
53 | * @param icon Use 'BSIcons' object to choose one of many bootstrap icons inside the button.
54 | * @param onClick Lambda which is triggered everytime a user clicks on a button.
55 | * */
56 | @Composable
57 | fun BSButton(
58 | modifier: Modifier = Modifier,
59 | id: String? = null,
60 | text: String,
61 | variant: ButtonVariant = ButtonVariant.Primary,
62 | type: ButtonType = ButtonType.Button,
63 | size: ButtonSize = ButtonSize.Default,
64 | borderRadius: BSBorderRadius? = null,
65 | customization: ButtonCustomization? = null,
66 | disabled: Boolean = false,
67 | loading: Boolean = false,
68 | loadingText: String? = null,
69 | badge: ButtonBadge? = null,
70 | icon: String? = null,
71 | onClick: () -> Unit
72 | ) {
73 | val randomId = remember {
74 | id ?: UniqueIdGenerator.generateUniqueId("button")
75 | }
76 | Button(attrs = Modifier
77 | .then(modifier)
78 | .id(randomId)
79 | .onClick { onClick() }
80 | .thenIf(
81 | condition = customization == null && borderRadius != null,
82 | other = borderRadius?.let {
83 | Modifier.borderRadius(
84 | topLeft = it.topLeft,
85 | topRight = borderRadius.topRight,
86 | bottomRight = borderRadius.bottomRight,
87 | bottomLeft = borderRadius.bottomLeft
88 | )
89 | } ?: Modifier
90 | )
91 | .thenIf(
92 | condition = customization == null,
93 | other = Modifier.classNames(
94 | *variant.classes.toTypedArray(),
95 | size.value
96 | )
97 | )
98 | .thenIf(
99 | condition = customization != null,
100 | other = Modifier.classNames(ButtonSize.Default.value)
101 | )
102 | .classNames(
103 | "d-flex",
104 | "align-items-center",
105 | )
106 | .thenIf(
107 | condition = badge != null,
108 | other = Modifier.classNames("position-relative")
109 | )
110 | .styleModifier {
111 | if (customization != null) {
112 | customization.color?.let { property(ButtonProperty.COLOR, it) }
113 | customization.backgroundColor?.let { property(ButtonProperty.BACKGROUND_COLOR, it) }
114 | customization.borderColor?.let { property(ButtonProperty.BORDER_COLOR, it) }
115 | customization.hoverColor?.let { property(ButtonProperty.HOVER_COLOR, it) }
116 | customization.hoverBackgroundColor?.let { property(ButtonProperty.HOVER_BACKGROUND_COLOR, it) }
117 | customization.hoverBorderColor?.let { property(ButtonProperty.HOVER_BORDER_COLOR, it) }
118 | customization.activeColor?.let { property(ButtonProperty.ACTIVE_COLOR, it) }
119 | customization.activeBackgroundColor?.let { property(ButtonProperty.ACTIVE_BACKGROUND_COLOR, it) }
120 | customization.activeBorderColor?.let { property(ButtonProperty.ACTIVE_BORDER_COLOR, it) }
121 | customization.disabledColor?.let { property(ButtonProperty.DISABLED_COLOR, it) }
122 | customization.disabledBackgroundColor?.let {
123 | property(
124 | ButtonProperty.DISABLED_BACKGROUND_COLOR,
125 | it
126 | )
127 | }
128 | customization.disabledBorderColor?.let { property(ButtonProperty.DISABLED_BORDER_COLOR, it) }
129 |
130 | property(ButtonProperty.BORDER_RADIUS, customization.borderRadius.value)
131 | property(ButtonProperty.HORIZONTAL_PADDING, customization.horizontalPadding)
132 | property(ButtonProperty.VERTICAL_PADDING, customization.verticalPadding)
133 |
134 | customization.fontFamily?.let { property(ButtonProperty.FONT_FAMILY, it) }
135 | property(ButtonProperty.FONT_SIZE, customization.fontSize)
136 | property(ButtonProperty.FONT_WEIGHT, customization.fontWeight.toString())
137 | property(ButtonProperty.LINE_HEIGHT, customization.lineHeight.toString())
138 |
139 | property(ButtonProperty.GRADIENT, customization.gradient.toString())
140 | }
141 | }
142 | .toAttrs {
143 | attr("type", type.value)
144 | if (disabled || loading) disabled()
145 | }
146 | ) {
147 | if (badge != null) {
148 | BSBadge(
149 | modifier = badge.modifier
150 | .classNames(
151 | "position-absolute",
152 | "top-0",
153 | "start-100",
154 | "translate-middle"
155 | ),
156 | text = badge.text,
157 | style = badge.style,
158 | variant = badge.variant,
159 | )
160 | }
161 | if (loading) {
162 | if (loadingText != null) {
163 | Span(attrs = Modifier
164 | .margin(right = 6.px)
165 | .classNames("spinner-border", "spinner-border-sm")
166 | .toAttrs {
167 | attr("role", "status")
168 | attr("aria-hidden", "true")
169 | }
170 | )
171 | Text(value = loadingText)
172 | } else {
173 | Span(attrs = Modifier
174 | .classNames("spinner-border", "spinner-border-sm")
175 | .toAttrs {
176 | attr("role", "status")
177 | attr("aria-hidden", "true")
178 | }
179 | )
180 | Span(
181 | attrs = Modifier
182 | .classNames("visually-hidden")
183 | .toAttrs()
184 | ) {
185 | Text(value = "Loading...")
186 | }
187 | }
188 | } else {
189 | if (icon != null) {
190 | BSIcon(
191 | modifier = Modifier.margin(right = 8.px),
192 | icon = icon
193 | )
194 | SpanText(text = text)
195 | } else {
196 | SpanText(text = text)
197 | }
198 | }
199 | }
200 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSCarousel.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.CarouselItem
6 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
7 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
8 | import com.varabyte.kobweb.compose.css.ObjectFit
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.attrsModifier
11 | import com.varabyte.kobweb.compose.ui.modifiers.*
12 | import com.varabyte.kobweb.compose.ui.thenIf
13 | import com.varabyte.kobweb.compose.ui.toAttrs
14 | import org.jetbrains.compose.web.dom.*
15 |
16 | /**
17 | * An interactive UI element that allows you to showcase a collection of images
18 | * in a sliding or fading manner. It is commonly used to create visually appealing and
19 | * dynamic slideshows or image galleries on web pages.
20 | *
21 | * The carousel component in Bootstrap provides a responsive and customizable carousel
22 | * container that can hold multiple slides. Each slide typically consists of an image
23 | * along with optional captions or additional content. The carousel automatically
24 | * transitions between slides, creating a smooth and visually engaging experience
25 | * for the user.
26 | *
27 | * @param id A unique identifier of the component.
28 | * @param items Single or multiple [CarouselItem]'s that will be shown into the carousel.
29 | * Each item contains a mandatory image parameter, and optional title and body.
30 | * @param width Width of the container.
31 | * @param height Height of the container.
32 | * @param showControls Whether to display controls or not.
33 | * @param showIndicators Whether to display indicators or not.
34 | * @param crossFade Whether to replace a sliding transition animation with a cross-fade.
35 | * @param dark If the value is true, controls and indicators will have dark colors.
36 | * Otherwise, their color will be white.
37 | * @param objectFit How an image should be resized and positioned within its container.
38 | * */
39 | @Composable
40 | fun BSCarousel(
41 | modifier: Modifier = Modifier,
42 | id: String? = null,
43 | items: List,
44 | width: CSSLengthOrPercentageNumericValue,
45 | height: CSSLengthOrPercentageNumericValue,
46 | showControls: Boolean = true,
47 | showIndicators: Boolean = true,
48 | crossFade: Boolean = false,
49 | dark: Boolean = false,
50 | objectFit: ObjectFit = ObjectFit.Fill,
51 | ) {
52 | val randomId = remember {
53 | id ?: UniqueIdGenerator.generateUniqueId("carousel")
54 | }
55 | Div(attrs = modifier
56 | .id(randomId)
57 | .classNames("carousel", "slide")
58 | .width(width)
59 | .height(height)
60 | .thenIf(
61 | condition = crossFade,
62 | other = Modifier.classNames("carousel-fade")
63 | )
64 | .thenIf(
65 | condition = dark,
66 | other = Modifier.classNames("carousel-dark")
67 | )
68 | .toAttrs {
69 | attr("data-bs-ride", "carousel")
70 | }
71 | ) {
72 | if (showIndicators) {
73 | Div(
74 | attrs = Modifier
75 | .classNames("carousel-indicators")
76 | .toAttrs()
77 | ) {
78 | items.forEachIndexed { index, image ->
79 | Button(attrs = Modifier
80 | .thenIf(
81 | condition = image == items.first(),
82 | other = Modifier.classNames("active")
83 | )
84 | .thenIf(
85 | condition = image == items.first(),
86 | other = Modifier.attrsModifier {
87 | attr("aria-current", "true")
88 | }
89 | )
90 | .toAttrs {
91 | attr("data-bs-target", "#carouselExampleIndicators")
92 | attr("data-bs-slide-to", "$index")
93 | attr("data-bs-slide-to", "$index")
94 | attr("aria-label", "Slide $index")
95 | }
96 | )
97 | }
98 | }
99 | }
100 | Div(
101 | attrs = Modifier
102 | .fillMaxSize()
103 | .classNames("carousel-inner")
104 | .toAttrs()
105 | ) {
106 | items.forEach {
107 | Div(
108 | attrs = Modifier
109 | .fillMaxSize()
110 | .classNames("carousel-item")
111 | .thenIf(
112 | condition = it == items.first(),
113 | other = Modifier.classNames("active")
114 | )
115 | .toAttrs()
116 | ) {
117 | Img(
118 | src = it.image,
119 | alt = "Carousel Image",
120 | attrs = Modifier
121 | .fillMaxSize()
122 | .objectFit(objectFit)
123 | .classNames("d-block", "w-100")
124 | .toAttrs(),
125 | )
126 | if (it.title != null || it.body != null) {
127 | Div(
128 | attrs = Modifier
129 | .classNames(
130 | "carousel-caption",
131 | "d-md-block",
132 | "d-none"
133 | )
134 | .toAttrs()
135 | ) {
136 | H5 {
137 | it.title?.let { title -> Text(value = title) }
138 | }
139 | P {
140 | it.body?.let { body -> Text(value = body) }
141 | }
142 | }
143 | }
144 | }
145 | }
146 | if (showControls) {
147 | Button(
148 | attrs = Modifier
149 | .classNames("carousel-control-prev")
150 | .toAttrs {
151 | attr("type", "button")
152 | attr("data-bs-target", "#$randomId")
153 | attr("data-bs-slide", "prev")
154 | }
155 | ) {
156 | Span(
157 | attrs = Modifier
158 | .classNames("carousel-control-prev-icon")
159 | .toAttrs {
160 | attr("aria-hidden", "true")
161 | }
162 | )
163 | Span(
164 | attrs = Modifier
165 | .classNames("visually-hidden")
166 | .toAttrs()
167 | ) {
168 | Text(value = "Previous")
169 | }
170 | }
171 | Button(
172 | attrs = Modifier
173 | .classNames("carousel-control-next")
174 | .toAttrs {
175 | attr("type", "button")
176 | attr("data-bs-target", "#$randomId")
177 | attr("data-bs-slide", "next")
178 | }
179 | ) {
180 | Span(
181 | attrs = Modifier
182 | .classNames("carousel-control-next-icon")
183 | .toAttrs {
184 | attr("aria-hidden", "true")
185 | }
186 | )
187 | Span(
188 | attrs = Modifier
189 | .classNames("visually-hidden")
190 | .toAttrs()
191 | ) {
192 | Text(value = "Next")
193 | }
194 | }
195 | }
196 | }
197 | }
198 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSCloseButton.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.varabyte.kobweb.compose.ui.Modifier
5 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
6 | import com.varabyte.kobweb.compose.ui.thenIf
7 | import com.varabyte.kobweb.compose.ui.toAttrs
8 | import org.jetbrains.compose.web.attributes.disabled
9 | import org.jetbrains.compose.web.dom.Button
10 |
11 | /**
12 | * Pre-designed UI element that provides a visually identifiable button for closing
13 | * or dismissing a content block, alert, modal, or any other component that requires
14 | * user interaction to be closed.
15 | * @param disabled Whether this close button should be disabled.
16 | * @param dark Whether the color of this close button should be dark or not.
17 | * */
18 | @Composable
19 | fun BSCloseButton(
20 | modifier: Modifier = Modifier,
21 | disabled: Boolean = false,
22 | dark: Boolean = true
23 | ) {
24 | Button(attrs = modifier
25 | .classNames("btn-close")
26 | .thenIf(
27 | condition = !dark,
28 | other = Modifier.classNames("btn-close-white")
29 | )
30 | .toAttrs {
31 | attr("type", "button")
32 | attr("aria-label", "Close")
33 | if (disabled) disabled()
34 | }
35 | )
36 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSCollapse.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.varabyte.kobweb.compose.ui.Modifier
5 | import com.varabyte.kobweb.compose.ui.attrsModifier
6 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
7 | import com.varabyte.kobweb.compose.ui.modifiers.id
8 | import com.varabyte.kobweb.compose.ui.toAttrs
9 | import org.jetbrains.compose.web.dom.Div
10 |
11 | /**
12 | * Toggle the visibility of your content.
13 | * Given how CSS handles animations, you cannot use padding on a content that
14 | * is collapsing. Instead, use the class as an independent wrapping element.
15 | * This component comes with a [showCollapse] util function, that is used to
16 | * trigger/show this component.
17 | * @param id A unique identifier of the component.
18 | * @param content The content that you're trying to hide.
19 | * */
20 | @Composable
21 | fun BSCollapse(
22 | modifier: Modifier = Modifier,
23 | id: String,
24 | content: @Composable () -> Unit
25 | ) {
26 | Div(
27 | attrs = modifier
28 | .id(id)
29 | .classNames("collapse")
30 | .toAttrs()
31 | ) {
32 | content()
33 | }
34 | }
35 |
36 | /**
37 | * Util function which is used in a combination with [BSCollapse].
38 | * */
39 | fun Modifier.showCollapse(id: String): Modifier = attrsModifier {
40 | attr("data-bs-toggle", "collapse")
41 | attr("data-bs-target", "#$id")
42 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSDropdown.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.*
4 | import com.stevdza.san.kotlinbs.models.DropdownDirection
5 | import com.stevdza.san.kotlinbs.models.button.ButtonSize
6 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
7 | import com.varabyte.kobweb.compose.css.Cursor
8 | import com.varabyte.kobweb.compose.ui.Modifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
10 | import com.varabyte.kobweb.compose.ui.modifiers.cursor
11 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
12 | import com.varabyte.kobweb.compose.ui.thenIf
13 | import com.varabyte.kobweb.compose.ui.toAttrs
14 | import org.jetbrains.compose.web.dom.*
15 |
16 | /**
17 | * A versatile UI element that allows you to create a menu or a list of options that can be
18 | * toggled to appear or disappear when triggered by user interaction. It provides a dropdown
19 | * menu that can contain other interactive elements.
20 | * @param items List of one or multiple strings that should be displayed within the dropdown.
21 | * @param selectedItem Item which is marked as selected in the drop-down, by default that's
22 | * a first item from the [items] list.
23 | * @param disabledItems List of one or multiple string items that should be displayed as
24 | * disabled within the dropdown.
25 | * @param style The style of the dropdown button.
26 | * @param size The size of the dropdown button.
27 | * @param direction Direction in which the dropdown should appear after it's opened.
28 | * @param splitToggleButton Whether the dropdown arrow should be presented as a separate
29 | * button within the dropdown button.
30 | * @param darkBackground Whether to display a dark background on a dropdown menu, when opened.
31 | * @param onItemSelect Lambda which is triggered when an item from the dropdown is clicked.
32 | * */
33 | @Composable
34 | fun BSDropdown(
35 | modifier: Modifier = Modifier,
36 | items: List,
37 | selectedItem: String = items.first(),
38 | placeholder: String? = null,
39 | disabledItems: List? = null,
40 | style: ButtonVariant = ButtonVariant.Primary,
41 | size: ButtonSize = ButtonSize.Default,
42 | direction: DropdownDirection = DropdownDirection.Bottom,
43 | splitToggleButton: Boolean = false,
44 | darkBackground: Boolean = false,
45 | onItemSelect: (Int, String) -> Unit
46 | ) {
47 | var selectedItemInternal by remember(key1 = selectedItem) { mutableStateOf(selectedItem) }
48 | var isSelected by remember { mutableStateOf(false) }
49 | Div(
50 | attrs = modifier
51 | .classNames(if (splitToggleButton) "btn-group" else "dropdown")
52 | .thenIf(
53 | condition = direction != DropdownDirection.Bottom,
54 | other = Modifier.classNames(direction.value.toString())
55 | )
56 | .toAttrs()
57 | ) {
58 | if (splitToggleButton && direction == DropdownDirection.Left) {
59 | Button(
60 | attrs = Modifier
61 | .classNames(
62 | *style.classes.toTypedArray(),
63 | size.value,
64 | "dropdown-toggle",
65 | "dropdown-toggle-split"
66 | )
67 | .toAttrs {
68 | attr("type", "button")
69 | attr("data-bs-toggle", "dropdown")
70 | }
71 | ) {
72 | Span(attrs = Modifier.classNames("visually-hidden").toAttrs()) {
73 | Text(value = "Toggle Dropdown")
74 | }
75 | }
76 | }
77 | Button(
78 | attrs = Modifier
79 | .classNames(
80 | *style.classes.toTypedArray(),
81 | size.value
82 | )
83 | .thenIf(
84 | condition = !splitToggleButton,
85 | other = Modifier.classNames("dropdown-toggle")
86 | )
87 | .toAttrs {
88 | attr("type", "button")
89 | if (!splitToggleButton) attr("data-bs-toggle", "dropdown")
90 | }
91 | ) {
92 | SpanText(
93 | if (placeholder != null) {
94 | if (isSelected) selectedItemInternal else placeholder
95 | } else selectedItemInternal
96 | )
97 | }
98 | if (splitToggleButton && direction != DropdownDirection.Left) {
99 | Button(
100 | attrs = Modifier
101 | .classNames(
102 | *style.classes.toTypedArray(),
103 | size.value,
104 | "dropdown-toggle",
105 | "dropdown-toggle-split"
106 | )
107 | .toAttrs {
108 | attr("type", "button")
109 | attr("data-bs-toggle", "dropdown")
110 | }
111 | ) {
112 | Span(attrs = Modifier.classNames("visually-hidden").toAttrs()) {
113 | Text(value = "Toggle Dropdown")
114 | }
115 | }
116 | }
117 | Ul(
118 | attrs = Modifier
119 | .classNames("dropdown-menu")
120 | .thenIf(
121 | condition = darkBackground,
122 | other = Modifier.classNames("dropdown-menu-dark")
123 | )
124 | .toAttrs()
125 | ) {
126 | repeat(items.size) { index ->
127 | val isDisabled = disabledItems?.contains(items[index]) ?: false
128 | Li(
129 | attrs = Modifier
130 | .onClick {
131 | if (!isDisabled) {
132 | selectedItemInternal = items[index]
133 | onItemSelect(index, items[index])
134 | isSelected = true
135 | }
136 | }
137 | .cursor(if (isDisabled) Cursor.NotAllowed else Cursor.Pointer)
138 | .toAttrs()
139 | ) {
140 | A(
141 | attrs = Modifier
142 | .classNames("dropdown-item")
143 | .thenIf(
144 | condition = selectedItemInternal == items[index] && (placeholder == null || isSelected),
145 | other = Modifier.classNames("active")
146 | )
147 | .thenIf(
148 | condition = isDisabled,
149 | other = Modifier.classNames("disabled")
150 | )
151 | .toAttrs {
152 | if (isDisabled) {
153 | attr("tabindex", "-1")
154 | attr("aria-disabled", "true")
155 | }
156 | }
157 | ) {
158 | SpanText(text = items[index])
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSIcon.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.icons.BSIcons
6 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
7 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
8 | import com.varabyte.kobweb.compose.ui.Modifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
10 | import com.varabyte.kobweb.compose.ui.modifiers.color
11 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize
12 | import com.varabyte.kobweb.compose.ui.modifiers.id
13 | import com.varabyte.kobweb.compose.ui.thenIf
14 | import com.varabyte.kobweb.compose.ui.toAttrs
15 | import org.jetbrains.compose.web.css.CSSColorValue
16 | import org.jetbrains.compose.web.dom.I
17 |
18 | /**
19 | * A simple composable function used to represent an icon.
20 | * @param id A unique identifier of the button.
21 | * @param icon An object [BSIcons] which is used to specify an icon.
22 | * @param size The overall size of the icon, usually a default value is '1.cssRem'.
23 | * @param color The color of the icon.
24 | * */
25 | @Composable
26 | fun BSIcon(
27 | modifier: Modifier = Modifier,
28 | id: String? = null,
29 | icon: String = BSIcons.CHECK,
30 | size: CSSLengthOrPercentageNumericValue? = null,
31 | color: CSSColorValue? = null
32 | ) {
33 | val randomId = remember {
34 | id ?: UniqueIdGenerator.generateUniqueId("icon")
35 | }
36 | I(
37 | attrs = modifier
38 | .id(randomId)
39 | .classNames(*icon.split(" ").toList().toTypedArray())
40 | .thenIf(
41 | condition = size != null,
42 | other = size?.let { Modifier.fontSize(it) } ?: Modifier
43 | )
44 | .thenIf(
45 | condition = color != null,
46 | other = color?.let { Modifier.color(it) } ?: Modifier
47 | )
48 | .toAttrs()
49 | )
50 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSIconButton.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.icons.BSIcons
6 | import com.stevdza.san.kotlinbs.models.BSBorderRadius
7 | import com.stevdza.san.kotlinbs.models.button.ButtonBadge
8 | import com.stevdza.san.kotlinbs.models.button.ButtonSize
9 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
10 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
11 | import com.varabyte.kobweb.compose.ui.Modifier
12 | import com.varabyte.kobweb.compose.ui.modifiers.borderRadius
13 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
14 | import com.varabyte.kobweb.compose.ui.modifiers.id
15 | import com.varabyte.kobweb.compose.ui.modifiers.onClick
16 | import com.varabyte.kobweb.compose.ui.thenIf
17 | import com.varabyte.kobweb.compose.ui.toAttrs
18 | import org.jetbrains.compose.web.attributes.disabled
19 | import org.jetbrains.compose.web.dom.Button
20 |
21 | /**
22 | * This component is used to display an icon within a button, without any text.
23 | * @param id A unique identifier of the button.
24 | * @param icon An object [BSIcons] which is used to specify an icon.
25 | * @param size The overall size of the button.
26 | * @param variant This one is used to stylize your button with a different color.
27 | * @param borderRadius The radius level of the button corners.
28 | * @param disabled Whether a button is clickable or not.
29 | * @param badge Small badge or label, providing additional information or indicating
30 | * a specific status or count associated with the button.
31 | * @param onClick Lambda which is triggered everytime a user clicks on a button.
32 | * */
33 | @Composable
34 | fun BSIconButton(
35 | modifier: Modifier = Modifier,
36 | id: String? = null,
37 | icon: String = BSIcons.CHECK,
38 | size: ButtonSize = ButtonSize.Default,
39 | variant: ButtonVariant = ButtonVariant.Primary,
40 | borderRadius: BSBorderRadius? = null,
41 | disabled: Boolean = false,
42 | badge: ButtonBadge? = null,
43 | onClick: () -> Unit
44 | ) {
45 | val randomId = remember {
46 | id ?: UniqueIdGenerator.generateUniqueId("iconButton")
47 | }
48 | Button(
49 | attrs = modifier
50 | .id(randomId)
51 | .classNames(*variant.classes.toTypedArray(), size.value)
52 | .thenIf(
53 | condition = borderRadius != null,
54 | other = borderRadius?.let {
55 | Modifier.borderRadius(
56 | topLeft = it.topLeft,
57 | topRight = it.topRight,
58 | bottomRight = it.bottomRight,
59 | bottomLeft = it.bottomLeft
60 | )
61 | } ?: Modifier
62 | )
63 | .thenIf(
64 | condition = badge != null,
65 | other = Modifier.classNames("position-relative")
66 | )
67 | .onClick { onClick() }
68 | .toAttrs {
69 | attr("type", "button")
70 | attr("tabindex", "-1")
71 | if (disabled) disabled()
72 | }
73 | ) {
74 | if (badge != null) {
75 | BSBadge(
76 | modifier = badge.modifier
77 | .classNames(
78 | "position-absolute",
79 | "top-0",
80 | "start-100",
81 | "translate-middle"
82 | ),
83 | text = badge.text,
84 | style = badge.style,
85 | variant = badge.variant,
86 | )
87 | }
88 | BSIcon(icon = icon)
89 | }
90 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSModal.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
5 | import com.stevdza.san.kotlinbs.models.ModalSize
6 | import com.varabyte.kobweb.compose.ui.Modifier
7 | import com.varabyte.kobweb.compose.ui.attrsModifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
9 | import com.varabyte.kobweb.compose.ui.modifiers.id
10 | import com.varabyte.kobweb.compose.ui.thenIf
11 | import com.varabyte.kobweb.compose.ui.toAttrs
12 | import org.jetbrains.compose.web.dom.Div
13 | import org.jetbrains.compose.web.dom.H2
14 | import org.jetbrains.compose.web.dom.Text
15 |
16 | /**
17 | * Powerful UI element used to display content, messages, or interactive forms in a popup
18 | * window that temporarily overlays the main content of a webpage. Modals are commonly used
19 | * to grab the user's attention and prompt them for an action, display additional information,
20 | * or confirm a choice.
21 | * This component comes with a [showModalOnClick] util function, that is used to trigger/show
22 | * this component. And [hideModalOnClick] that is used to dismiss the same component.
23 | * @param title Modal title.
24 | * @param body Modal body.
25 | * @param negativeButtonText Text of the negative button.
26 | * @param positiveButtonText Text of the positive button.
27 | * @param closableOutside Whether we can close a Modal when clicking somewhere outside.
28 | * @param centered Whether to center the content of the Modal.
29 | * @param size The size of the Modal itself.
30 | * @param onNegativeButtonClick Lambda which is triggered when a negative button is clicked.
31 | * @param onPositiveButtonClick Lambda which is triggered when a positive button is clicked.
32 | * */
33 | @Composable
34 | fun BSModal(
35 | modifier: Modifier = Modifier,
36 | id: String,
37 | title: String,
38 | body: @Composable () -> Unit,
39 | negativeButtonText: String = "Close",
40 | positiveButtonText: String = "Okay",
41 | closableOutside: Boolean = false,
42 | centered: Boolean = true,
43 | size: ModalSize = ModalSize.None,
44 | onNegativeButtonClick: () -> Unit,
45 | onPositiveButtonClick: () -> Unit,
46 | ) {
47 | Div(attrs = modifier
48 | .id(id)
49 | .classNames("modal", "fade")
50 | .thenIf(
51 | condition = !closableOutside,
52 | other = Modifier.attrsModifier {
53 | attr("data-bs-backdrop", "static")
54 | }
55 | )
56 | .toAttrs {
57 | attr("tabindex", "-1")
58 | }
59 | ) {
60 | Div(
61 | attrs = Modifier
62 | .classNames("modal-dialog")
63 | .thenIf(
64 | condition = size != ModalSize.None,
65 | other = Modifier.classNames(size.value)
66 | )
67 | .thenIf(
68 | condition = centered,
69 | other = Modifier.classNames("modal-dialog-centered")
70 | )
71 | .toAttrs()
72 | ) {
73 | Div(
74 | attrs = Modifier
75 | .classNames("modal-content")
76 | .toAttrs()
77 | ) {
78 | Div(
79 | attrs = Modifier
80 | .classNames("modal-header")
81 | .toAttrs()
82 | ) {
83 | H2(
84 | attrs = Modifier
85 | .classNames("modal-title")
86 | .toAttrs()
87 | ) {
88 | Text(value = title)
89 | }
90 | BSCloseButton(modifier = Modifier.attrsModifier {
91 | attr("data-bs-dismiss", "modal")
92 | })
93 | }
94 | Div(
95 | attrs = Modifier
96 | .classNames("modal-body")
97 | .toAttrs()
98 | ) {
99 | body()
100 | }
101 | Div(
102 | attrs = Modifier
103 | .classNames("modal-footer")
104 | .toAttrs()
105 | ) {
106 | BSButton(
107 | modifier = Modifier.attrsModifier {
108 | attr("data-bs-dismiss", "modal")
109 | },
110 | text = negativeButtonText,
111 | variant = ButtonVariant.Secondary,
112 | onClick = { onNegativeButtonClick() }
113 | )
114 | BSButton(
115 | modifier = Modifier.attrsModifier {
116 | attr("data-bs-dismiss", "modal")
117 | },
118 | text = positiveButtonText,
119 | variant = ButtonVariant.Primary,
120 | onClick = { onPositiveButtonClick() }
121 | )
122 | }
123 | }
124 | }
125 | }
126 | }
127 |
128 | /**
129 | * Util function which is used to trigger/show [BSModal] component.
130 | * */
131 | fun Modifier.showModalOnClick(id: String): Modifier = attrsModifier {
132 | attr("data-bs-toggle", "modal")
133 | attr("data-bs-target", "#$id")
134 | }
135 |
136 | /**
137 | * Util function which is used to hide [BSModal] component.
138 | * */
139 | fun Modifier.hideModalOnClick(): Modifier = attrsModifier {
140 | attr("data-bs-dismiss", "modal")
141 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSNavBar.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.forms.BSInput
5 | import com.stevdza.san.kotlinbs.models.BackgroundStyle
6 | import com.stevdza.san.kotlinbs.models.navbar.*
7 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
8 | import com.varabyte.kobweb.compose.css.Cursor
9 | import com.varabyte.kobweb.compose.foundation.layout.Column
10 | import com.varabyte.kobweb.compose.foundation.layout.Row
11 | import com.varabyte.kobweb.compose.ui.*
12 | import com.varabyte.kobweb.compose.ui.modifiers.*
13 | import org.jetbrains.compose.web.css.CSSNumeric
14 | import org.jetbrains.compose.web.css.px
15 | import org.jetbrains.compose.web.dom.*
16 |
17 | /**
18 | * UI element that helps create responsive navigation menus for websites or web applications.
19 | * The NavBar provides a consistent and user-friendly way to navigate between different
20 | * sections or pages of a website.
21 | *
22 | * The NavBar typically appears at the top of the web page and contains various navigation
23 | * elements such as links, buttons, dropdown menus, and branding elements like logos or
24 | * site names. It adapts to different screen sizes and devices, making it ideal for
25 | * responsive web design.
26 | * @param stickyTop Whether to make this component stickied on the top.
27 | * @param brand A [NavBarBrand] that allows you to specify a brand text, and optional
28 | * brand image as well.
29 | * @param items Currently there are two different [NavItem]'s that you can use. [NavLink]
30 | * that represents a simple link within this component, usually used to navigate between
31 | * different pages on your website. And the second one [NavDropdown] used to display a
32 | * dropdown menu item within this component.
33 | * @param itemsAlignment The alignment of the NavBar items.
34 | * @param inputField Use this parameter if you want to display an input field.
35 | * @param button An optional button.
36 | * @param offcanvas Use this parameter if you want to replace a default expanded
37 | * navBar with a [BSOffcanvas] (Side panel/Overflow panel).
38 | * @param expand This parameter allows you to specify when should your component
39 | * display a collapsed state.
40 | * @param horizontalPadding The padding on the left/right of the component.
41 | * @param backgroundStyle A background style of this component.
42 | * */
43 | @Composable
44 | fun BSNavBar(
45 | modifier: Modifier = Modifier,
46 | stickyTop: Boolean = false,
47 | brand: NavBarBrand? = null,
48 | items: List,
49 | itemsAlignment: Alignment.Horizontal = Alignment.Start,
50 | inputField: NavBarInputField? = null,
51 | button: NavBarButton? = null,
52 | offcanvas: NavBarOffcanvas? = null,
53 | expand: NavBarExpand = NavBarExpand.LG,
54 | horizontalPadding: CSSLengthOrPercentageNumericValue = 8.px,
55 | backgroundStyle: BackgroundStyle = BackgroundStyle.Light
56 | ) {
57 | Nav(
58 | attrs = modifier
59 | .classNames(
60 | "navbar",
61 | expand.value,
62 | backgroundStyle.value
63 | )
64 | .thenIf(
65 | condition = stickyTop,
66 | other = Modifier.classNames("sticky-top")
67 | )
68 | .toAttrs {
69 | if (backgroundStyle == BackgroundStyle.Light ||
70 | backgroundStyle == BackgroundStyle.Info ||
71 | backgroundStyle == BackgroundStyle.Warning
72 | ) {
73 | attr("data-bs-theme", "light")
74 | } else {
75 | attr("data-bs-theme", "dark")
76 | }
77 | }
78 | ) {
79 | Div(
80 | attrs = Modifier
81 | .padding(leftRight = horizontalPadding)
82 | .classNames("container-fluid").toAttrs()
83 | ) {
84 | if (brand != null) {
85 | A(
86 | attrs = Modifier
87 | .classNames("navbar-brand")
88 | .toAttrs(),
89 | href = brand.href
90 | ) {
91 | Row(
92 | verticalAlignment = Alignment.CenterVertically
93 | ) {
94 | if (brand.image != null) {
95 | Img(
96 | src = brand.image,
97 | alt = "Brand Logo",
98 | attrs = Modifier
99 | .width(brand.imageWidth)
100 | .margin(right = 8.px)
101 | .classNames("d-inline-block", "align-text-top")
102 | .toAttrs()
103 | )
104 | }
105 | SpanText(brand.title)
106 | }
107 | }
108 | }
109 | Button(
110 | attrs = Modifier
111 | .classNames("navbar-toggler")
112 | .toAttrs {
113 | if (offcanvas != null) {
114 | attr("data-bs-toggle", "offcanvas")
115 | attr("data-bs-target", "#${offcanvas.id}")
116 | } else {
117 | attr("data-bs-toggle", "collapse")
118 | attr("data-bs-target", "#navbarSupportedContent")
119 | }
120 | attr("aria-controls", "navbarSupportedContent")
121 | attr("aria-expanded", "false")
122 | attr("aria-label", "Toggle Navigation")
123 | }
124 | ) {
125 | Span(attrs = Modifier.classNames("navbar-toggler-icon").toAttrs())
126 | }
127 | Div(
128 | attrs = Modifier
129 | .id("navbarSupportedContent")
130 | .classNames("collapse", "navbar-collapse")
131 | .toAttrs()
132 | ) {
133 | NavBarContent(
134 | items = items,
135 | itemsAlignment = itemsAlignment,
136 | inputField = inputField,
137 | button = button
138 | )
139 | }
140 | }
141 | }
142 | if (offcanvas != null) {
143 | BSOffcanvas(
144 | id = offcanvas.id,
145 | title = offcanvas.title,
146 | body = {
147 | Column {
148 | NavBarContent(
149 | items = items,
150 | itemsAlignment = itemsAlignment,
151 | inputField = inputField,
152 | button = button,
153 | offcanvas = offcanvas
154 | )
155 | }
156 | },
157 | dark = offcanvas.dark,
158 | allowScrolling = offcanvas.allowScrolling,
159 | disableBackdrop = offcanvas.disableBackdrop,
160 | closableOutside = offcanvas.closableOutside,
161 | placement = offcanvas.placement
162 | )
163 | }
164 | }
165 |
166 | @Composable
167 | private fun NavBarContent(
168 | items: List,
169 | itemsAlignment: Alignment.Horizontal,
170 | inputField: NavBarInputField?,
171 | button: NavBarButton?,
172 | offcanvas: NavBarOffcanvas? = null
173 | ) {
174 | Ul(
175 | attrs = Modifier
176 | .classNames(
177 | "navbar-nav",
178 | if (offcanvas != null) {
179 | "me-auto"
180 | } else {
181 | if (itemsAlignment is Alignment.Start) "me-auto" else if (itemsAlignment is Alignment.End) "ms-auto" else "mx-auto"
182 | }
183 | )
184 | .toAttrs()
185 | ) {
186 | items.forEachIndexed { index, navItem ->
187 | if (navItem is NavDropdown) {
188 | Li(
189 | attrs = Modifier
190 | .classNames("nav-item", "dropdown")
191 | .toAttrs()
192 | ) {
193 | A(
194 | attrs = Modifier
195 | .classNames("nav-link", "dropdown-toggle")
196 | .toAttrs {
197 | attr("role", "button")
198 | attr("data-bs-toggle", "dropdown")
199 | attr("aria-expanded", "false")
200 | }
201 | ) {
202 | Text(value = navItem.placeholder)
203 | }
204 | Ul(
205 | attrs = Modifier.classNames("dropdown-menu").toAttrs()
206 | ) {
207 | navItem.items.forEachIndexed { index, dropdownItem ->
208 | Li(
209 | attrs = Modifier
210 | .id(dropdownItem.id)
211 | .onClick {
212 | dropdownItem.onClick(index)
213 | }
214 | .toAttrs()
215 | ) {
216 | A(
217 | attrs = Modifier
218 | .classNames("dropdown-item")
219 | .cursor(Cursor.Pointer)
220 | .toAttrs(),
221 | ) { Text(value = dropdownItem.title) }
222 | }
223 | }
224 | }
225 | }
226 | } else if (navItem is NavLink) {
227 | Li(
228 | attrs = Modifier
229 | .id(navItem.id)
230 | .classNames("nav-item")
231 | .onClick { navItem.onClick(index) }
232 | .toAttrs()
233 | ) {
234 | A(
235 | attrs = Modifier
236 | .classNames("nav-link")
237 | .thenIf(
238 | condition = navItem.active,
239 | other = Modifier.classNames("active")
240 | )
241 | .thenIf(
242 | condition = navItem.disabled,
243 | other = Modifier.classNames("disabled")
244 | )
245 | .cursor(Cursor.Pointer)
246 | .toAttrs {
247 | if (navItem.active) attr("aria-current", "page")
248 | }
249 | ) {
250 | Text(value = navItem.title)
251 | }
252 | }
253 | }
254 | }
255 | }
256 | Form(
257 | attrs = Modifier
258 | .classNames("d-flex")
259 | .toAttrs {
260 | attr("role", "search")
261 | }
262 | ) {
263 | if (inputField != null) {
264 | BSInput(
265 | modifier = Modifier
266 | .thenIf(
267 | condition = button != null,
268 | other = Modifier.margin(right = 8.px)
269 | ),
270 | id = inputField.id,
271 | placeholder = inputField.placeholder,
272 | value = inputField.value,
273 | onValueChange = inputField.onValueChange,
274 | onEnterClick = inputField.onEnterClick
275 | )
276 | }
277 | if (button != null) {
278 | BSButton(
279 | id = button.id,
280 | text = button.text,
281 | variant = button.variant,
282 | disabled = button.disabled,
283 | loading = button.loading,
284 | loadingText = button.loadingText,
285 | badge = button.badge,
286 | onClick = button.onClick
287 | )
288 | }
289 | }
290 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSPagination.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.*
4 | import com.stevdza.san.kotlinbs.models.NextButton
5 | import com.stevdza.san.kotlinbs.models.PaginationSize
6 | import com.stevdza.san.kotlinbs.models.PreviousButton
7 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
8 | import com.varabyte.kobweb.compose.css.Cursor
9 | import com.varabyte.kobweb.compose.css.UserSelect
10 | import com.varabyte.kobweb.compose.ui.Modifier
11 | import com.varabyte.kobweb.compose.ui.modifiers.*
12 | import com.varabyte.kobweb.compose.ui.thenIf
13 | import com.varabyte.kobweb.compose.ui.toAttrs
14 | import org.jetbrains.compose.web.dom.A
15 | import org.jetbrains.compose.web.dom.Li
16 | import org.jetbrains.compose.web.dom.Nav
17 | import org.jetbrains.compose.web.dom.Ul
18 |
19 | /**
20 | * Pagination component is used to divide long lists or tables into multiple pages,
21 | * making it easier for users to navigate through the content.
22 | * @param id A unique identifier of the button.
23 | * @param pages A total number of pages. The minimum number of pages is 2.
24 | * @param currentPage Currently selected page. This will apply an active state to the page.
25 | * @param maxVisiblePages How many pages should be visible on the screen.
26 | * @param size The size of the [BSPagination] component.
27 | * @param previousButton Preview button when pressed, should decrement the [currentPage] value.
28 | * @param nextButton Next button when pressed, should increment the [currentPage] value.
29 | * @param onPageClick Lambda which is triggered when you click on a page. It holds the Int value
30 | * of the clicked page.
31 | * */
32 | @Composable
33 | fun BSPagination(
34 | modifier: Modifier = Modifier,
35 | id: String? = null,
36 | pages: Int,
37 | currentPage: Int = 1,
38 | maxVisiblePages: Int = 3,
39 | size: PaginationSize = PaginationSize.Default,
40 | previousButton: PreviousButton? = null,
41 | nextButton: NextButton? = null,
42 | onPageClick: (Int) -> Unit
43 | ) {
44 | val randomId = remember {
45 | id ?: UniqueIdGenerator.generateUniqueId("pagination")
46 | }
47 | val numberOfPages = remember(pages) { if (pages > 1) 1..pages else 1..2 }
48 | val numberOfMaxVisiblePages = remember(maxVisiblePages) { if (maxVisiblePages < 2) 2 else maxVisiblePages }
49 | val currentPageInternal = remember(currentPage) {
50 | if (currentPage <= numberOfPages.last
51 | && currentPage >= numberOfPages.first
52 | ) currentPage
53 | else if (currentPage < 1) 1
54 | else numberOfPages.last
55 | }
56 |
57 | var previousButtonDisabled by remember(previousButton?.disabled) { mutableStateOf(previousButton?.disabled) }
58 | var nextButtonDisabled by remember(nextButton?.disabled) { mutableStateOf(nextButton?.disabled) }
59 |
60 | val currentPageIndex = remember(currentPageInternal) { numberOfPages.indexOf(currentPageInternal) }
61 | val endIndex = remember(currentPageIndex) { (currentPageIndex + numberOfMaxVisiblePages).coerceAtMost(numberOfPages.count()) }
62 | val visiblePages = remember(currentPageIndex) {
63 | numberOfPages.toList().subList(
64 | if ((endIndex - currentPageIndex) < numberOfMaxVisiblePages)
65 | currentPageIndex - (numberOfMaxVisiblePages - (endIndex - currentPageIndex))
66 | else currentPageIndex,
67 | endIndex
68 | )
69 | }
70 |
71 | LaunchedEffect(currentPageInternal) {
72 | previousButtonDisabled = currentPageInternal <= numberOfPages.first
73 | nextButtonDisabled = currentPageInternal >= numberOfPages.last
74 | }
75 |
76 | Nav(
77 | attrs = modifier
78 | .id(randomId)
79 | .toAttrs {
80 | attr("aria-label", "Pagination")
81 | }
82 | ) {
83 | Ul(
84 | attrs = Modifier
85 | .classNames(*size.classes.toTypedArray())
86 | .toAttrs()
87 | ) {
88 | if (previousButton != null) {
89 | Li(
90 | attrs = Modifier
91 | .classNames("page-item")
92 | .thenIf(
93 | condition = previousButtonDisabled == true,
94 | other = Modifier.classNames("disabled")
95 | )
96 | .onClick {
97 | if (currentPageInternal > numberOfPages.first) {
98 | previousButton.onClick(currentPageInternal - 1)
99 | }
100 | }
101 | .cursor(Cursor.Pointer)
102 | .toAttrs()
103 | ) {
104 | A(
105 | attrs = Modifier
106 | .classNames("page-link")
107 | .toAttrs {
108 | if (previousButtonDisabled == true) attr("tabindex", "-1")
109 | }
110 | ) {
111 | SpanText(
112 | modifier = Modifier.userSelect(UserSelect.None),
113 | text = previousButton.text
114 | )
115 | }
116 | }
117 | }
118 | visiblePages.forEach { page ->
119 | Li(
120 | attrs = Modifier
121 | .classNames("page-item")
122 | .thenIf(
123 | condition = currentPageInternal == page,
124 | other = Modifier.classNames("active")
125 | )
126 | .onClick { onPageClick(page) }
127 | .cursor(Cursor.Pointer)
128 | .toAttrs()
129 | ) {
130 | A(
131 | attrs = Modifier
132 | .classNames("page-link")
133 | .toAttrs()
134 | ) {
135 | SpanText(
136 | modifier = Modifier.userSelect(UserSelect.None),
137 | text = "$page"
138 | )
139 | }
140 | }
141 | }
142 | if (nextButton != null) {
143 | Li(
144 | attrs = Modifier
145 | .classNames("page-item")
146 | .thenIf(
147 | condition = nextButtonDisabled == true,
148 | other = Modifier.classNames("disabled")
149 | )
150 | .onClick {
151 | if (currentPageInternal < numberOfPages.last) {
152 | nextButton.onClick(currentPageInternal + 1)
153 | }
154 | }
155 | .cursor(Cursor.Pointer)
156 | .toAttrs()
157 | ) {
158 | A(
159 | attrs = Modifier
160 | .classNames("page-link")
161 | .toAttrs {
162 | if (nextButtonDisabled == true) attr("tabindex", "-1")
163 | }
164 | ) {
165 | SpanText(
166 | modifier = Modifier.userSelect(UserSelect.None),
167 | text = nextButton.text
168 | )
169 | }
170 | }
171 | }
172 | }
173 | }
174 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSProgress.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.BackgroundStyle
5 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
6 | import com.varabyte.kobweb.compose.ui.Modifier
7 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
8 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxWidth
9 | import com.varabyte.kobweb.compose.ui.modifiers.height
10 | import com.varabyte.kobweb.compose.ui.modifiers.width
11 | import com.varabyte.kobweb.compose.ui.thenIf
12 | import com.varabyte.kobweb.compose.ui.toAttrs
13 | import org.jetbrains.compose.web.css.*
14 | import org.jetbrains.compose.web.dom.Div
15 | import org.jetbrains.compose.web.dom.Text
16 |
17 | /**
18 | * A visual element used to display the progress or completion of a task or process.
19 | * It provides a horizontal bar that visually represents the current status or percentage
20 | * of completion for a particular operation.
21 | * @param label Progress label.
22 | * @param percentage Progress completion in percentage. From 0% to 100%.
23 | * @param height Progress height.
24 | * @param style Background style of progress.
25 | * @param striped Whether to use a stripped style.
26 | * @param stripedAnimated Whether to animate a striped style.
27 | * */
28 | @Composable
29 | fun BSProgress(
30 | modifier: Modifier = Modifier.fillMaxWidth(),
31 | label: String? = null,
32 | percentage: CSSSizeValue = 50.percent,
33 | height: CSSLengthOrPercentageNumericValue = 20.px,
34 | width: CSSLengthOrPercentageNumericValue = 300.px,
35 | style: BackgroundStyle = BackgroundStyle.Primary,
36 | striped: Boolean = false,
37 | stripedAnimated: Boolean = false
38 | ) {
39 | Div(
40 | attrs = modifier
41 | .height(height)
42 | .width(width)
43 | .classNames("progress")
44 | .toAttrs()
45 | ) {
46 | Div(
47 | attrs = Modifier
48 | .classNames("progress-bar", style.value)
49 | .thenIf(
50 | condition = striped,
51 | other = Modifier.classNames("progress-bar-striped")
52 | )
53 | .thenIf(
54 | condition = stripedAnimated,
55 | other = Modifier.classNames("progress-bar-striped", "progress-bar-animated")
56 | )
57 | .width(percentage)
58 | .toAttrs {
59 | attr("role", "progressbar")
60 | attr("aria-label", "Progress Bar")
61 | attr("aria-valuenow", "${percentage.value}")
62 | attr("aria-valuemin", "0")
63 | attr("aria-valuemax", "100")
64 | }
65 | ) {
66 | label?.let { Text(value = it) }
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSSpinner.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.SpinnerStyle
5 | import com.stevdza.san.kotlinbs.models.SpinnerVariant
6 | import com.varabyte.kobweb.compose.ui.Modifier
7 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
8 | import com.varabyte.kobweb.compose.ui.modifiers.size
9 | import com.varabyte.kobweb.compose.ui.thenIf
10 | import com.varabyte.kobweb.compose.ui.toAttrs
11 | import org.jetbrains.compose.web.css.cssRem
12 | import org.jetbrains.compose.web.dom.Div
13 | import org.jetbrains.compose.web.dom.Span
14 | import org.jetbrains.compose.web.dom.Text
15 |
16 | /**
17 | * Indicate that content is loading or processing. It provides an animated spinner that
18 | * rotates continuously, giving users a visual cue that an operation is in progress, and
19 | * they need to wait for the result.
20 | * @param style The visual style of the spinner.
21 | * @param variant A different variant of the spinner.
22 | * */
23 | @Composable
24 | fun BSSpinner(
25 | modifier: Modifier = Modifier,
26 | style: SpinnerStyle = SpinnerStyle.Primary,
27 | variant: SpinnerVariant = SpinnerVariant.Default
28 | ) {
29 | Div(attrs = modifier
30 | .classNames(
31 | style.value,
32 | *variant.classes.toTypedArray()
33 | )
34 | .thenIf(
35 | condition = variant is SpinnerVariant.Large || variant is SpinnerVariant.LargeGrow,
36 | other = Modifier.size(3.cssRem)
37 | )
38 | .toAttrs {
39 | attr("role", "status")
40 | }
41 | ) {
42 | Span(attrs = Modifier.classNames("visually-hidden").toAttrs()) {
43 | Text(value = "Loading...")
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSToast.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.*
5 | import com.stevdza.san.kotlinbs.models.button.ButtonSize
6 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
7 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
8 | import com.varabyte.kobweb.compose.foundation.layout.Box
9 | import com.varabyte.kobweb.compose.foundation.layout.Row
10 | import com.varabyte.kobweb.compose.ui.*
11 | import com.varabyte.kobweb.compose.ui.modifiers.*
12 | import kotlinx.browser.document
13 | import org.jetbrains.compose.web.css.percent
14 | import org.jetbrains.compose.web.css.px
15 | import org.jetbrains.compose.web.dom.Div
16 | import org.w3c.dom.HTMLDivElement
17 |
18 | /**
19 | * Scope which is used to place only Toast components within the [BSToastGroup].
20 | * */
21 | class ToastGroupScope
22 |
23 | private val toastGroupScopeImpl = ToastGroupScope()
24 |
25 | /**
26 | * Lightweight and unobtrusive UI element used to display temporary messages or
27 | * notifications to users. It is typically used to show non-disruptive information that
28 | * can be easily dismissed or will automatically disappear after a short period.
29 | *
30 | * This composable represents a group that can contain single or multiple different
31 | * toast components. Call a [showToast] function with specified [BSToast] id in
32 | * order to display it.
33 | * @param placement Where should be this Toast displayed.
34 | * @param content Content that should contain single, or multiple different Toast
35 | * components, that when displayed, should be stacked on top of each other.
36 | * */
37 | @Composable
38 | fun BSToastGroup(
39 | modifier: Modifier = Modifier,
40 | placement: ToastPlacement = ToastPlacement.BottomRight,
41 | content: @Composable ToastGroupScope.() -> Unit
42 | ) {
43 | Div(
44 | attrs = modifier
45 | .classNames("position-static")
46 | .toAttrs()
47 | ) {
48 | Div(
49 | attrs = Modifier
50 | .padding(all = 16.px)
51 | .classNames(*placement.classes.toTypedArray(), "toast-container")
52 | .styleModifier {
53 | property("pointer-events", "none")
54 | }
55 | .toAttrs()
56 | ) {
57 | content(toastGroupScopeImpl)
58 | }
59 | }
60 | }
61 |
62 | /**
63 | * Lightweight and unobtrusive UI element used to display temporary messages or
64 | * notifications to users. It is typically used to show non-disruptive information that
65 | * can be easily dismissed or will automatically disappear after a short period.
66 | * Call a [showToast] function with specified [BSToast] id in order to display it.
67 | * @param id Unique identifier of the toast component.
68 | * @param title Title of the toast.
69 | * @param body Body of the toast.
70 | * @param autoHide Whether to autohide the toast after a certain delay or not.
71 | * @param indicatorStyle Background color.
72 | * @param onCloseClick Lambda that is triggered when close button is clicked.
73 | * */
74 | @Composable
75 | fun ToastGroupScope.BSToast(
76 | modifier: Modifier = Modifier,
77 | id: String,
78 | title: String,
79 | body: String,
80 | autoHide: Boolean = true,
81 | indicatorStyle: BackgroundStyle = BackgroundStyle.Primary,
82 | onCloseClick: () -> Unit
83 | ) {
84 | Div(
85 | attrs = modifier
86 | .id(id)
87 | .zIndex(9)
88 | .classNames("toast")
89 | .styleModifier {
90 | property("pointer-events", "auto")
91 | }
92 | .toAttrs {
93 | attr("role", "alert")
94 | attr("aria-live", "assertive")
95 | attr("aria-atomic", "true")
96 | attr("data-bs-autohide", autoHide.toString())
97 | }
98 | ) {
99 | Row(
100 | modifier = Modifier.classNames("toast-header"),
101 | horizontalArrangement = Arrangement.SpaceBetween
102 | ) {
103 | Row(verticalAlignment = Alignment.CenterVertically) {
104 | Box(
105 | modifier = Modifier
106 | .size(16.px)
107 | .borderRadius(50.percent)
108 | .margin(right = 8.px)
109 | .classNames(indicatorStyle.value)
110 | )
111 | SpanText(text = title)
112 | }
113 | BSCloseButton(
114 | modifier = Modifier
115 | .onClick { onCloseClick() }
116 | .attrsModifier {
117 | attr("data-bs-dismiss", "toast")
118 | }
119 | )
120 | }
121 | Div(
122 | attrs = Modifier
123 | .classNames("toast-body")
124 | .toAttrs()
125 | ) {
126 | SpanText(text = body)
127 | }
128 | }
129 | }
130 |
131 | /**
132 | * Toast variant that contains just a text and a close button.
133 | * Lightweight and unobtrusive UI element used to display temporary
134 | * messages or notifications to users. It is typically used to show non-disruptive
135 | * information that can be easily dismissed or will automatically disappear after a short
136 | * period.
137 | * Call a [showToast] function with specified [BSToast] id in order to display it.
138 | * @param id Unique identifier of the toast component.
139 | * @param text Toast text.
140 | * @param style The style of the toast.
141 | * @param autoHide Whether to autohide the toast after a certain delay or not.
142 | * @param closeButtonDark Whether a close icon should have a dark color or not.
143 | * @param onCloseClick Lambda that is triggered when close button is clicked.
144 | * */
145 | @Composable
146 | fun ToastGroupScope.BSToastBasic(
147 | modifier: Modifier = Modifier,
148 | id: String,
149 | text: String,
150 | style: ToastStyle = ToastStyle.Primary,
151 | autoHide: Boolean = true,
152 | closeButtonDark: Boolean = true,
153 | onCloseClick: () -> Unit
154 | ) {
155 | Div(
156 | attrs = modifier
157 | .id(id)
158 | .zIndex(9)
159 | .classNames("toast", style.value, "border-0")
160 | .styleModifier {
161 | property("pointer-events", "auto")
162 | }
163 | .toAttrs {
164 | attr("role", "alert")
165 | attr("aria-live", "assertive")
166 | attr("aria-atomic", "true")
167 | attr("data-bs-autohide", autoHide.toString())
168 | }
169 | ) {
170 | Row(
171 | modifier = Modifier.classNames("toast-body"),
172 | horizontalArrangement = Arrangement.SpaceBetween
173 | ) {
174 | SpanText(text = text)
175 | BSCloseButton(
176 | modifier = Modifier
177 | .onClick { onCloseClick() }
178 | .attrsModifier {
179 | attr("data-bs-dismiss", "toast")
180 | },
181 | dark = closeButtonDark
182 | )
183 | }
184 | }
185 | }
186 |
187 | /**
188 | * Toast variant that contains two action buttons.
189 | * Lightweight and unobtrusive UI element used to display temporary
190 | * messages or notifications to users. It is typically used to show non-disruptive
191 | * information that can be easily dismissed or will automatically disappear after a short
192 | * period.
193 | * Call a [showToast] function with specified [BSToast] id in order to display it.
194 | * @param id Unique identifier of the toast component.
195 | * @param text Toast text.
196 | * @param positiveButtonText The text of the positive button.
197 | * @param negativeButtonText The text of the negative button.
198 | * @param autoHide Whether to autohide the toast after a certain delay or not.
199 | * @param onPositiveButtonClick A lambda that is triggered when a user clicks a positive
200 | * button.
201 | * @param onNegativeButtonClick A lambda that is triggered when a user clicks a negative
202 | * button.
203 | * @param style The style of the toast.
204 | * @param borderColor The color of the border.
205 | * @param positiveButtonVariant The style of the positive button.
206 | * @param negativeButtonVariant The style of the negative button.
207 | *
208 | * */
209 | @Composable
210 | fun ToastGroupScope.BSToastAction(
211 | modifier: Modifier = Modifier,
212 | id: String,
213 | text: String,
214 | positiveButtonText: String = "Take Action",
215 | negativeButtonText: String = "Close",
216 | autoHide: Boolean = false,
217 | onPositiveButtonClick: () -> Unit,
218 | onNegativeButtonClick: () -> Unit,
219 | style: ToastStyle = ToastStyle.Link,
220 | borderColor: BorderColor? = null,
221 | positiveButtonVariant: ButtonVariant = ButtonVariant.Primary,
222 | negativeButtonVariant: ButtonVariant = ButtonVariant.Secondary,
223 | ) {
224 | Div(
225 | attrs = modifier
226 | .id(id)
227 | .zIndex(9)
228 | .classNames("toast", style.value)
229 | .styleModifier {
230 | property("pointer-events", "auto")
231 | }
232 | .toAttrs {
233 | attr("role", "alert")
234 | attr("aria-live", "assertive")
235 | attr("aria-atomic", "true")
236 | attr("data-bs-autohide", autoHide.toString())
237 | }
238 | ) {
239 | Div(
240 | attrs = Modifier.classNames("toast-body").toAttrs(),
241 | ) {
242 | SpanText(text = text)
243 | Row(
244 | modifier = Modifier
245 | .classNames("mt-2", "pt-2", "border-top")
246 | .thenIf(
247 | condition = borderColor != null,
248 | other = Modifier.classNames(borderColor?.value ?: "")
249 | )
250 | ) {
251 | BSButton(
252 | modifier = Modifier
253 | .attrsModifier {
254 | attr("data-bs-dismiss", "toast")
255 | },
256 | text = positiveButtonText,
257 | size = ButtonSize.Small,
258 | variant = positiveButtonVariant,
259 | onClick = onPositiveButtonClick
260 | )
261 | BSButton(
262 | modifier = Modifier
263 | .margin(left = 8.px)
264 | .attrsModifier {
265 | attr("data-bs-dismiss", "toast")
266 | },
267 | text = negativeButtonText,
268 | size = ButtonSize.Small,
269 | variant = negativeButtonVariant,
270 | onClick = onNegativeButtonClick
271 | )
272 | }
273 | }
274 | }
275 | }
276 |
277 | /**
278 | * A function which is used to trigger/show a Toast component which you have already
279 | * declared somewhere within your composable hierarchy.
280 | * @param toastId The ID of the Toast component that you've already declared, and want to show.
281 | * */
282 | fun showToast(toastId: String) {
283 | try {
284 | val toastElement = (document.getElementById(toastId) as HTMLDivElement)
285 | val jsCode = """
286 | const toast = new bootstrap.Toast(toastElement);
287 | toast.show();
288 | """.trimIndent()
289 | js("eval(jsCode)") as Unit
290 | } catch (e: Exception) {
291 | println("showToast: ${e.message}")
292 | }
293 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/BSTooltip.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.TooltipDirection
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 | import com.varabyte.kobweb.compose.ui.toAttrs
7 | import org.jetbrains.compose.web.dom.Div
8 |
9 | /**
10 | * Small pop-up box that provides additional information or context when the user hovers
11 | * over or interacts with an element. Tooltips are commonly used to provide explanations,
12 | * labels, or short descriptions for elements such as buttons, links, or icons.
13 | * [initializeTooltips] function must be called if you want to be able to see those
14 | * same tooltips that you're using.
15 | * @param text Text which will appear inside the tooltip.
16 | * @param direction Direction in which a tooltip will appear.
17 | * @param content Component on top of which you want to add a tooltip.
18 | * */
19 | @Composable
20 | fun BSTooltip(
21 | text: String,
22 | direction: TooltipDirection = TooltipDirection.Top,
23 | content: @Composable () -> Unit
24 | ) {
25 | Div(
26 | attrs = Modifier
27 | .toAttrs {
28 | attr("data-bs-toggle", "tooltip")
29 | attr("data-bs-placement", direction.value)
30 | attr("data-bs-title", text)
31 | }
32 | ) {
33 | content()
34 | }
35 | }
36 |
37 | /**
38 | * An initialization function which is used with tooltips. If you don't call this function,
39 | * your tooltips will not show.
40 | * */
41 | fun initializeTooltips() {
42 | try {
43 | val jsCode = """
44 | var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
45 | var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
46 | return new bootstrap.Tooltip(tooltipTriggerEl)
47 | })
48 | """.trimIndent()
49 | js("eval(jsCode)") as Unit
50 | } catch (e: Exception) {
51 | println("initializeTooltips: ${e.message}")
52 | }
53 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/Offcanvas.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.stevdza.san.kotlinbs.models.offcanvas.OffcanvasPlacement
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 | import com.varabyte.kobweb.compose.ui.attrsModifier
7 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
8 | import com.varabyte.kobweb.compose.ui.modifiers.id
9 | import com.varabyte.kobweb.compose.ui.toAttrs
10 | import org.jetbrains.compose.web.dom.Div
11 | import org.jetbrains.compose.web.dom.H5
12 |
13 | /**
14 | * Offcanvas is used to create sidebar or panel that can slide in and out of the viewport.
15 | * This component is often used to display additional content, navigation menus,
16 | * or options without taking up the entire screen space.
17 | *
18 | * @param id Unique identifier of the component. You will need this in order to
19 | * trigger/show the [BSOffcanvas] component. To do that, specify a [showOffcanvasOnClick]
20 | * modifier on a clickable component, that you want to trigger a [BSOffcanvas] with.
21 | * There's also a [hideOffcanvasOnClick] modifier that is applied on a clickable
22 | * component that you want to use for closing the [BSOffcanvas].
23 | * @param title The title which is displayed on the top of the component, along with
24 | * a [BSCloseButton].
25 | * @param body This is where you place a custom components inside the body of this
26 | * component.
27 | * @param dark Whether this component should have a dark background or not.
28 | * @param allowScrolling Whether to allow scrolling on the page, while [BSOffcanvas]
29 | * is active.
30 | * @param disableBackdrop Whether to remove a black overlay behind the component.
31 | * @param closableOutside Whether to allow users to close this component by clicking
32 | * anywhere on the outside.
33 | * @param placement Side on which you want this component to appear.
34 | * */
35 | @Composable
36 | fun BSOffcanvas(
37 | modifier: Modifier = Modifier,
38 | id: String,
39 | title: String,
40 | body: @Composable () -> Unit,
41 | dark: Boolean = false,
42 | allowScrolling: Boolean = false,
43 | disableBackdrop: Boolean = false,
44 | closableOutside: Boolean = true,
45 | placement: OffcanvasPlacement = OffcanvasPlacement.END
46 | ) {
47 | Div(
48 | attrs = modifier
49 | .id(id)
50 | .classNames("offcanvas",placement.value)
51 | .toAttrs {
52 | attr("tabindex", "-1")
53 | attr("aria-labelledby", "offcanvasLabel")
54 | attr("aria-controls", "#$id")
55 | if (allowScrolling) attr("data-bs-scroll", "true")
56 | if (disableBackdrop) attr("data-bs-backdrop", "false")
57 | if (!closableOutside) attr("data-bs-backdrop", "static")
58 | if (dark) attr("data-bs-theme", "dark")
59 | }
60 | ) {
61 | Div(
62 | attrs = Modifier
63 | .classNames("offcanvas-header")
64 | .toAttrs()
65 | ) {
66 | H5(
67 | attrs = Modifier
68 | .id("offcanvasLabel")
69 | .classNames("offcanvas-title")
70 | .toAttrs()
71 | ) {
72 | SpanText(text = title)
73 | }
74 | BSCloseButton(modifier = Modifier.hideOffcanvasOnClick())
75 | }
76 | Div(
77 | attrs = Modifier
78 | .classNames("offcanvas-body")
79 | .toAttrs()
80 | ) {
81 | body()
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * Util function which is used to trigger/show [BSOffcanvas] component.
88 | * */
89 | fun Modifier.showOffcanvasOnClick(id: String): Modifier = attrsModifier {
90 | attr("data-bs-toggle", "offcanvas")
91 | attr("data-bs-target", "#$id")
92 | }
93 |
94 | /**
95 | * Util function which is used to hide [BSOffcanvas] component.
96 | * */
97 | fun Modifier.hideOffcanvasOnClick(): Modifier = attrsModifier {
98 | attr("data-bs-dismiss", "offcanvas")
99 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/SpanText.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.varabyte.kobweb.compose.ui.Modifier
5 | import com.varabyte.kobweb.compose.ui.toAttrs
6 | import org.jetbrains.compose.web.dom.Span
7 | import org.jetbrains.compose.web.dom.Text
8 |
9 | // This is a loose fork of SpanText from Kobweb's Silk widget set. However, we don't want to depend on Silk, so
10 | // we just duplicate the subset of it we care about ourselves.
11 |
12 | @Composable
13 | internal fun SpanText(
14 | text: String,
15 | modifier: Modifier = Modifier,
16 | ) {
17 | Span(modifier.toAttrs()) {
18 | Text(text)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/components/SvgElement.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import kotlinx.browser.document
5 | import org.jetbrains.compose.web.dom.AttrBuilderContext
6 | import org.jetbrains.compose.web.dom.ContentBuilder
7 | import org.jetbrains.compose.web.dom.ElementBuilder
8 | import org.jetbrains.compose.web.dom.TagElement
9 | import org.w3c.dom.Element
10 | import org.w3c.dom.svg.SVGElement
11 | import org.w3c.dom.svg.SVGPathElement
12 |
13 | private val Svg: ElementBuilder = object : ElementBuilder {
14 | private val el: Element by lazy { document.createElementNS("http://www.w3.org/2000/svg", "svg") }
15 | override fun create(): SVGElement = el.cloneNode() as SVGElement
16 | }
17 |
18 | private val Path: ElementBuilder = object : ElementBuilder {
19 | private val el: Element by lazy { document.createElementNS("http://www.w3.org/2000/svg", "path") }
20 | override fun create(): SVGPathElement = el.cloneNode() as SVGPathElement
21 | }
22 |
23 | @Composable
24 | internal fun Svg(
25 | attrs: AttrBuilderContext? = null,
26 | content: ContentBuilder? = null
27 | ) {
28 | TagElement(
29 | elementBuilder = Svg,
30 | applyAttrs = attrs,
31 | content = content
32 | )
33 | }
34 |
35 | @Composable
36 | internal fun Path(
37 | attrs: AttrBuilderContext? = null,
38 | content: ContentBuilder? = null
39 | ) {
40 | TagElement(
41 | elementBuilder = Path,
42 | applyAttrs = attrs,
43 | content = content
44 | )
45 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSCheckbox.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
6 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
9 | import com.varabyte.kobweb.compose.ui.modifiers.id
10 | import com.varabyte.kobweb.compose.ui.thenIf
11 | import com.varabyte.kobweb.compose.ui.toAttrs
12 | import kotlinx.browser.document
13 | import org.jetbrains.compose.web.attributes.InputType
14 | import org.jetbrains.compose.web.attributes.disabled
15 | import org.jetbrains.compose.web.dom.Div
16 | import org.jetbrains.compose.web.dom.Input
17 | import org.jetbrains.compose.web.dom.Label
18 | import org.jetbrains.compose.web.dom.Text
19 | import org.w3c.dom.HTMLInputElement
20 |
21 | /**
22 | * UI component used for allowing users to make multiple selections from a predefined
23 | * set of options. It represents a checkbox input field with an associated label.
24 | * @param id Unique identifier of the parent.
25 | * @param label Checkbox label.
26 | * @param disabled Whether the checkbox is disabled or not.
27 | * @param defaultChecked Here you specify a default checked state of the checkbox.
28 | * @param reverse Whether you want to display the checkbox and the label in a reverse order.
29 | * @param toggleButton Whether you want to transform the checkbox into a toggle button.
30 | * @param toggleButtonVariant The style of the toggle button.
31 | * @param onClick Lambda that is triggered when a user clicks on a checkbox.
32 | * */
33 | @Composable
34 | fun BSCheckbox(
35 | modifier: Modifier = Modifier,
36 | id: String? = null,
37 | label: String,
38 | disabled: Boolean = false,
39 | defaultChecked: Boolean = false,
40 | reverse: Boolean = false,
41 | toggleButton: Boolean = false,
42 | toggleButtonVariant: ButtonVariant = ButtonVariant.PrimaryOutline,
43 | onClick: (Boolean) -> Unit
44 | ) {
45 | val randomId = remember {
46 | id ?: UniqueIdGenerator.generateUniqueId("checkbox")
47 | }
48 | Div(
49 | attrs = modifier
50 | .classNames("form-check")
51 | .thenIf(
52 | condition = reverse,
53 | other = Modifier.classNames("form-check-reverse")
54 | )
55 | .toAttrs()
56 | ) {
57 | Input(
58 | attrs = Modifier
59 | .id(randomId)
60 | .thenIf(
61 | condition = toggleButton,
62 | other = Modifier.classNames("btn-check")
63 | )
64 | .thenIf(
65 | condition = !toggleButton,
66 | other = Modifier.classNames("form-check-input")
67 | )
68 | .toAttrs {
69 | if (defaultChecked) defaultChecked()
70 | if (disabled) disabled()
71 | onClick {
72 | onClick((document.getElementById(randomId) as HTMLInputElement).checked)
73 | }
74 | },
75 | type = InputType.Checkbox
76 | )
77 | Label(
78 | attrs = Modifier
79 | .thenIf(
80 | condition = toggleButton,
81 | other = Modifier.classNames(*toggleButtonVariant.classes.toTypedArray())
82 | )
83 | .thenIf(
84 | condition = !toggleButton,
85 | other = Modifier.classNames("form-check-label")
86 | )
87 | .toAttrs(),
88 | forId = randomId
89 | ) {
90 | Text(value = label)
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSColorPicker.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.InputSize
6 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
7 | import com.varabyte.kobweb.compose.ui.Modifier
8 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
9 | import com.varabyte.kobweb.compose.ui.modifiers.id
10 | import com.varabyte.kobweb.compose.ui.thenIf
11 | import com.varabyte.kobweb.compose.ui.toAttrs
12 | import org.jetbrains.compose.web.attributes.InputType
13 | import org.jetbrains.compose.web.attributes.disabled
14 | import org.jetbrains.compose.web.dom.Div
15 | import org.jetbrains.compose.web.dom.Input
16 | import org.jetbrains.compose.web.dom.Label
17 | import org.jetbrains.compose.web.dom.Text
18 |
19 | /**
20 | * UI component that allows users to select a color or specify a custom color
21 | * using a color picker. It is used to capture color preferences or to enable users
22 | * to choose colors for various purposes, such as styling, theming, or data visualization.
23 | * @param id Unique identifier of the color picker.
24 | * @param label Color picker label.
25 | * @param title Color picker title.
26 | * @param size Size of a color picker.
27 | * @param disabled Whether this component is disabled or not.
28 | * @param onColorSelected Lambda which is triggered when a user selects a color.
29 | * */
30 | @Composable
31 | fun BSColorPicker(
32 | modifier: Modifier = Modifier,
33 | id: String? = null,
34 | label: String? = null,
35 | title: String = "Choose your color",
36 | size: InputSize = InputSize.Default,
37 | disabled: Boolean = false,
38 | onColorSelected: (String) -> Unit
39 | ) {
40 | val randomId = remember {
41 | id ?: UniqueIdGenerator.generateUniqueId("colorpicker")
42 | }
43 | Div(attrs = modifier.toAttrs()) {
44 | Label(
45 | attrs = Modifier
46 | .classNames("form-label")
47 | .toAttrs(),
48 | forId = randomId
49 | )
50 | {
51 | label?.let { Text(value = it) }
52 | }
53 | Input(
54 | attrs = Modifier
55 | .id(randomId)
56 | .classNames("form-control-color", "form-control")
57 | .thenIf(
58 | condition = size != InputSize.Default,
59 | other = Modifier.classNames(size.value)
60 | )
61 | .toAttrs {
62 | onChange { onColorSelected(it.value) }
63 | if (disabled) disabled()
64 | attr("title", title)
65 | attr("value", "#000000")
66 | },
67 | type = InputType.Color
68 | )
69 | }
70 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSFileInput.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.*
4 | import com.stevdza.san.kotlinbs.components.BSButton
5 | import com.stevdza.san.kotlinbs.components.SpanText
6 | import com.stevdza.san.kotlinbs.models.InputSize
7 | import com.stevdza.san.kotlinbs.models.button.ButtonSize
8 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
9 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
10 | import com.varabyte.kobweb.browser.file.loadDataUrlFromDisk
11 | import com.varabyte.kobweb.compose.css.Overflow
12 | import com.varabyte.kobweb.compose.css.TextOverflow
13 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement
14 | import com.varabyte.kobweb.compose.foundation.layout.Row
15 | import com.varabyte.kobweb.compose.ui.Alignment
16 | import com.varabyte.kobweb.compose.ui.Modifier
17 | import com.varabyte.kobweb.compose.ui.graphics.Color
18 | import com.varabyte.kobweb.compose.ui.modifiers.*
19 | import com.varabyte.kobweb.compose.ui.thenIf
20 | import com.varabyte.kobweb.compose.ui.toAttrs
21 | import kotlinx.browser.document
22 | import org.jetbrains.compose.web.css.LineStyle
23 | import org.jetbrains.compose.web.css.cssRem
24 | import org.jetbrains.compose.web.css.px
25 | import org.jetbrains.compose.web.css.rgba
26 | import org.jetbrains.compose.web.dom.Div
27 | import org.jetbrains.compose.web.dom.Label
28 | import org.jetbrains.compose.web.dom.Text
29 |
30 | /**
31 | * UI component that allows users to upload files from their local system
32 | * to a web application. It provides a button and an input field where you can see
33 | * the name of the selected file.
34 | * @param id Unique identifier of the parent.
35 | * @param label File Input label that is shown on top of the component.
36 | * @param placeholder A placeholder text that will show up when a file is not selected.
37 | * @param size The size of the File Input.
38 | * @param disabled Whether this component is disabled or not.
39 | * @param accept A string value that you can use to specify which type is accepted/selectable.
40 | * @param onFileSelected Lambda which is triggered when a user selects a file. The first
41 | * string represents a file name, while the second one represents the actual BASE_64
42 | * encoded file.
43 | * */
44 | @Composable
45 | fun BSFileInput(
46 | modifier: Modifier = Modifier,
47 | id: String? = null,
48 | label: String? = null,
49 | placeholder: String = "No file selected.",
50 | size: InputSize = InputSize.Default,
51 | disabled: Boolean = false,
52 | accept: String = "image/png, image/jpeg",
53 | onFileSelected: (String, String) -> Unit
54 | ) {
55 | val randomId = remember {
56 | id ?: UniqueIdGenerator.generateUniqueId("fileinput")
57 | }
58 | var placeholderText by remember { mutableStateOf(placeholder) }
59 | Div(attrs = modifier.toAttrs()) {
60 | if(label != null) {
61 | Label(
62 | attrs = Modifier
63 | .classNames("form-label")
64 | .toAttrs(),
65 | forId = randomId
66 | )
67 | {
68 | Text(value = label)
69 | }
70 | }
71 | Row(
72 | modifier = Modifier
73 | .id(randomId)
74 | .thenIf(
75 | condition = disabled,
76 | // TODO: Will this color get used anywhere? Should we extract it into a constant?
77 | other = Modifier.backgroundColor(Color.rgb(0xFAFAFA))
78 | )
79 | .border(
80 | width = 1.px,
81 | style = LineStyle.Solid,
82 | color = rgba(r = 206, g = 212, b = 218, a = 1)
83 | )
84 | .padding(all = 0.px)
85 | .onClick {
86 | if (!disabled) {
87 | document.loadDataUrlFromDisk(
88 | accept = accept,
89 | onLoad = {
90 | onFileSelected(filename, it)
91 | placeholderText = filename
92 | }
93 | )
94 | }
95 | }
96 | .overflow(Overflow.Hidden)
97 | .textOverflow(TextOverflow.Ellipsis)
98 | .thenIf(
99 | condition = size != InputSize.Default,
100 | other = Modifier.classNames(size.value)
101 | )
102 | .borderRadius(0.375.cssRem),
103 | verticalAlignment = Alignment.CenterVertically,
104 | horizontalArrangement = Arrangement.Start
105 | ) {
106 | BSButton(
107 | modifier = Modifier
108 | .margin(all = 0.px)
109 | .borderRadius(topRight = 0.px, bottomRight = 0.px),
110 | text = "Browse...",
111 | variant = ButtonVariant.Light,
112 | size = when (size) {
113 | InputSize.Default -> {
114 | ButtonSize.Default
115 | }
116 |
117 | InputSize.Small -> {
118 | ButtonSize.Small
119 | }
120 |
121 | InputSize.Large -> {
122 | ButtonSize.Large
123 | }
124 | },
125 | disabled = disabled,
126 | onClick = {}
127 | )
128 | SpanText(
129 | modifier = Modifier
130 | .thenIf(
131 | condition = disabled,
132 | other = Modifier.classNames("text-muted")
133 | )
134 | .margin(leftRight = 12.px),
135 | text = placeholderText
136 | )
137 | }
138 | }
139 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSInput.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.InputSize
6 | import com.stevdza.san.kotlinbs.models.InputValidation
7 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
8 | import com.varabyte.kobweb.compose.ui.Modifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
10 | import com.varabyte.kobweb.compose.ui.modifiers.id
11 | import com.varabyte.kobweb.compose.ui.thenIf
12 | import com.varabyte.kobweb.compose.ui.toAttrs
13 | import org.jetbrains.compose.web.attributes.InputType
14 | import org.jetbrains.compose.web.attributes.disabled
15 | import org.jetbrains.compose.web.attributes.readOnly
16 | import org.jetbrains.compose.web.attributes.required
17 | import org.jetbrains.compose.web.dom.Div
18 | import org.jetbrains.compose.web.dom.Input
19 | import org.jetbrains.compose.web.dom.Label
20 | import org.jetbrains.compose.web.dom.Text
21 |
22 | /**
23 | * UI component used to capture user input or display information. It provides a text field
24 | * or other types of input fields where users can enter data.
25 | * @param id Unique identifier of the component.
26 | * @param label Input field label.
27 | * @param placeholder Input field placeholder.
28 | * @param type The type of the input.
29 | * @param size The size of the input.
30 | * @param disabled Whether this component should be disabled or not.
31 | * @param readOnly Whether to enable a read-only state of the component.
32 | * @param plainText Whether to remove all default styling from the component.
33 | * @param floating Whether to use a floating variant of the component.
34 | * @param required This field is particularly used inside a Form to indicate that
35 | * the form cannot be submitted if this field is empty.
36 | * @param validation Here you can customize the look of the input component, in two
37 | * different states: Success and Error.
38 | * @param value A default value that will be included inside the input.
39 | * @param onValueChange Lambda that will be triggered when a user types something.
40 | * @param onEnterClick Lambda that triggers when you press 'Enter' key on your keyword,
41 | * while the input component is focused.
42 | * inside the component.
43 | * */
44 | @Composable
45 | fun BSInput(
46 | modifier: Modifier = Modifier,
47 | id: String? = null,
48 | label: String? = null,
49 | placeholder: String? = null,
50 | type: InputType = InputType.Text,
51 | size: InputSize = InputSize.Default,
52 | disabled: Boolean = false,
53 | readOnly: Boolean = false,
54 | plainText: Boolean = false,
55 | floating: Boolean = false,
56 | required: Boolean = false,
57 | validation: InputValidation = InputValidation(),
58 | value: String,
59 | onValueChange: (String) -> Unit,
60 | onEnterClick: (() -> Unit)? = null
61 | ) {
62 | val randomId = remember {
63 | id ?: UniqueIdGenerator.generateUniqueId("input")
64 | }
65 | Div(
66 | attrs = modifier
67 | .thenIf(
68 | condition = floating,
69 | other = Modifier.classNames("form-floating")
70 | )
71 | .toAttrs()
72 | ) {
73 | if (!floating && label != null) {
74 | Label(
75 | attrs = Modifier
76 | .classNames("form-label")
77 | .toAttrs(),
78 | forId = randomId
79 | )
80 | {
81 | Text(value = label)
82 | }
83 | }
84 | Input(
85 | attrs = Modifier
86 | .id(randomId)
87 | .classNames(if (plainText) "form-control-plaintext" else "form-control")
88 | .thenIf(
89 | condition = validation.isValid,
90 | other = Modifier.classNames("is-valid")
91 | )
92 | .thenIf(
93 | condition = validation.isInvalid,
94 | other = Modifier.classNames("is-invalid")
95 | )
96 | .thenIf(
97 | condition = size != InputSize.Default,
98 | other = Modifier.classNames(size.value)
99 | )
100 | .toAttrs {
101 | value(value)
102 | onInput { onValueChange(it.value) }
103 | onKeyUp {
104 | if (onEnterClick != null) {
105 | it.preventDefault()
106 | if (it.key == "Enter") {
107 | onEnterClick()
108 | }
109 | }
110 | }
111 | if (!placeholder.isNullOrEmpty()) attr("placeholder", placeholder)
112 | if (disabled) disabled()
113 | if (readOnly) readOnly()
114 | if (required) required()
115 | },
116 | type = type
117 | )
118 | if (validation.isValid) {
119 | Div(
120 | attrs = Modifier
121 | .classNames("valid-feedback")
122 | .toAttrs()
123 | ) {
124 | Text(value = validation.validFeedback)
125 | }
126 | }
127 | if (validation.isInvalid) {
128 | Div(
129 | attrs = Modifier
130 | .classNames("invalid-feedback")
131 | .toAttrs()
132 | ) {
133 | Text(value = validation.invalidFeedback)
134 | }
135 | }
136 | if (floating && label != null) {
137 | Label(
138 | attrs = Modifier
139 | .classNames("form-label")
140 | .toAttrs(),
141 | forId = randomId
142 | )
143 | {
144 | Text(value = label)
145 | }
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSRadioButton.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.runtime.compositionLocalOf
6 | import androidx.compose.runtime.remember
7 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
8 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
11 | import com.varabyte.kobweb.compose.ui.modifiers.id
12 | import com.varabyte.kobweb.compose.ui.thenIf
13 | import com.varabyte.kobweb.compose.ui.toAttrs
14 | import org.jetbrains.compose.web.attributes.AutoComplete
15 | import org.jetbrains.compose.web.attributes.InputType
16 | import org.jetbrains.compose.web.attributes.autoComplete
17 | import org.jetbrains.compose.web.attributes.disabled
18 | import org.jetbrains.compose.web.dom.Div
19 | import org.jetbrains.compose.web.dom.Input
20 | import org.jetbrains.compose.web.dom.Label
21 | import org.jetbrains.compose.web.dom.Text
22 |
23 | private val radioGroupScopeImpl = BSRadioGroupScope()
24 |
25 | /**
26 | * Component used for allowing users to make a single selection from a predefined
27 | * set of options. This component is used within the [BSRadioButtonGroup].
28 | * @param id Unique identifier of the component.
29 | * @param label Label of the radio button.
30 | * @param disabled Whether this component should be disabled or not.
31 | * @param onClick Lambda that is triggered when a user makes a selection.
32 | * */
33 | @Composable
34 | fun BSRadioGroupScope.BSRadioButton(
35 | modifier: Modifier = Modifier,
36 | id: String? = null,
37 | label: String,
38 | disabled: Boolean = false,
39 | onClick: () -> Unit
40 | ) {
41 | val name = getCompositionLocalGroupName()
42 | val checkedValue = getCompositionLocalCheckedValue()
43 | val inline = getCompositionLocalInlineValue()
44 | val reverse = getCompositionLocalReverseValue()
45 | val toggleButton = getCompositionLocalToggleButton()
46 | val toggleButtonStyle = getCompositionLocalToggleButtonStyle()
47 | val randomId = remember {
48 | id ?: UniqueIdGenerator.generateUniqueId("radiobutton")
49 | }
50 | Div(
51 | attrs = modifier
52 | .classNames("form-check")
53 | .thenIf(
54 | condition = inline,
55 | other = Modifier.classNames("form-check-inline")
56 | )
57 | .thenIf(
58 | condition = reverse,
59 | other = Modifier.classNames("form-check-reverse")
60 | )
61 | .toAttrs()
62 | ) {
63 | Input(
64 | attrs = Modifier
65 | .id(randomId)
66 | .thenIf(
67 | condition = toggleButton,
68 | other = Modifier.classNames("btn-check")
69 | )
70 | .thenIf(
71 | condition = !toggleButton,
72 | other = Modifier.classNames("form-check-input")
73 | )
74 | .toAttrs {
75 | if (checkedValue == label) defaultChecked()
76 | if (disabled) disabled()
77 | if (toggleButton) autoComplete(AutoComplete.off)
78 | attr("name", name)
79 | onClick { onClick() }
80 | },
81 | type = InputType.Radio
82 | )
83 | Label(
84 | attrs = Modifier
85 | .thenIf(
86 | condition = toggleButton,
87 | other = Modifier.classNames(*toggleButtonStyle.toTypedArray())
88 | )
89 | .thenIf(
90 | condition = !toggleButton,
91 | other = Modifier.classNames("form-check-label")
92 | )
93 | .toAttrs(),
94 | forId = randomId
95 | ) {
96 | Text(value = label)
97 | }
98 | }
99 | }
100 |
101 | /**
102 | * Component used for allowing users to make a single selection from a predefined
103 | * * set of options. This component is used to wrap multiple different [BSRadioButton]'s
104 | * where only a single selection is allowed.
105 | * @param name The name of this group (It should be unique).
106 | * @param checkedValue A string value that represents a radio button that should be
107 | * selected by default. This value needs to be the same as the name of the [BSRadioButton]'s
108 | * label.
109 | * @param inline Whether this group should be inlined, instead of placed within a column.
110 | * @param reverse Whether to reverse the order of the radio button and a label.
111 | * @param toggleButton Whether to change the style of the radio buttons to toggle buttons.
112 | * @param toggleButtonVariant The style of the toggle button.
113 | * @param content Here, inside the lambda we can call one or multiple [BSRadioButton]'s.
114 | * */
115 | @Composable
116 | fun BSRadioButtonGroup(
117 | modifier: Modifier = Modifier,
118 | name: String? = null,
119 | checkedValue: String? = null,
120 | inline: Boolean = false,
121 | reverse: Boolean = false,
122 | toggleButton: Boolean = false,
123 | toggleButtonVariant: ButtonVariant = ButtonVariant.PrimaryOutline,
124 | content: @Composable BSRadioGroupScope.() -> Unit
125 | ) {
126 | val radioGroupName = remember { name ?: radioGroupScopeImpl.generateNextRadioGroupName() }
127 |
128 | CompositionLocalProvider(
129 | radioGroupScopeImpl.checkedValueCompositionLocal provides checkedValue,
130 | radioGroupScopeImpl.groupNameCompositionLocal provides radioGroupName,
131 | radioGroupScopeImpl.inlineCompositionLocal provides inline,
132 | radioGroupScopeImpl.reverseCompositionLocal provides reverse,
133 | radioGroupScopeImpl.toggleButtonCompositionLocal provides toggleButton,
134 | radioGroupScopeImpl.toggleButtonStyleCompositionLocal provides toggleButtonVariant.classes,
135 | content = {
136 | Div(attrs = modifier.toAttrs()) {
137 | content(radioGroupScopeImpl)
138 | }
139 | }
140 | )
141 | }
142 |
143 | class BSRadioGroupScope {
144 | private var radioGroupNamesCounter = 0
145 |
146 | internal val checkedValueCompositionLocal = compositionLocalOf {
147 | error("No radio group checked value provided")
148 | }
149 |
150 | internal val groupNameCompositionLocal = compositionLocalOf {
151 | error("No radio group name provided")
152 | }
153 |
154 | internal val inlineCompositionLocal = compositionLocalOf {
155 | error("No inline value provided")
156 | }
157 |
158 | internal val reverseCompositionLocal = compositionLocalOf {
159 | error("No reverse value provided")
160 | }
161 |
162 | internal val toggleButtonCompositionLocal = compositionLocalOf {
163 | error("No toggle button provided")
164 | }
165 |
166 | internal val toggleButtonStyleCompositionLocal = compositionLocalOf> {
167 | error("No toggle button style provided")
168 | }
169 |
170 | internal fun generateNextRadioGroupName(): String {
171 | return "\$compose\$generated\$radio\$group-${radioGroupNamesCounter++}"
172 | }
173 | }
174 |
175 | @Composable
176 | internal fun BSRadioGroupScope.getCompositionLocalCheckedValue(): String? {
177 | return checkedValueCompositionLocal.current
178 | }
179 |
180 | @Composable
181 | internal fun BSRadioGroupScope.getCompositionLocalGroupName(): String {
182 | return groupNameCompositionLocal.current
183 | }
184 |
185 | @Composable
186 | internal fun BSRadioGroupScope.getCompositionLocalInlineValue(): Boolean {
187 | return inlineCompositionLocal.current
188 | }
189 |
190 | @Composable
191 | internal fun BSRadioGroupScope.getCompositionLocalReverseValue(): Boolean {
192 | return reverseCompositionLocal.current
193 | }
194 |
195 | @Composable
196 | internal fun BSRadioGroupScope.getCompositionLocalToggleButton(): Boolean {
197 | return toggleButtonCompositionLocal.current
198 | }
199 |
200 | @Composable
201 | internal fun BSRadioGroupScope.getCompositionLocalToggleButtonStyle(): List {
202 | return toggleButtonStyleCompositionLocal.current
203 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSRange.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
6 | import com.varabyte.kobweb.compose.ui.Modifier
7 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
8 | import com.varabyte.kobweb.compose.ui.modifiers.id
9 | import com.varabyte.kobweb.compose.ui.toAttrs
10 | import kotlinx.browser.document
11 | import org.jetbrains.compose.web.attributes.*
12 | import org.jetbrains.compose.web.dom.Div
13 | import org.jetbrains.compose.web.dom.Input
14 | import org.jetbrains.compose.web.dom.Label
15 | import org.jetbrains.compose.web.dom.Text
16 | import org.w3c.dom.HTMLInputElement
17 |
18 | /**
19 | * This UI component is used to capture a numeric value within a specified range.
20 | * It provides a slider control that users can interact with, to select a value within
21 | * the defined range.
22 | * @param id Unique identifier of the component.
23 | * @param label Label of this component.
24 | * @param min The minimum number which is used in this range.
25 | * @param max The maximum number which is used in this range.
26 | * @param step A double value that represents the steps that will be counted while
27 | * interacting with a component.
28 | * @param disabled Whether this component should be disabled or not.
29 | * @param onSelect Lambda which is triggered when you select a value on a slider.
30 | * It provides a double value that represents a selected number from that range.
31 | * */
32 | @Composable
33 | fun BSRange(
34 | modifier: Modifier = Modifier,
35 | id: String? = null,
36 | label: String? = null,
37 | min: Int = 0,
38 | max: Int = 100,
39 | step: Double = 1.0,
40 | disabled: Boolean = false,
41 | onSelect: (Double) -> Unit
42 | ) {
43 | val randomId = remember {
44 | id ?: UniqueIdGenerator.generateUniqueId("range")
45 | }
46 | Div(attrs = modifier.toAttrs()) {
47 | if (label != null) {
48 | Label(
49 | attrs = Modifier
50 | .classNames("form-label")
51 | .toAttrs(),
52 | forId = randomId
53 | ) {
54 | Text(value = label)
55 | }
56 | }
57 | Input(
58 | attrs = Modifier
59 | .id(randomId)
60 | .classNames("form-range")
61 | .toAttrs {
62 | if (disabled) disabled()
63 | min(value = "$min")
64 | max(value = "$max")
65 | step(value = step)
66 | onChange {
67 | onSelect((document.getElementById(randomId) as HTMLInputElement).value.toDouble())
68 | }
69 | },
70 | type = InputType.Range
71 | )
72 | }
73 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSSelect.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.InputValidation
6 | import com.stevdza.san.kotlinbs.models.SelectSize
7 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
8 | import com.varabyte.kobweb.compose.css.disabled
9 | import com.varabyte.kobweb.compose.ui.Modifier
10 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
11 | import com.varabyte.kobweb.compose.ui.modifiers.id
12 | import com.varabyte.kobweb.compose.ui.thenIf
13 | import com.varabyte.kobweb.compose.ui.toAttrs
14 | import org.jetbrains.compose.web.attributes.selected
15 | import org.jetbrains.compose.web.dom.*
16 |
17 | /**
18 | * This component allows you to make a single selection from a list of predefined
19 | * options. Select components are widely used for various purposes, such as selecting
20 | * a country in a registration form, choosing a product category in an e-commerce site, etc.
21 | * @param id A unique identifier of the component.
22 | * @param items Here you define a list of items to be displayed inside this component.
23 | * @param placeholder Placeholder that that will be displayed by default.
24 | * @param size The size of the component.
25 | * @param validation Define the Valid/Invalid style of the component.
26 | * @param disabled Whether this component should be disabled or not.
27 | * @param floating Whether to use the floating style of the component.
28 | * @param floatingLabel The label that will be displayed on top of the component if
29 | * the [floating] is enabled.
30 | * @param onItemSelect Lambda which is triggered when you select an option from this
31 | * component. It provides an index value and the text of the selected option.
32 | * */
33 | @Composable
34 | fun BSSelect(
35 | modifier: Modifier = Modifier,
36 | id: String? = null,
37 | items: List,
38 | placeholder: String? = null,
39 | size: SelectSize = SelectSize.Default,
40 | validation: InputValidation = InputValidation(),
41 | disabled: Boolean = false,
42 | floating: Boolean = false,
43 | floatingLabel: String = "Select",
44 | onItemSelect: (Int, String) -> Unit
45 | ) {
46 | val randomId = remember {
47 | id ?: UniqueIdGenerator.generateUniqueId("select")
48 | }
49 | if (floating) {
50 | Div(
51 | attrs = modifier
52 | .classNames("form-floating")
53 | .toAttrs()
54 | ) {
55 | BSSelectInternal(
56 | id = randomId,
57 | items = items,
58 | placeholder = placeholder,
59 | validation = validation,
60 | size = size,
61 | disabled = disabled,
62 | onItemSelect = onItemSelect
63 | )
64 | Label(
65 | attrs = Modifier
66 | .classNames("form-label")
67 | .toAttrs(),
68 | forId = randomId
69 | ) {
70 | Text(value = floatingLabel)
71 | }
72 | }
73 | } else {
74 | Div(attrs = modifier.toAttrs()) {
75 | BSSelectInternal(
76 | modifier = modifier,
77 | id = randomId,
78 | items = items,
79 | placeholder = placeholder,
80 | validation = validation,
81 | size = size,
82 | disabled = disabled,
83 | onItemSelect = onItemSelect
84 | )
85 | }
86 | }
87 | }
88 |
89 | @Composable
90 | private fun BSSelectInternal(
91 | modifier: Modifier? = null,
92 | id: String,
93 | items: List,
94 | placeholder: String?,
95 | validation: InputValidation,
96 | size: SelectSize,
97 | disabled: Boolean,
98 | onItemSelect: (Int, String) -> Unit
99 | ) {
100 | Select(
101 | attrs = Modifier
102 | .then(modifier ?: Modifier)
103 | .id(id)
104 | .classNames("form-select")
105 | .thenIf(
106 | condition = validation.isValid,
107 | other = Modifier.classNames("is-valid")
108 | )
109 | .thenIf(
110 | condition = validation.isInvalid,
111 | other = Modifier.classNames("is-invalid")
112 | )
113 | .thenIf(
114 | condition = size != SelectSize.Default,
115 | other = Modifier.classNames(size.value)
116 | )
117 | .toAttrs {
118 | if (disabled) disabled()
119 | onChange {
120 | it.value?.let { text ->
121 | if (text != placeholder) {
122 | onItemSelect(items.indexOf(text), text)
123 | }
124 | }
125 | }
126 | }
127 | ) {
128 | if (!placeholder.isNullOrEmpty()) {
129 | Option(
130 | attrs = Modifier.toAttrs { selected() },
131 | value = placeholder
132 | ) {
133 | Text(placeholder)
134 | }
135 | }
136 | items.forEach { text ->
137 | Option(value = text) {
138 | Text(value = text)
139 | }
140 | }
141 | }
142 | if (validation.isValid) {
143 | Div(
144 | attrs = Modifier
145 | .classNames("valid-feedback")
146 | .toAttrs()
147 | ) {
148 | Text(value = validation.validFeedback)
149 | }
150 | }
151 | if (validation.isInvalid) {
152 | Div(
153 | attrs = Modifier
154 | .classNames("invalid-feedback")
155 | .toAttrs()
156 | ) {
157 | Text(value = validation.invalidFeedback)
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSSwitch.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
6 | import com.varabyte.kobweb.compose.ui.Modifier
7 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
8 | import com.varabyte.kobweb.compose.ui.modifiers.id
9 | import com.varabyte.kobweb.compose.ui.toAttrs
10 | import kotlinx.browser.document
11 | import org.jetbrains.compose.web.attributes.InputType
12 | import org.jetbrains.compose.web.attributes.disabled
13 | import org.jetbrains.compose.web.dom.Div
14 | import org.jetbrains.compose.web.dom.Input
15 | import org.jetbrains.compose.web.dom.Label
16 | import org.jetbrains.compose.web.dom.Text
17 | import org.w3c.dom.HTMLInputElement
18 |
19 | /**
20 | * UI component used for binary on/off or true/false selections. It provides a visually
21 | * appealing toggle button that can be toggled between two states.
22 | * @param id Unique identifier of the component.
23 | * @param label The label of this component.
24 | * @param checked Whether this switch is checked or not.
25 | * @param disabled Whether this switch should be disabled or not.
26 | * @param onClick Lambda that is triggered when a user clicks the switch. It holds the
27 | * boolean value that represents a checked state.
28 | * */
29 | @Composable
30 | fun BSSwitch(
31 | modifier: Modifier = Modifier,
32 | id: String? = null,
33 | label: String,
34 | checked: Boolean = false,
35 | disabled: Boolean = false,
36 | onClick: (Boolean) -> Unit
37 | ) {
38 | val randomId = remember {
39 | id ?: UniqueIdGenerator.generateUniqueId("switch")
40 | }
41 | Div(
42 | attrs = modifier
43 | .classNames("form-check", "form-switch")
44 | .toAttrs()
45 | ) {
46 | Input(
47 | attrs = Modifier
48 | .id(randomId)
49 | .classNames("form-check-input")
50 | .toAttrs {
51 | attr("role", "switch")
52 | this@toAttrs.checked(checked)
53 | if (disabled) disabled()
54 | onClick {
55 | onClick((document.getElementById(randomId) as HTMLInputElement).checked)
56 | }
57 | },
58 | type = InputType.Checkbox
59 | )
60 | Label(
61 | attrs = Modifier
62 | .classNames("form-check-label")
63 | .toAttrs(),
64 | forId = randomId
65 | ) {
66 | Text(value = label)
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/forms/BSTextArea.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.forms
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.stevdza.san.kotlinbs.models.InputSize
6 | import com.stevdza.san.kotlinbs.models.InputValidation
7 | import com.stevdza.san.kotlinbs.util.UniqueIdGenerator
8 | import com.varabyte.kobweb.compose.ui.Modifier
9 | import com.varabyte.kobweb.compose.ui.modifiers.classNames
10 | import com.varabyte.kobweb.compose.ui.modifiers.id
11 | import com.varabyte.kobweb.compose.ui.thenIf
12 | import com.varabyte.kobweb.compose.ui.toAttrs
13 | import org.jetbrains.compose.web.attributes.disabled
14 | import org.jetbrains.compose.web.attributes.readOnly
15 | import org.jetbrains.compose.web.attributes.required
16 | import org.jetbrains.compose.web.dom.Div
17 | import org.jetbrains.compose.web.dom.Label
18 | import org.jetbrains.compose.web.dom.Text
19 | import org.jetbrains.compose.web.dom.TextArea
20 |
21 | /**
22 | * UI component used to capture multi-line text input from users. It provides a resizable
23 | * input field that allows users to enter and edit longer blocks of text.
24 | * @param id Unique identifier of the component
25 | * @param value A default value that will be included inside the input.
26 | * @param label Label of the component,
27 | * @param placeholder Placeholder of the component.
28 | * @param disabled Whether this component is disabled or not.
29 | * @param readOnly Whether to enable a read-only state of the component.
30 | * @param floating Whether to use a floating variant of the component.
31 | * @param required This field is particularly used inside a Form to indicate that
32 | * @param size The size of the TextArea component.
33 | * @param validation Here you can customize the look of the input component, in two
34 | * different states: Success and Error.
35 | * @param onValueChange Lambda that will be triggered when a user types something
36 | * inside the component.
37 | * */
38 | @Composable
39 | fun BSTextArea(
40 | modifier: Modifier = Modifier,
41 | id: String? = null,
42 | value: String,
43 | label: String? = null,
44 | placeholder: String? = null,
45 | disabled: Boolean = false,
46 | readOnly: Boolean = false,
47 | floating: Boolean = false,
48 | required: Boolean = false,
49 | size: InputSize = InputSize.Default,
50 | validation: InputValidation = InputValidation(),
51 | onValueChange: (String) -> Unit
52 | ) {
53 | val randomId = remember {
54 | id ?: UniqueIdGenerator.generateUniqueId("textarea")
55 | }
56 | Div(
57 | attrs = modifier
58 | .thenIf(
59 | condition = floating,
60 | other = Modifier.classNames("form-floating")
61 | )
62 | .toAttrs()
63 | ) {
64 | if (!floating && label != null) {
65 | Label(
66 | attrs = Modifier
67 | .classNames("form-label")
68 | .toAttrs(),
69 | forId = randomId
70 | )
71 | {
72 | Text(value = label)
73 | }
74 | }
75 | TextArea(
76 | attrs = modifier
77 | .id(randomId)
78 | .thenIf(
79 | condition = size != InputSize.Default,
80 | other = Modifier.classNames(size.value)
81 | )
82 | .thenIf(
83 | condition = validation.isValid,
84 | other = Modifier.classNames("is-valid")
85 | )
86 | .thenIf(
87 | condition = validation.isInvalid,
88 | other = Modifier.classNames("is-invalid")
89 | )
90 | .classNames("form-control")
91 | .toAttrs {
92 | value(value)
93 | onInput { onValueChange(it.value) }
94 | if (!placeholder.isNullOrEmpty()) attr("placeholder", placeholder)
95 | if (readOnly) readOnly()
96 | if (disabled) disabled()
97 | if (required) required()
98 | }
99 | )
100 | if (validation.isValid) {
101 | Div(
102 | attrs = Modifier
103 | .classNames("valid-feedback")
104 | .toAttrs()
105 | ) {
106 | Text(value = validation.validFeedback)
107 | }
108 | }
109 | if (validation.isInvalid) {
110 | Div(
111 | attrs = Modifier
112 | .classNames("invalid-feedback")
113 | .toAttrs()
114 | ) {
115 | Text(value = validation.invalidFeedback)
116 | }
117 | }
118 | if (floating && label != null) {
119 | Label(forId = randomId) {
120 | Text(value = label)
121 | }
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/AccordionItem.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | data class AccordionItem(
6 | val title: String,
7 | val content: @Composable () -> Unit,
8 | val defaultOpened: Boolean = false
9 | )
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/AlertIcon.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class AlertIcon(
4 | val classes: String,
5 | val label: String,
6 | val path: String
7 | ) {
8 | Info(
9 | classes = "bi-info-fill",
10 | label = "Info:",
11 | path = "M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"
12 | ),
13 | Warning(
14 | classes = "bi-exclamation-triangle-fill",
15 | label = "Warning:",
16 | path = "M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"
17 | ),
18 | Checkmark(
19 | classes = "bi-check-circle-fill",
20 | label = "Success:",
21 | path = "M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"
22 | )
23 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/AlertStyle.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class AlertStyle(val classes: List) {
4 | Primary(classes = listOf("alert", "alert-primary")),
5 | Secondary(classes = listOf("alert", "alert-secondary")),
6 | Success(classes = listOf("alert", "alert-success")),
7 | Danger(classes = listOf("alert", "alert-danger")),
8 | Warning(classes = listOf("alert", "alert-warning")),
9 | Info(classes = listOf("alert", "alert-info")),
10 | Light(classes = listOf("alert", "alert-light")),
11 | Dark(classes = listOf("alert", "alert-dark"))
12 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/BSBorderRadius.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | import com.varabyte.kobweb.compose.css.CSSLengthOrPercentageNumericValue
4 | import org.jetbrains.compose.web.css.px
5 |
6 | data class BSBorderRadius(
7 | val topLeft: CSSLengthOrPercentageNumericValue = 0.px,
8 | val topRight: CSSLengthOrPercentageNumericValue = 0.px,
9 | val bottomRight: CSSLengthOrPercentageNumericValue = 0.px,
10 | val bottomLeft: CSSLengthOrPercentageNumericValue = 0.px
11 | ) {
12 | constructor(all: CSSLengthOrPercentageNumericValue = 0.px) : this(
13 | topLeft = all,
14 | topRight = all,
15 | bottomRight = all,
16 | bottomLeft = all
17 | )
18 |
19 | val value = "$topLeft $topRight $bottomRight $bottomLeft"
20 | }
21 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/BackgroundStyle.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class BackgroundStyle(val value: String) {
4 | Primary(value = "bg-primary"),
5 | Secondary(value = "bg-secondary"),
6 | Success(value = "bg-success"),
7 | Danger(value = "bg-danger"),
8 | Warning(value = "bg-warning"),
9 | Info(value = "bg-info"),
10 | Light(value = "bg-light"),
11 | Dark(value = "bg-dark")
12 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/BadgeVariant.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class BadgeVariant(val classes: List? = null) {
4 | Regular,
5 | Straight,
6 | Rounded(listOf("rounded-pill")),
7 | Empty(listOf("border", "border-light", "rounded-circle", "p-2"))
8 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/BorderColor.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class BorderColor(val value: String) {
4 | Primary(value = "border-primary"),
5 | Secondary(value = "border-secondary"),
6 | Success(value = "border-success"),
7 | Danger(value = "border-danger"),
8 | Warning(value = "border-warning"),
9 | Info(value = "border-info"),
10 | Dark(value = "border-dark"),
11 | Light(value = "border-light"),
12 | White(value = "border-white")
13 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/BreadcrumbItem.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | data class BreadcrumbItem(
4 | val text: String,
5 | val href: String
6 | )
7 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/CarouselItem.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | data class CarouselItem(
4 | val image: String,
5 | val title: String? = null,
6 | val body: String? = null
7 | )
8 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/DropdownDirection.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class DropdownDirection(val value: String? = null) {
4 | Bottom,
5 | Top(value = "dropup"),
6 | Left(value = "dropstart"),
7 | Right(value = "dropend")
8 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/InputSize.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class InputSize(val value: String) {
4 | Default(value = ""),
5 | Small(value = "form-control-sm"),
6 | Large(value = "form-control-lg"),
7 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/InputValidation.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | data class InputValidation(
4 | val isValid: Boolean = false,
5 | val isInvalid: Boolean = false,
6 | val validFeedback: String = "Correct!",
7 | val invalidFeedback: String = "Incorrect!"
8 | )
9 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/ModalSize.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class ModalSize(val value: String) {
4 | None(value = ""),
5 | Small(value = "modal-sm"),
6 | Large(value = "modal-lg"),
7 | ExtraLarge(value = "modal-xl")
8 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/PaginationButtons.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | data class PreviousButton(
4 | val text: String = "Previous",
5 | val disabled: Boolean = false,
6 | val onClick: (Int) -> Unit
7 | )
8 |
9 | data class NextButton(
10 | val text: String = "Next",
11 | val disabled: Boolean = false,
12 | val onClick: (Int) -> Unit
13 | )
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/PaginationSize.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class PaginationSize(val classes: List) {
4 | Default(classes = listOf("pagination")),
5 | Small(classes = listOf("pagination", "pagination-sm")),
6 | Large(classes = listOf("pagination", "pagination-lg"))
7 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/SelectSize.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class SelectSize(val value: String) {
4 | Default(value = ""),
5 | Small(value = "form-select-sm"),
6 | Large(value = "form-select-lg"),
7 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/SpinnerStyle.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class SpinnerStyle(val value: String) {
4 | Primary(value = "text-primary"),
5 | Secondary(value = "text-secondary"),
6 | Success(value = "text-success"),
7 | Danger(value = "text-danger"),
8 | Warning(value = "text-warning"),
9 | Info(value = "text-info"),
10 | Light(value = "text-light"),
11 | Dark(value = "text-dark")
12 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/SpinnerVariant.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | sealed class SpinnerVariant(val classes: List = listOf()) {
4 | object Default : SpinnerVariant(listOf("spinner-border"))
5 | object DefaultGrow : SpinnerVariant(listOf("spinner-grow"))
6 |
7 | object Small : SpinnerVariant(listOf("spinner-border", "spinner-border-sm"))
8 | object SmallGrow : SpinnerVariant(listOf("spinner-grow", "spinner-grow-sm"))
9 |
10 | object Large : SpinnerVariant(listOf("spinner-border", "spinner-border-lg"))
11 | object LargeGrow : SpinnerVariant(listOf("spinner-grow", "spinner-grow-lg"))
12 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/ToastPlacement.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class ToastPlacement(val classes: List) {
4 | TopLeft(classes = listOf("top-0", "start-0")),
5 | TopCenter(classes = listOf("top-0", "start-50")),
6 | TopRight(classes = listOf("top-0", "end-0")),
7 | CenterLeft(classes = listOf("top-50", "start-0", "translate-middle-y")),
8 | Center(classes = listOf("top-50", "start-50", "translate-middle")),
9 | CenterRight(classes = listOf("top-50", "end-0", "translate-middle-y")),
10 | BottomLeft(classes = listOf("bottom-0", "start-0")),
11 | BottomCenter(classes = listOf("bottom-0", "start-50", "translate-middle-x")),
12 | BottomRight(classes = listOf("bottom-0", "end-0"))
13 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/ToastStyle.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class ToastStyle(val value: String) {
4 | Primary(value = "text-bg-primary"),
5 | Secondary(value = "text-bg-secondary"),
6 | Success(value = "text-bg-success"),
7 | Danger(value = "text-bg-danger"),
8 | Warning(value = "text-bg-warning"),
9 | Info(value = "text-bg-info"),
10 | Light(value = "text-bg-light"),
11 | Dark(value = "text-bg-dark"),
12 | Link(value = "text-bg-link")
13 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/TooltipDirection.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models
2 |
3 | enum class TooltipDirection(val value: String) {
4 | Top(value = "top"),
5 | Bottom(value = "bottom"),
6 | Left(value = "left"),
7 | Right(value = "right")
8 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonBadge.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | import com.stevdza.san.kotlinbs.models.BackgroundStyle
4 | import com.stevdza.san.kotlinbs.models.BadgeVariant
5 | import com.varabyte.kobweb.compose.ui.Modifier
6 |
7 | data class ButtonBadge(
8 | val modifier: Modifier = Modifier,
9 | val text: String,
10 | val style: BackgroundStyle = BackgroundStyle.Danger,
11 | val variant: BadgeVariant = BadgeVariant.Rounded
12 | )
13 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonCustomization.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | import com.stevdza.san.kotlinbs.models.BSBorderRadius
4 | import com.varabyte.kobweb.compose.css.FontWeight
5 | import com.varabyte.kobweb.compose.css.functions.Gradient
6 | import org.jetbrains.compose.web.css.CSSColorValue
7 | import org.jetbrains.compose.web.css.CSSNumeric
8 | import org.jetbrains.compose.web.css.cssRem
9 | import org.jetbrains.compose.web.css.px
10 |
11 | data class ButtonCustomization(
12 | val color: CSSColorValue? = null,
13 | val backgroundColor: CSSColorValue? = null,
14 | val borderColor: CSSColorValue? = null,
15 | val hoverColor: CSSColorValue? = null,
16 | val hoverBackgroundColor: CSSColorValue? = null,
17 | val hoverBorderColor: CSSColorValue? = null,
18 | val activeColor: CSSColorValue? = null,
19 | val activeBackgroundColor: CSSColorValue? = null,
20 | val activeBorderColor: CSSColorValue? = null,
21 | val disabledColor: CSSColorValue? = null,
22 | val disabledBackgroundColor: CSSColorValue? = null,
23 | val disabledBorderColor: CSSColorValue? = null,
24 |
25 | val borderRadius: BSBorderRadius = BSBorderRadius(all = 0.px),
26 | val horizontalPadding: CSSNumeric = 0.75.cssRem,
27 | val verticalPadding: CSSNumeric = 0.375.cssRem,
28 |
29 | val fontFamily: String? = null,
30 | val fontSize: CSSNumeric = 1.cssRem,
31 | val fontWeight: FontWeight = FontWeight.Normal,
32 | val lineHeight: CSSNumeric = 1.5.cssRem,
33 |
34 | val gradient: Gradient? = null
35 | )
36 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonProperty.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | object ButtonProperty {
4 | const val COLOR = "--bs-btn-color"
5 | const val BACKGROUND_COLOR = "--bs-btn-bg"
6 | const val BORDER_COLOR = "--bs-btn-border-color"
7 | const val HOVER_COLOR = "--bs-btn-hover-color"
8 | const val HOVER_BACKGROUND_COLOR = "--bs-btn-hover-bg"
9 | const val HOVER_BORDER_COLOR = "--bs-btn-hover-border-color"
10 | const val ACTIVE_COLOR = "--bs-btn-active-color"
11 | const val ACTIVE_BACKGROUND_COLOR = "--bs-btn-active-bg"
12 | const val ACTIVE_BORDER_COLOR = "--bs-btn-active-border-color"
13 | const val DISABLED_COLOR = "--bs-btn-disabled-color"
14 | const val DISABLED_BACKGROUND_COLOR = "--bs-btn-disabled-bg"
15 | const val DISABLED_BORDER_COLOR = "--bs-btn-disabled-border-color"
16 |
17 | const val BORDER_RADIUS = "--bs-btn-border-radius"
18 | const val HORIZONTAL_PADDING = "--bs-btn-padding-x"
19 | const val VERTICAL_PADDING = "--bs-btn-padding-y"
20 |
21 | const val FONT_FAMILY = "--bs-btn-font-family"
22 | const val FONT_SIZE = "--bs-btn-font-size"
23 | const val FONT_WEIGHT = "--bs-btn-font-weight"
24 | const val LINE_HEIGHT = "--bs-btn-line-height"
25 |
26 | const val GRADIENT = "background"
27 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonSize.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | enum class ButtonSize(val value: String) {
4 | Default(value = "btn"),
5 | Small(value = "btn-sm"),
6 | Large(value = "btn-lg")
7 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonType.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | enum class ButtonType(val value: String) {
4 | Button(value = "button"),
5 | Submit(value = "submit"),
6 | Reset(value = "reset")
7 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/button/ButtonVariant.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.button
2 |
3 | enum class ButtonVariant(val classes: List) {
4 | Primary(classes = listOf("btn", "btn-primary")),
5 | PrimaryOutline(classes = listOf("btn", "btn-outline-primary")),
6 | Secondary(classes = listOf("btn", "btn-secondary")),
7 | SecondaryOutline(classes = listOf("btn", "btn-outline-secondary")),
8 | Success(classes = listOf("btn", "btn-success")),
9 | SuccessOutline(classes = listOf("btn", "btn-outline-success")),
10 | Danger(classes = listOf("btn", "btn-danger")),
11 | DangerOutline(classes = listOf("btn", "btn-outline-danger")),
12 | Warning(classes = listOf("btn", "btn-warning")),
13 | WarningOutline(classes = listOf("btn", "btn-outline-warning")),
14 | Info(classes = listOf("btn", "btn-info")),
15 | InfoOutline(classes = listOf("btn", "btn-outline-info")),
16 | Light(classes = listOf("btn", "btn-light")),
17 | LightOutline(classes = listOf("btn", "btn-outline-light")),
18 | Dark(classes = listOf("btn", "btn-dark")),
19 | DarkOutline(classes = listOf("btn", "btn-outline-dark")),
20 | Link(classes = listOf("btn", "btn-link")),
21 | LinkOutline(classes = listOf("btn", "btn-outline-link")),
22 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavBarBrand.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | import org.jetbrains.compose.web.css.CSSSizeValue
4 | import org.jetbrains.compose.web.css.CSSUnit
5 | import org.jetbrains.compose.web.css.px
6 |
7 | data class NavBarBrand(
8 | val title: String,
9 | val image: String? = null,
10 | val imageWidth: CSSSizeValue = 30.px,
11 | val href: String
12 | )
13 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavBarButton.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | import com.stevdza.san.kotlinbs.models.button.ButtonBadge
4 | import com.stevdza.san.kotlinbs.models.button.ButtonVariant
5 |
6 | data class NavBarButton(
7 | val id: String? = null,
8 | val text: String,
9 | val variant: ButtonVariant = ButtonVariant.Primary,
10 | val disabled: Boolean = false,
11 | val loading: Boolean = false,
12 | val loadingText: String? = null,
13 | val badge: ButtonBadge? = null,
14 | val onClick: () -> Unit
15 | )
16 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavBarExpand.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | enum class NavBarExpand(val value: String) {
4 | SM(value = "navbar-expand-sm"),
5 | MD(value = "navbar-expand-md"),
6 | LG(value = "navbar-expand-lg"),
7 | XL(value = "navbar-expand-xl"),
8 | XXL(value = "navbar-expand-xxl")
9 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavBarInputField.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | data class NavBarInputField(
4 | val id: String? = null,
5 | val placeholder: String,
6 | val value: String,
7 | val onValueChange: (String) -> Unit,
8 | val onEnterClick: (() -> Unit)? = null,
9 | )
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavBarOffcanvas.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | import com.stevdza.san.kotlinbs.models.offcanvas.OffcanvasPlacement
4 |
5 | data class NavBarOffcanvas(
6 | val id: String,
7 | val title: String,
8 | val dark: Boolean = false,
9 | val allowScrolling: Boolean = false,
10 | val disableBackdrop: Boolean = false,
11 | val closableOutside: Boolean = true,
12 | val placement: OffcanvasPlacement = OffcanvasPlacement.END
13 | )
14 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavDropdownItem.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | data class NavDropdown(
4 | val placeholder: String,
5 | val items: List
6 | ): NavItem
7 |
8 | data class NavDropdownItem(
9 | val id: String,
10 | val title: String,
11 | val onClick: (Int) -> Unit
12 | )
13 |
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavItem.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | interface NavItem
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/navbar/NavLink.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.navbar
2 |
3 | data class NavLink(
4 | val id: String,
5 | val title: String,
6 | val active: Boolean = false,
7 | val disabled: Boolean = false,
8 | val onClick: (Int) -> Unit
9 | ): NavItem
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/offcanvas/OffcanvasBreakpoint.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.offcanvas
2 |
3 | enum class OffcanvasBreakpoint(val value: String) {
4 | NONE(value = "offcanvas"),
5 | SM(value = "offcanvas-sm"),
6 | MD(value = "offcanvas-md"),
7 | LG(value = "offcanvas-lg"),
8 | XL(value = "offcanvas-xl"),
9 | XXL(value = "offcanvas-xxl"),
10 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/models/offcanvas/OffcanvasPlacement.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.models.offcanvas
2 |
3 | enum class OffcanvasPlacement(val value: String) {
4 | TOP(value = "offcanvas-top"),
5 | BOTTOM(value = "offcanvas-bottom"),
6 | START(value = "offcanvas-start"),
7 | END(value = "offcanvas-end")
8 | }
--------------------------------------------------------------------------------
/bootstrap/src/jsMain/kotlin/com/stevdza/san/kotlinbs/util/UniqueIdGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.util
2 |
3 | object UniqueIdGenerator {
4 | private val componentIds = mutableSetOf()
5 |
6 | fun generateUniqueId(baseName: String): String {
7 | var id = baseName
8 | var counter = 1
9 |
10 | while (componentIds.contains(id)) {
11 | id = "$baseName${counter++}"
12 | }
13 |
14 | componentIds.add(id)
15 | return id
16 | }
17 |
18 | fun removeUniqueId(id: String) {
19 | componentIds.remove(id)
20 | }
21 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.kotlin.multiplatform) apply false
3 | alias(libs.plugins.kobweb.application) apply false
4 | alias(libs.plugins.kobweb.library) apply false
5 | alias(libs.plugins.kobwebx.markdown) apply false
6 | alias(libs.plugins.kotlin.compose) apply false
7 | }
8 |
9 | subprojects {
10 | repositories {
11 | mavenCentral()
12 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
13 | google()
14 | maven("https://us-central1-maven.pkg.dev/varabyte-repos/public")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | jetbrains-compose = "1.7.3"
3 | kobweb = "0.21.1"
4 | kotlin = "2.1.20"
5 |
6 | [libraries]
7 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core ", version.ref = "kobweb" }
8 | kobweb-compose = { module = "com.varabyte.kobweb:kobweb-compose", version.ref = "kobweb" }
9 | kobweb-silk-core = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" }
10 | kobweb-silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" }
11 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" }
12 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" }
13 |
14 | [plugins]
15 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" }
16 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" }
17 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
18 | kobweb-library = { id = "com.varabyte.kobweb.library", version.ref = "kobweb" }
19 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MSYS* | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
3 | install:
4 | - echo "Building only the bootstrap library"
5 | - chmod +x gradlew
6 | - ./gradlew :bootstrap:clean :bootstrap:publishToMavenLocal
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
7 | maven("https://us-central1-maven.pkg.dev/varabyte-repos/public")
8 | }
9 | }
10 |
11 | rootProject.name = "kotlinbs"
12 |
13 | include(":bootstrap")
14 | include(":site")
--------------------------------------------------------------------------------
/site/.gitignore:
--------------------------------------------------------------------------------
1 | # Kobweb ignores
2 | .kobweb/*
3 | !.kobweb/conf.yaml
4 |
--------------------------------------------------------------------------------
/site/.kobweb/conf.yaml:
--------------------------------------------------------------------------------
1 | site:
2 | title: "KotlinBootstrap"
3 |
4 | server:
5 | files:
6 | dev:
7 | contentRoot: "build/processedResources/js/main/public"
8 | script: "build/kotlin-webpack/js/developmentExecutable/kotlinbs.js"
9 | prod:
10 | script: "build/kotlin-webpack/js/productionExecutable/kotlinbs.js"
11 | siteRoot: ".kobweb/site"
12 |
13 | port: 8080
--------------------------------------------------------------------------------
/site/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication
2 |
3 | plugins {
4 | alias(libs.plugins.kotlin.multiplatform)
5 | alias(libs.plugins.kobweb.application)
6 | alias(libs.plugins.kotlin.compose)
7 | }
8 |
9 | group = "com.stevdza.san.kotlinbs"
10 | version = "1.0.0"
11 |
12 | kobweb {
13 | app {
14 | index {
15 | description.set("Powered by Kobweb")
16 | }
17 | }
18 | }
19 |
20 | kotlin {
21 | configAsKobwebApplication("kotlinbs")
22 |
23 | sourceSets {
24 | commonMain.dependencies {
25 | implementation(libs.compose.runtime)
26 | }
27 |
28 | jsMain.dependencies {
29 | implementation(libs.compose.html.core)
30 | implementation(libs.kobweb.core)
31 | implementation(libs.kobweb.silk.core)
32 | implementation(libs.kobweb.silk.icons.fa)
33 | implementation(project(":bootstrap"))
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/site/src/jsMain/kotlin/com/stevdza/san/kotlinbs/MyApp.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs
2 |
3 | import androidx.compose.runtime.*
4 | import com.varabyte.kobweb.compose.ui.modifiers.*
5 | import com.varabyte.kobweb.core.App
6 | import com.varabyte.kobweb.silk.init.InitSilk
7 | import com.varabyte.kobweb.silk.init.InitSilkContext
8 | import com.varabyte.kobweb.silk.SilkApp
9 | import com.varabyte.kobweb.silk.components.layout.Surface
10 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle
11 | import com.varabyte.kobweb.silk.style.toModifier
12 |
13 | import org.jetbrains.compose.web.css.*
14 |
15 | @InitSilk
16 | fun updateTheme(ctx: InitSilkContext) {
17 | // Configure silk here
18 | }
19 |
20 | @App
21 | @Composable
22 | fun MyApp(content: @Composable () -> Unit) {
23 | SilkApp {
24 | Surface(SmoothColorStyle.toModifier().minHeight(100.vh)) {
25 | content()
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/site/src/jsMain/kotlin/com/stevdza/san/kotlinbs/pages/Index.kt:
--------------------------------------------------------------------------------
1 | package com.stevdza.san.kotlinbs.pages
2 |
3 | import androidx.compose.runtime.*
4 | import com.stevdza.san.kotlinbs.forms.BSSwitch
5 | import com.varabyte.kobweb.compose.foundation.layout.Column
6 | import com.varabyte.kobweb.core.Page
7 |
8 | @Page
9 | @Composable
10 | fun HomePage() {
11 | Column {
12 | var isChecked by remember { mutableStateOf(false) }
13 |
14 | BSSwitch(
15 | label = "Example Switch",
16 | checked = isChecked,
17 | onClick = {
18 | isChecked = it
19 | }
20 | )
21 | }
22 | }
--------------------------------------------------------------------------------
/site/src/jsMain/resources/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevdza-san/KotlinBootstrap/6420d3d92f7cd4d343c02809790f261f403c6bdd/site/src/jsMain/resources/public/favicon.ico
--------------------------------------------------------------------------------