2023-07-18 16:46:44 +02:00
|
|
|
|
;; The Bootstrap example 'Album' demonstrates how to create a responsive,
|
|
|
|
|
;; mobile-first photo album layout using Bootstrap's grid system, cards, and
|
|
|
|
|
;; some other components. Here's a brief overview of its structure:
|
|
|
|
|
|
|
|
|
|
;; Navigation Bar: At the top, there's a navbar with the site name and some
|
|
|
|
|
;; links. The navbar collapses into a hamburger menu on smaller screens for
|
|
|
|
|
;; better mobile usability.
|
|
|
|
|
|
|
|
|
|
;; Hero Unit: The main headline or 'Hero unit' is a large callout with some
|
|
|
|
|
;; text and a call-to-action button. It's a marketing section of the page that
|
|
|
|
|
;; offers users a brief overview of the product's main feature.
|
|
|
|
|
|
|
|
|
|
;; Photo Album: The main content of the page is a photo album. This is
|
|
|
|
|
;; implemented as a responsive grid of card components. Each card contains an
|
|
|
|
|
;; image, some text, and a button. The grid adjusts from a single column on the
|
|
|
|
|
;; smallest screens to multi-column layouts on larger screens.
|
|
|
|
|
|
|
|
|
|
;; Footer: Finally, there's a footer at the bottom of the page with some links
|
|
|
|
|
;; and copyright information.
|
|
|
|
|
|
|
|
|
|
;; The Album example is a good demonstration of how Bootstrap can be used to
|
|
|
|
|
;; create a responsive, mobile-first layout with relatively little code. It
|
|
|
|
|
;; shows how to use some of the most common Bootstrap components, like navbars,
|
|
|
|
|
;; cards, and the grid system, and it provides a solid basis for further
|
|
|
|
|
;; customization and extension.
|
|
|
|
|
|
|
|
|
|
;; It's worth noting that while the layout is quite simple, it can be used as a
|
|
|
|
|
;; starting point for more complex projects. By understanding and working
|
|
|
|
|
;; through this example, one can get a grasp of Bootstrap's conventions and
|
|
|
|
|
;; learn how to use its components effectively.
|
|
|
|
|
|
2023-07-17 21:50:42 +02:00
|
|
|
|
(defpackage cl-sbt/album
|
2023-07-15 21:09:40 +02:00
|
|
|
|
(:use :cl)
|
2023-07-17 21:50:42 +02:00
|
|
|
|
(:import-from :cl-sbt/grid :con :row :col)
|
2023-07-20 17:22:23 +02:00
|
|
|
|
(:import-from :cl-sbt/btn :btn-primary)
|
2023-07-18 19:58:32 +02:00
|
|
|
|
(:import-from :cl-sbt/navbar :navbar :brand :toggler :collapsible)
|
2023-07-19 17:49:35 +02:00
|
|
|
|
(:import-from :cl-sbt/utility :color)
|
2023-07-16 13:58:34 +02:00
|
|
|
|
(:import-from :cl-sbt :write-string-to-file :with-page)
|
2023-07-16 11:54:23 +02:00
|
|
|
|
(:import-from :spinneret :with-html-string)
|
2023-07-15 21:09:40 +02:00
|
|
|
|
(:export
|
2023-07-16 14:10:31 +02:00
|
|
|
|
:contact
|
|
|
|
|
:about
|
|
|
|
|
:header
|
|
|
|
|
:footer
|
2023-07-18 16:46:44 +02:00
|
|
|
|
:album
|
2023-07-19 17:49:35 +02:00
|
|
|
|
:page
|
|
|
|
|
:write-page
|
2023-07-16 14:10:31 +02:00
|
|
|
|
:*navbar-header-id*)
|
|
|
|
|
(:documentation "The `cl-sbt-album` package provides macros for building an
|
|
|
|
|
album-style website page using Bootstrap and Spinneret."))
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
2023-07-17 21:50:42 +02:00
|
|
|
|
(in-package :cl-sbt/album)
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
2023-07-15 22:36:49 +02:00
|
|
|
|
(defvar *navbar-header-id* "navbarHeader")
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
2023-07-23 14:10:31 +02:00
|
|
|
|
(defmacro about ((&key (color '(:body :secondary))) &body body)
|
2023-07-16 14:10:31 +02:00
|
|
|
|
"Generates an HTML 'About' section with the provided content.
|
|
|
|
|
|
2023-07-18 20:14:10 +02:00
|
|
|
|
COLOR: Specifies the color scheme of the text.
|
2023-07-16 14:10:31 +02:00
|
|
|
|
|
|
|
|
|
BODY: Specifies the HTML content to be included in the 'About' section. This
|
|
|
|
|
can be any valid HTML content that spinneret:with-html can parse.
|
|
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
(about () \"This is an about section.\")
|
|
|
|
|
; This will generate an 'About' section with secondary color text and the
|
|
|
|
|
; provided content."
|
2023-07-15 21:09:40 +02:00
|
|
|
|
`(spinneret:with-html
|
2023-07-20 08:03:14 +02:00
|
|
|
|
(:h4 "About")
|
2023-07-21 09:39:56 +02:00
|
|
|
|
(:p :class ,(apply #'cl-sbt/utility:color color)
|
2023-07-20 08:03:14 +02:00
|
|
|
|
,@body)))
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
|
|
|
|
(defmacro contact (&rest rest)
|
2023-07-16 14:10:31 +02:00
|
|
|
|
"Generates an HTML 'Contact' section with the provided links.
|
|
|
|
|
|
|
|
|
|
REST: A list of plists, each representing a link. Each plist should contain a
|
|
|
|
|
:url and a :label.
|
|
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
(contact (:url \"#\" :label \"Follow on Twitter\")
|
|
|
|
|
(:url \"#\" :label \"Like on Facebook\")
|
|
|
|
|
(:url \"#\" :label \"Email me\"))
|
|
|
|
|
; This will generate a 'Contact' section with three links, each with the
|
|
|
|
|
; provided URL and label."
|
2023-07-15 21:09:40 +02:00
|
|
|
|
`(spinneret:with-html
|
|
|
|
|
(:h4 "Contact")
|
|
|
|
|
(:ul :class "list-unstyled"
|
|
|
|
|
,@(loop for item in rest
|
|
|
|
|
collect (destructuring-bind (&key url label) item
|
2023-07-18 20:21:59 +02:00
|
|
|
|
`(:li (:a :class ,(cl-sbt/utility:color :text :white)
|
2023-07-15 21:09:40 +02:00
|
|
|
|
:href ,url
|
|
|
|
|
,label)))))))
|
|
|
|
|
|
2023-07-18 16:56:00 +02:00
|
|
|
|
(defmacro navigation (&body body)
|
2023-07-16 13:58:34 +02:00
|
|
|
|
"Generates an HTML header for the album page.
|
|
|
|
|
|
|
|
|
|
This macro generates a header with a navigation bar and some predefined
|
|
|
|
|
content, including an 'About' section with a description of the album and a
|
|
|
|
|
'Contact' section with some dummy contact links.
|
|
|
|
|
|
|
|
|
|
Additional content can be added to the header by passing it as BODY arguments
|
|
|
|
|
to the macro. The BODY content will be included in the header after the
|
|
|
|
|
predefined content."
|
2023-07-15 21:09:40 +02:00
|
|
|
|
`(spinneret:with-html
|
2023-07-20 21:26:14 +02:00
|
|
|
|
(:header :data-bs-theme "dark"
|
|
|
|
|
(collapsible *navbar-header-id* (:color (:text "bg-dark"))
|
|
|
|
|
,@body)
|
|
|
|
|
(navbar (:fluid nil :dark t)
|
2023-07-16 13:58:34 +02:00
|
|
|
|
(brand () "Album")
|
2023-07-19 10:16:55 +02:00
|
|
|
|
(toggler *navbar-header-id*)))))
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
2023-07-26 16:15:43 +02:00
|
|
|
|
(defmacro footer ((&key (color '(:body "secondary")) (spacing '(:property "p" :size 5))) &rest rest)
|
2023-07-16 10:28:22 +02:00
|
|
|
|
"Generates an HTML footer with Bootstrap classes.
|
|
|
|
|
|
2023-07-17 21:50:42 +02:00
|
|
|
|
COLOR: Specifies the color scheme of the footer. It's a list containing keyword
|
2023-07-20 20:05:04 +02:00
|
|
|
|
arguments that can be passed to the cl-sbt/utility:color function. Default is
|
|
|
|
|
'(:text :body-secondary), setting the text color to secondary.
|
2023-07-16 10:28:22 +02:00
|
|
|
|
|
|
|
|
|
SPACING: A list specifying the Bootstrap spacing class. The list should contain
|
2023-07-17 21:50:42 +02:00
|
|
|
|
keyword arguments that can be passed to the cl-sbt/utility:spacing function.
|
2023-07-20 20:05:04 +02:00
|
|
|
|
Default is '(:property :p :size 5), setting the padding size to 5.
|
2023-07-16 10:28:22 +02:00
|
|
|
|
|
2023-07-20 20:05:04 +02:00
|
|
|
|
REST: A list of key-value pairs. A recognized key is :copyright which
|
|
|
|
|
sets the copyright notice in the footer. If not provided, it defaults to nil.
|
|
|
|
|
Specifies additional HTML content to be added to the footer. This can be any
|
|
|
|
|
valid HTML content that spinneret:with-html can parse.
|
2023-07-16 11:36:58 +02:00
|
|
|
|
|
2023-07-16 10:28:22 +02:00
|
|
|
|
The footer generated contains fixed content, including a 'Back to top' link and
|
|
|
|
|
a short paragraph about Bootstrap.
|
|
|
|
|
|
|
|
|
|
Example usage:
|
2023-07-17 21:50:42 +02:00
|
|
|
|
(footer (:color (:text :primary :background :light)
|
|
|
|
|
:spacing (:property :p :side :y :size 4))
|
2023-07-20 21:26:14 +02:00
|
|
|
|
:copyright \"Copyright 2023\")
|
2023-07-17 21:50:42 +02:00
|
|
|
|
; This will generate a footer with primary color text and light background
|
2023-07-20 20:05:04 +02:00
|
|
|
|
; with a top/bottom padding of size 4. A copyright notice is set. Additionally,
|
|
|
|
|
; a paragraph with class 'custom-class' and text 'Custom content here' will
|
|
|
|
|
; be added to the footer."
|
2023-07-15 21:09:40 +02:00
|
|
|
|
`(spinneret:with-html
|
2023-07-16 10:28:22 +02:00
|
|
|
|
(:footer :class ,(concatenate 'string
|
2023-07-21 21:36:36 +02:00
|
|
|
|
(apply #'cl-sbt/utility:color color) " "
|
2023-07-21 09:55:33 +02:00
|
|
|
|
(apply #'cl-sbt/utility:spacing spacing))
|
2023-07-20 19:26:10 +02:00
|
|
|
|
(con nil
|
2023-07-20 20:10:06 +02:00
|
|
|
|
,@(destructuring-bind (&key copyright content) rest
|
2023-07-20 19:26:10 +02:00
|
|
|
|
`((:p :class "float-end mb-1"
|
|
|
|
|
(:a :href "#" "Back to top"))
|
|
|
|
|
(:p :class "mb-1"
|
|
|
|
|
,copyright)
|
|
|
|
|
(:p :class "mb-0"
|
|
|
|
|
"New to Bootstrap? "
|
|
|
|
|
(:a :href "/" "Visit the homepage")
|
|
|
|
|
" or read our "
|
2023-07-20 20:10:06 +02:00
|
|
|
|
(:a :href "/docs/5.3/getting-started/introduction/" "getting started guide"))
|
|
|
|
|
,content))))))
|
2023-07-15 21:09:40 +02:00
|
|
|
|
|
2023-07-20 15:21:45 +02:00
|
|
|
|
(defmacro hero (&rest rest)
|
2023-07-20 17:22:09 +02:00
|
|
|
|
"Generates a Bootstrap hero unit.
|
|
|
|
|
|
|
|
|
|
REST: A list of key-value pairs. The keys can be:
|
|
|
|
|
:title - The title of the hero unit.
|
|
|
|
|
:lead - The leading text of the hero unit.
|
|
|
|
|
:cta - The text for the call to action button.
|
|
|
|
|
|
|
|
|
|
This macro generates a hero unit with the given title, leading text, and call
|
|
|
|
|
to action button. The generated hero unit uses Bootstrap classes for styling."
|
2023-07-18 16:46:13 +02:00
|
|
|
|
`(spinneret:with-html
|
2023-07-20 15:21:45 +02:00
|
|
|
|
,@(destructuring-bind (&key title lead cta) rest
|
2023-07-20 21:26:14 +02:00
|
|
|
|
`((con nil
|
|
|
|
|
(row nil
|
|
|
|
|
(col nil
|
|
|
|
|
(:h1 :class "fw-light" ,title)
|
|
|
|
|
(lead-p () ,lead)
|
|
|
|
|
(:p (btn-primary ,cta)))))))))
|
2023-07-18 16:46:13 +02:00
|
|
|
|
|
2023-07-19 17:49:14 +02:00
|
|
|
|
(defmacro lead-p ((&key (color '(:text "body-secondary"))) &body body)
|
|
|
|
|
`(spinneret:with-html
|
2023-07-21 09:55:33 +02:00
|
|
|
|
(:p :class ,(concatenate 'string
|
|
|
|
|
"lead "
|
|
|
|
|
(apply #'color color))
|
|
|
|
|
,@body)))
|
2023-07-19 17:49:14 +02:00
|
|
|
|
|
2023-07-20 08:33:03 +02:00
|
|
|
|
(defmacro page (title cdn)
|
2023-07-20 19:26:10 +02:00
|
|
|
|
"Generates a complete HTML page using Bootstrap components.
|
|
|
|
|
|
|
|
|
|
TITLE: The title of the page.
|
|
|
|
|
|
|
|
|
|
CDN: The Content Delivery Network (CDN) link for Bootstrap.
|
|
|
|
|
|
|
|
|
|
This macro generates a page that includes a navigation bar with an 'about'
|
|
|
|
|
section and a 'contact' section, a main section with a hero unit, a leading
|
|
|
|
|
paragraph, and a 'call to action' button, and a footer with copyright info.
|
|
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
(page \"My Page Title\" t)
|
|
|
|
|
; This will generate an HTML page with the title 'My Page Title', and the
|
|
|
|
|
; Bootstrap CSS will be loaded from the provided CDN link.
|
|
|
|
|
|
|
|
|
|
The 'about' section of the navigation bar contains a paragraph of text. The
|
|
|
|
|
'contact' section includes links to Twitter, Facebook, and an email address.
|
|
|
|
|
|
|
|
|
|
The hero unit in the main section includes a title, a lead paragraph, and a
|
|
|
|
|
'call to action' button.
|
|
|
|
|
|
|
|
|
|
The footer contains a copyright notice.
|
|
|
|
|
|
|
|
|
|
All of these components are customizable by providing different arguments
|
|
|
|
|
to the respective component macros."
|
2023-07-20 08:33:03 +02:00
|
|
|
|
`(with-page (:cdn ,cdn :title ,title)
|
2023-07-18 20:09:19 +02:00
|
|
|
|
(navigation
|
|
|
|
|
(col (:breakpoint (:kind :col :sm (8 nil) :md (7 nil))
|
2023-07-26 16:15:43 +02:00
|
|
|
|
:spacing (:property "p" :side "y" :size 4))
|
2023-07-23 14:10:31 +02:00
|
|
|
|
(about ()
|
2023-07-20 19:26:10 +02:00
|
|
|
|
"Add some information about the album below, the author, or any
|
|
|
|
|
other background context. Make it a few sentences long so folks can
|
|
|
|
|
pick up some informative tidbits. Then, link them off to some social
|
|
|
|
|
networking sites or contact information."))
|
2023-07-20 15:21:45 +02:00
|
|
|
|
|
2023-07-20 08:03:14 +02:00
|
|
|
|
(col (:breakpoint (:kind :col :sm (4 nil) :md (nil 1)))
|
2023-07-18 20:09:19 +02:00
|
|
|
|
(contact (:url "#" :label "Follow on Twitter")
|
|
|
|
|
(:url "#" :label "Like on Facebook")
|
|
|
|
|
(:url "#" :label "Email me"))))
|
2023-07-20 15:21:45 +02:00
|
|
|
|
|
2023-07-20 17:22:34 +02:00
|
|
|
|
(:main (hero :title "Album example"
|
2023-07-20 19:26:10 +02:00
|
|
|
|
:lead "Something short and leading about the collection
|
|
|
|
|
below—its contents, the creator, etc. Make it short and
|
|
|
|
|
sweet, but not too short so folks don’t simply skip over it
|
|
|
|
|
entirely."
|
2023-07-20 17:22:34 +02:00
|
|
|
|
:cta "Main call to action"))
|
2023-07-20 15:21:45 +02:00
|
|
|
|
|
2023-07-20 19:26:10 +02:00
|
|
|
|
(footer nil :copyright "Album example is © Bootstrap, but please download and customize it for yourself!")))
|
2023-07-18 16:46:13 +02:00
|
|
|
|
|
2023-07-20 08:03:47 +02:00
|
|
|
|
(defun write-page (filepath &key (lang "de") (style :tree) (fc 120))
|
2023-07-18 16:46:13 +02:00
|
|
|
|
(let ((spinneret:*html-lang* lang)
|
|
|
|
|
(spinneret:*html-style* style)
|
|
|
|
|
(spinneret:*fill-column* fc))
|
2023-07-20 08:03:47 +02:00
|
|
|
|
(write-string-to-file filepath
|
2023-07-20 08:33:03 +02:00
|
|
|
|
(with-html-string (page "Album" t)))))
|
2023-07-20 08:03:47 +02:00
|
|
|
|
|
2023-07-23 14:10:31 +02:00
|
|
|
|
;(cl-sbt/album:write-page "~/quicklisp/local-projects/cl-sbt/public/examples/album.html")
|