@razerspine/pug-ui-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +57 -0
- package/index.js +3 -0
- package/mixins/btn.pug +103 -0
- package/mixins/data-table.pug +112 -0
- package/mixins/form-input.pug +68 -0
- package/mixins/form-textarea.pug +55 -0
- package/mixins/input-checkbox.pug +50 -0
- package/mixins/input-radio.pug +53 -0
- package/mixins/single-select.pug +92 -0
- package/package.json +25 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Razerspine
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @razerspine/pug-ui-kit
|
|
2
|
+
|
|
3
|
+
A collection of reusable Pug mixins designed for the [Webpack Starter Monorepo](https://github.com/Razerspine/webpack-starter-monorepo).
|
|
4
|
+
|
|
5
|
+
This package centralizes UI components, ensuring consistency across all templates generated by the CLI.
|
|
6
|
+
|
|
7
|
+
## 📦 Installation
|
|
8
|
+
|
|
9
|
+
This package is automatically included in templates generated via the CLI. To install it manually:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @razerspine/pug-ui-kit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 🛠Webpack Configuration
|
|
16
|
+
|
|
17
|
+
To use these mixins efficiently without dealing with complex relative paths (e.g., ../../node_modules/...), configure a Webpack alias in your webpack.config.js:
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const pugMixinsPath = require('@razerspine/pug-ui-kit');
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
// ...
|
|
24
|
+
resolve: {
|
|
25
|
+
alias: {
|
|
26
|
+
// Create an alias 'pug-ui-kit' pointing to the mixins directory
|
|
27
|
+
'pug-ui-kit': pugMixinsPath,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
## 🚀 Usage
|
|
33
|
+
|
|
34
|
+
Once the alias is configured, you can include any mixin in your .pug files using the ~pug-ui-kit/ prefix:
|
|
35
|
+
|
|
36
|
+
### Example: Button (btn.pug)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
include ~pug-ui-kit/btn.pug
|
|
40
|
+
|
|
41
|
+
+btn('Save', 'primary', 'small', {type: 'button'})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 📂 Available Mixins
|
|
45
|
+
|
|
46
|
+
The package currently includes the following components:
|
|
47
|
+
|
|
48
|
+
* btn.pug - renders a configurable button with optional icon and text.
|
|
49
|
+
* data-table.pug - renders a flexible table from an array of objects.
|
|
50
|
+
* form-input.pug - renders a configurable input element with optional label and placeholder.
|
|
51
|
+
* form-textarea.pug - renders a configurable textarea element with optional label and placeholder.
|
|
52
|
+
* input-checkbox.pug - renders a configurable checkbox with label and optional attributes.
|
|
53
|
+
* input-radio.pug — renders a configurable radio input with label for use in radio groups.
|
|
54
|
+
* single-select.pug - renders a configurable select element with label, options, and optional placeholder.
|
|
55
|
+
|
|
56
|
+
## 📄 License
|
|
57
|
+
This project is licensed under the ISC License.
|
package/index.js
ADDED
package/mixins/btn.pug
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//- Button mixin
|
|
2
|
+
//- Renders a configurable button with optional icon and text. Visual appearance
|
|
3
|
+
//- is primarily controlled by the `variant` and `size` parameters and by CSS
|
|
4
|
+
//- classes applied via `attrs` or global styles.
|
|
5
|
+
//-
|
|
6
|
+
//- NOTE: project migrated to an SVG sprite. The mixin references symbols by id
|
|
7
|
+
//- using <use href="#icon-{iconName}"> (xlink:href kept for legacy browsers).
|
|
8
|
+
//- Ensure your sprite contains <symbol id="icon-{name}"> entries.
|
|
9
|
+
//-
|
|
10
|
+
//- Parameters:
|
|
11
|
+
//- text - **String | null** — Visible button text. If `null` or an empty
|
|
12
|
+
//- string, the text node is omitted (icon-only button).
|
|
13
|
+
//- Default: null
|
|
14
|
+
//- variant - **String** — Visual variant modifier appended to `.btn--{variant}`.
|
|
15
|
+
//- Common values: 'primary', 'secondary', 'outline', 'text-primary',
|
|
16
|
+
//- 'text-secondary', 'icon-primary', 'icon-secondary', 'icon-outline', 'icon'.
|
|
17
|
+
//- Default: 'primary'
|
|
18
|
+
//- size - **String** — Size modifier appended to `.btn--{size}` and used
|
|
19
|
+
//- for icon sizing `.icon--{size}`. Common values: 'small', 'medium', 'large'.
|
|
20
|
+
//- Default: 'medium'
|
|
21
|
+
//- attrs - **Object | null** — Attributes to spread onto the `<button>`
|
|
22
|
+
//- element via `&attributes(attrs)`. Use this to pass `type`,
|
|
23
|
+
//- `id`, `aria-*`, `data-*`, etc. If `null`, no attributes are spread.
|
|
24
|
+
//- If `attrs.class` is present it will be merged with the base classes.
|
|
25
|
+
//- Default: { type: 'button' }
|
|
26
|
+
//- iconName - **String | null** — Icon name (without prefix). The mixin uses
|
|
27
|
+
//- `#icon-{iconName}` in <use> to reference the SVG sprite symbol.
|
|
28
|
+
//- If `null`, no icon is rendered.
|
|
29
|
+
//- Default: null
|
|
30
|
+
//-
|
|
31
|
+
//- Accessibility notes:
|
|
32
|
+
//- - If the button has visible text, the SVG is treated as decorative and gets
|
|
33
|
+
//- `aria-hidden="true"` so screen readers ignore it.
|
|
34
|
+
//- - If the button is icon-only (text === null or empty), the mixin will set
|
|
35
|
+
//- `aria-label` on the button if `attrs` does not already provide one. Provide
|
|
36
|
+
//- a meaningful `aria-label` in `attrs` for icon-only buttons.
|
|
37
|
+
//-
|
|
38
|
+
//- Behavior summary:
|
|
39
|
+
//- - Computes `hasText` to decide whether to render the text node.
|
|
40
|
+
//- - Builds base classes and safely merges any `attrs.class` with them.
|
|
41
|
+
//- - Renders <svg><use href="#icon-{iconName}" xlink:href="#icon-{iconName}"></use></svg>
|
|
42
|
+
//- when `iconName` is provided.
|
|
43
|
+
//- - Sets `aria-hidden` on the SVG when text is present; ensures icon-only buttons
|
|
44
|
+
//- have an accessible label.
|
|
45
|
+
//-
|
|
46
|
+
//- Examples (corrected to match mixin signature):
|
|
47
|
+
//- // Primary sizes (text only)
|
|
48
|
+
//- +btn('Save', 'primary', 'small', {type: 'button'})
|
|
49
|
+
//- +btn('Save', 'primary', 'medium', {type: 'button'})
|
|
50
|
+
//- +btn('Save', 'primary', 'large', {type: 'button'})
|
|
51
|
+
//-
|
|
52
|
+
//- // Secondary sizes (text only)
|
|
53
|
+
//- +btn('Cancel', 'secondary', 'small', {type: 'button'})
|
|
54
|
+
//- +btn('Cancel', 'secondary', 'medium', {type: 'button'})
|
|
55
|
+
//- +btn('Cancel', 'secondary', 'large', {type: 'button'})
|
|
56
|
+
//-
|
|
57
|
+
//- // Outline variant (text only)
|
|
58
|
+
//- +btn('More', 'outline', 'small', {type: 'button'})
|
|
59
|
+
//- +btn('More', 'outline', 'medium', {type: 'button'})
|
|
60
|
+
//- +btn('More', 'outline', 'large', {type: 'button'})
|
|
61
|
+
//-
|
|
62
|
+
//- // Button with icon and text
|
|
63
|
+
//- +btn('Settings', 'primary', 'small', {type: 'button'}, 'settings')
|
|
64
|
+
//- +btn('Settings', 'secondary', 'medium', {type: 'button'}, 'settings')
|
|
65
|
+
//- +btn('Settings', 'outline', 'large', {type: 'button'}, 'settings')
|
|
66
|
+
//-
|
|
67
|
+
//- // Text-only style variants
|
|
68
|
+
//- +btn('Learn more', 'text-primary', 'medium', {type: 'button'})
|
|
69
|
+
//- +btn('Dismiss', 'text-secondary', 'medium', {type: 'button'})
|
|
70
|
+
//-
|
|
71
|
+
//- // Icon-only buttons (pass null for text and use an icon-* variant)
|
|
72
|
+
//- // Provide aria-label in attrs or rely on mixin fallback to iconName -> label
|
|
73
|
+
//- +btn(null, 'icon-primary', 'medium', {type: 'button', 'aria-label': 'Settings'}, 'settings')
|
|
74
|
+
//- +btn(null, 'icon-secondary', 'small', {type: 'button', 'aria-label': 'Close'}, 'close')
|
|
75
|
+
//- +btn(null, 'icon-outline', 'large', {type: 'button'}, 'menu')
|
|
76
|
+
//-
|
|
77
|
+
//- Keep this docblock updated when you change parameter names, defaults, or
|
|
78
|
+
//- the CSS variants so editor hover comments remain accurate.
|
|
79
|
+
mixin btn(text, variant='primary', size='medium', attrs={'type':'button'}, iconName=null)
|
|
80
|
+
- const hasText = typeof text === 'string' && text.trim().length > 0;
|
|
81
|
+
- const baseClass = 'btn btn--' + variant + ' btn--' + size;
|
|
82
|
+
- const incomingClass = attrs && attrs.class ? String(attrs.class).trim() : '';
|
|
83
|
+
- const combinedClass = (incomingClass ? incomingClass + ' ' : '') + baseClass;
|
|
84
|
+
- // shallow copy attrs so we don't mutate caller object
|
|
85
|
+
- const safeAttrs = Object.assign({}, attrs || {});
|
|
86
|
+
- // ensure class is present in the attrs we pass to &attributes
|
|
87
|
+
- safeAttrs.class = combinedClass;
|
|
88
|
+
- // accessibility fallback for icon-only buttons
|
|
89
|
+
- if (!hasText && iconName && !(safeAttrs['aria-label'] || safeAttrs['ariaLabel'] || safeAttrs['aria-labelledby'])) {
|
|
90
|
+
- safeAttrs['aria-label'] = iconName.replace(/[-_]/g, ' ');
|
|
91
|
+
- }
|
|
92
|
+
button&attributes(safeAttrs)
|
|
93
|
+
if iconName
|
|
94
|
+
- // build svgAttrs as a plain object without undefined values
|
|
95
|
+
- const svgAttrs = Object.assign(
|
|
96
|
+
- {},
|
|
97
|
+
- hasText ? { 'aria-hidden': 'true' } : { role: 'img' },
|
|
98
|
+
- { class: 'button-icon icon--' + size }
|
|
99
|
+
- );
|
|
100
|
+
svg&attributes(svgAttrs)
|
|
101
|
+
use(xlink:href='#icon-' + iconName, href='#icon-' + iconName)
|
|
102
|
+
if hasText
|
|
103
|
+
| #{text}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//- dataTable mixin
|
|
2
|
+
//- Renders a flexible table from an array of objects. Columns are derived
|
|
3
|
+
//- from the provided `columns` array or automatically collected from object keys.
|
|
4
|
+
//- Supports optional index column, optional actions column (slot), per-column
|
|
5
|
+
//- formatters and custom header labels.
|
|
6
|
+
//-
|
|
7
|
+
//- Parameters:
|
|
8
|
+
//- items - **Array** — Array of objects to render as rows. Each object
|
|
9
|
+
//- represents one row; object keys map to columns. If not an
|
|
10
|
+
//- array, an empty table is rendered.
|
|
11
|
+
//- Example: [{ id:1, name:'Olya' }, { id:2, name:'Ivan' }]
|
|
12
|
+
//- Default: []
|
|
13
|
+
//- columns - **Array | undefined** — Optional ordered list of keys to use
|
|
14
|
+
//- as columns. If omitted or empty, unique keys are collected
|
|
15
|
+
//- from all objects in `items` and used in insertion order.
|
|
16
|
+
//- Example: ['id','name','email']
|
|
17
|
+
//- Default: auto-collected from items
|
|
18
|
+
//- options - **Object | undefined** — Additional options:
|
|
19
|
+
//- - emptyText: **String** — Text shown when no rows; Default: 'No data'.
|
|
20
|
+
//- - showIndex: **Boolean** — Render leading index column; Default: false.
|
|
21
|
+
//- - showActions: **Boolean** — Render trailing Actions column (slot); Default: true.
|
|
22
|
+
//- - formatters: **Object** — Map of column -> function(value) for custom rendering.
|
|
23
|
+
//- Example: { createdAt: v => new Date(v).toLocaleDateString() }.
|
|
24
|
+
//- - labels: **Object** — Map of column -> header label string. If absent,
|
|
25
|
+
//- header is generated by humanizing the key (underscores -> spaces, capitalized).
|
|
26
|
+
//- Default: {}
|
|
27
|
+
//-
|
|
28
|
+
//- Behavior:
|
|
29
|
+
//- - If `items` is empty, a single header cell spanning all columns displays `emptyText`.
|
|
30
|
+
//- - Column order follows `columns` if provided; otherwise it follows the unique keys
|
|
31
|
+
//- discovered across `items`.
|
|
32
|
+
//- - For each cell, `formatters[col]` is used when present; otherwise arrays are joined,
|
|
33
|
+
//- objects are JSON-stringified, and primitives are stringified.
|
|
34
|
+
//- - The Actions column is a slot: when `showActions` is true, the mixin renders a `td`
|
|
35
|
+
//- containing the caller-provided block. Inside the block the current row object is
|
|
36
|
+
//- available as `item` and the row index as `i`.
|
|
37
|
+
//-
|
|
38
|
+
//- Notes and best practices:
|
|
39
|
+
//- - Prefer passing an explicit `columns` array when you need stable column order.
|
|
40
|
+
//- - Use `labels` for human-friendly or localized header text instead of renaming keys.
|
|
41
|
+
//- - Keep `formatters` pure and return safe strings; if returning HTML, use `!=` in the mixin call
|
|
42
|
+
//- or return already-escaped content carefully to avoid XSS.
|
|
43
|
+
//- - To hide the Actions column entirely, set `showActions: false` in `options`.
|
|
44
|
+
//- - For large datasets, consider server-side pagination or client-side slicing before passing `items`.
|
|
45
|
+
//-
|
|
46
|
+
//- Examples:
|
|
47
|
+
//- // Basic usage with auto columns
|
|
48
|
+
//- - const data = [{ id:1, name:'Olya' }, { id:2, name:'Ivan' }]
|
|
49
|
+
//- +dataTable(data)
|
|
50
|
+
//-
|
|
51
|
+
//- // Explicit columns, labels and formatters
|
|
52
|
+
//- +dataTable(data, ['id','name'], {
|
|
53
|
+
//- showIndex: true,
|
|
54
|
+
//- showActions: false,
|
|
55
|
+
//- labels: { id: 'ID', name: 'Full name' },
|
|
56
|
+
//- formatters: { id: v => '#' + v }
|
|
57
|
+
//- })
|
|
58
|
+
//-
|
|
59
|
+
//- // With Actions slot (edit/delete links). Inside block `item` is current row.
|
|
60
|
+
//- +dataTable(data, ['id','name'], { showActions: true })
|
|
61
|
+
//- a(href='/edit/#{item && item.id || ""}') Edit
|
|
62
|
+
//- |
|
|
63
|
+
//- a(href='/delete/#{item && item.id || ""}') Delete
|
|
64
|
+
//-
|
|
65
|
+
//- Keep this docblock updated when you change parameter names, defaults, or
|
|
66
|
+
//- the mixin behavior so editor hover comments remain accurate.
|
|
67
|
+
mixin dataTable(items, columns, options)
|
|
68
|
+
- const _items = Array.isArray(items) ? items : [];
|
|
69
|
+
- const cols = Array.isArray(columns) && columns.length ? columns : Array.from(new Set(_items.reduce((acc, it) => acc.concat(Object.keys(it || {})), [])));
|
|
70
|
+
- const opts = Object.assign({ emptyText: 'No data', showIndex: false, showActions: false, formatters: {}, labels: {} }, options || {});
|
|
71
|
+
|
|
72
|
+
- const humanize = function(key) {
|
|
73
|
+
- if (!key && key !== 0) return '';
|
|
74
|
+
- const s = String(key).replace(/_/g, ' ');
|
|
75
|
+
- return s.charAt(0).toUpperCase() + s.slice(1);
|
|
76
|
+
- }
|
|
77
|
+
|
|
78
|
+
- const formatValue = function(v, col) {
|
|
79
|
+
- if (v === null || v === undefined || v === '') return '';
|
|
80
|
+
- if (opts.formatters && typeof opts.formatters[col] === 'function') return opts.formatters[col](v);
|
|
81
|
+
- if (Array.isArray(v)) return v.join(', ');
|
|
82
|
+
- if (typeof v === 'object') return JSON.stringify(v);
|
|
83
|
+
- return String(v);
|
|
84
|
+
- }
|
|
85
|
+
|
|
86
|
+
if !_items.length
|
|
87
|
+
table.table
|
|
88
|
+
thead
|
|
89
|
+
tr
|
|
90
|
+
th(colspan=cols.length + (opts.showIndex ? 1 : 0) + (opts.showActions ? 1 : 0)) #{opts.emptyText}
|
|
91
|
+
else
|
|
92
|
+
table.table
|
|
93
|
+
thead
|
|
94
|
+
tr
|
|
95
|
+
if opts.showIndex
|
|
96
|
+
th #
|
|
97
|
+
each col in cols
|
|
98
|
+
- const label = (opts.labels && Object.prototype.hasOwnProperty.call(opts.labels, col)) ? opts.labels[col] : humanize(col)
|
|
99
|
+
th #{label}
|
|
100
|
+
if opts.showActions
|
|
101
|
+
th Actions
|
|
102
|
+
tbody
|
|
103
|
+
each item, i in _items
|
|
104
|
+
tr
|
|
105
|
+
if opts.showIndex
|
|
106
|
+
td #{i + 1}
|
|
107
|
+
each col in cols
|
|
108
|
+
- const val = (item && Object.prototype.hasOwnProperty.call(item, col)) ? item[col] : ''
|
|
109
|
+
td!= formatValue(val, col)
|
|
110
|
+
if opts.showActions
|
|
111
|
+
td
|
|
112
|
+
block
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//- Form input mixin
|
|
2
|
+
//- Renders a configurable <input> element with optional label and placeholder.
|
|
3
|
+
//- Supports different input types, custom name, value, and placeholder text.
|
|
4
|
+
//-
|
|
5
|
+
//- Parameters:
|
|
6
|
+
//- type - **String** — Input type attribute.
|
|
7
|
+
//- Example: 'text', 'email', 'password', 'number'.
|
|
8
|
+
//- Default: none (required).
|
|
9
|
+
//- id - **String** — Unique identifier for the <input> element.
|
|
10
|
+
//- Also used by the <label> `for` attribute.
|
|
11
|
+
//- Example: 'username', 'email'.
|
|
12
|
+
//- Default: none (required).
|
|
13
|
+
//- label - **String | null** — Visible label text. If `null` or empty,
|
|
14
|
+
//- no label is rendered.
|
|
15
|
+
//- Example: 'Name', 'Email address'.
|
|
16
|
+
//- Default: null.
|
|
17
|
+
//- placeholder - **String** — Placeholder text displayed inside the input
|
|
18
|
+
//- when empty.
|
|
19
|
+
//- Example: 'Enter your name', 'example@mail.com'.
|
|
20
|
+
//- Default: ''.
|
|
21
|
+
//- name - **String** — Name attribute for form submission.
|
|
22
|
+
//- Example: 'username', 'email'.
|
|
23
|
+
//- Default: ''.
|
|
24
|
+
//- value - **String** — Default value for the input field.
|
|
25
|
+
//- Example: 'John Doe', 'test@mail.com'.
|
|
26
|
+
//- Default: ''.
|
|
27
|
+
//-
|
|
28
|
+
//- Behavior:
|
|
29
|
+
//- - The input always has `.form-control` class for styling.
|
|
30
|
+
//- - The label, if provided, uses `.form-label` class and is linked via `for=id`.
|
|
31
|
+
//- - The placeholder text is shown until the user types content.
|
|
32
|
+
//- - The `name` attribute ensures the input value is submitted with the form.
|
|
33
|
+
//- - The `value` attribute pre-fills the input field if provided.
|
|
34
|
+
//-
|
|
35
|
+
//- Notes about styling and classes:
|
|
36
|
+
//- - Visual appearance is controlled by `.form-control` and `.form-label`
|
|
37
|
+
//- styles in your SCSS.
|
|
38
|
+
//- - To add custom classes or attributes, extend the mixin or wrap it.
|
|
39
|
+
//- - Ensure `id` is unique to avoid duplicate `for`/`id` collisions.
|
|
40
|
+
//-
|
|
41
|
+
//- Examples:
|
|
42
|
+
//- // Basic text input
|
|
43
|
+
//- +formInput('text', 'name', 'Name', 'Enter your name', 'name')
|
|
44
|
+
//-
|
|
45
|
+
//- // Email input with placeholder
|
|
46
|
+
//- +formInput('email', 'email', 'Email', 'example@mail.com', 'email')
|
|
47
|
+
//-
|
|
48
|
+
//- // Password input
|
|
49
|
+
//- +formInput('password', 'password', 'Password', 'Enter password', 'password')
|
|
50
|
+
//-
|
|
51
|
+
//- // Input with default value
|
|
52
|
+
//- +formInput('text', 'username', 'Username', 'Enter username', 'username', 'JohnDoe')
|
|
53
|
+
//-
|
|
54
|
+
//- // Multiple inputs in a form
|
|
55
|
+
//- form.form
|
|
56
|
+
//- +formInput('text', 'first-name', 'First Name', 'Enter first name', 'firstName')
|
|
57
|
+
//- +formInput('text', 'last-name', 'Last Name', 'Enter last name', 'lastName')
|
|
58
|
+
//- +formInput('email', 'email', 'Email', 'example@mail.com', 'email')
|
|
59
|
+
mixin formInput(type, id, label, placeholder='', name='', value='')
|
|
60
|
+
if label
|
|
61
|
+
label.form-label(for=id)= label
|
|
62
|
+
input.form-control(
|
|
63
|
+
type=type,
|
|
64
|
+
id=id,
|
|
65
|
+
name=name,
|
|
66
|
+
value=value,
|
|
67
|
+
placeholder=placeholder
|
|
68
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//- Form textarea mixin
|
|
2
|
+
//- Renders a configurable <textarea> element with optional label and placeholder.
|
|
3
|
+
//- Supports custom name attribute for form submission.
|
|
4
|
+
//-
|
|
5
|
+
//- Parameters:
|
|
6
|
+
//- id - **String** — Unique identifier for the <textarea> element.
|
|
7
|
+
//- Also used by the <label> `for` attribute.
|
|
8
|
+
//- Example: 'message', 'comment'.
|
|
9
|
+
//- Default: none (required).
|
|
10
|
+
//- label - **String | null** — Visible label text. If `null` or empty,
|
|
11
|
+
//- no label is rendered.
|
|
12
|
+
//- Example: 'Message', 'Comment'.
|
|
13
|
+
//- Default: null.
|
|
14
|
+
//- placeholder - **String** — Placeholder text displayed inside the textarea
|
|
15
|
+
//- when empty.
|
|
16
|
+
//- Example: 'Type your message...', 'Enter comment here'.
|
|
17
|
+
//- Default: ''.
|
|
18
|
+
//- name - **String** — Name attribute for form submission.
|
|
19
|
+
//- Example: 'message', 'feedback'.
|
|
20
|
+
//- Default: ''.
|
|
21
|
+
//-
|
|
22
|
+
//- Behavior:
|
|
23
|
+
//- - The textarea always has `.form-textarea` class for styling.
|
|
24
|
+
//- - The label, if provided, uses `.form-label` class and is linked via `for=id`.
|
|
25
|
+
//- - The placeholder text is shown until the user types content.
|
|
26
|
+
//- - The `name` attribute ensures the textarea value is submitted with the form.
|
|
27
|
+
//-
|
|
28
|
+
//- Notes about styling and classes:
|
|
29
|
+
//- - Visual appearance is controlled by `.form-textarea` and `.form-label`
|
|
30
|
+
//- styles in your SCSS.
|
|
31
|
+
//- - To add custom classes or attributes, extend the mixin or wrap it.
|
|
32
|
+
//- - Ensure `id` is unique to avoid duplicate `for`/`id` collisions.
|
|
33
|
+
//-
|
|
34
|
+
//- Examples:
|
|
35
|
+
//- // Basic textarea with label and placeholder
|
|
36
|
+
//- +formTextarea('message', 'Message', 'Type your message...', 'message')
|
|
37
|
+
//-
|
|
38
|
+
//- // Textarea without label
|
|
39
|
+
//- +formTextarea('comment', null, 'Enter comment here', 'comment')
|
|
40
|
+
//-
|
|
41
|
+
//- // Textarea with custom name
|
|
42
|
+
//- +formTextarea('feedback', 'Feedback', 'Write your feedback...', 'feedback')
|
|
43
|
+
//-
|
|
44
|
+
//- // Multiple text areas in a form
|
|
45
|
+
//- form.form
|
|
46
|
+
//- +formTextarea('bio', 'Biography', 'Tell us about yourself', 'bio')
|
|
47
|
+
//- +formTextarea('notes', 'Notes', 'Additional notes...', 'notes')
|
|
48
|
+
mixin formTextarea(id, label, placeholder='', name='')
|
|
49
|
+
if label
|
|
50
|
+
label.form-label(for=id)= label
|
|
51
|
+
textarea.form-textarea(
|
|
52
|
+
id=id,
|
|
53
|
+
name=name,
|
|
54
|
+
placeholder=placeholder
|
|
55
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//- inputCheckbox mixin
|
|
2
|
+
//- Renders a configurable checkbox with label and optional attributes.
|
|
3
|
+
//-
|
|
4
|
+
//- Summary
|
|
5
|
+
//- Renders a checkbox input and its label inside a single inline control.
|
|
6
|
+
//- Uses the project's base input styles so visual appearance is driven by SCSS.
|
|
7
|
+
//-
|
|
8
|
+
//- Parameters
|
|
9
|
+
//- id - **String** — Unique id for the <input>. Also used by <label for>.
|
|
10
|
+
//- Required; example: 'agree'.
|
|
11
|
+
//- label - **String** — Visible label text shown next to the checkbox.
|
|
12
|
+
//- Required; example: 'I agree to terms'.
|
|
13
|
+
//- name - **String** — Name attribute for form submission/grouping.
|
|
14
|
+
//- Optional; default: ''.
|
|
15
|
+
//- value - **String** — Value submitted when checked. Optional; default: 'on'.
|
|
16
|
+
//- checked - **Boolean** — Whether the checkbox is checked by default.
|
|
17
|
+
//- Optional; default: false.
|
|
18
|
+
//-
|
|
19
|
+
//- Behavior
|
|
20
|
+
//- - Wraps input and label in a single inline control element (uses .check-control-label).
|
|
21
|
+
//- - Input uses base input class (.input-base) so theme styles apply consistently.
|
|
22
|
+
//- - Label text is rendered in a sibling element (.input-text) and is clickable.
|
|
23
|
+
//- - If checked is true, the input is rendered with the checked attribute.
|
|
24
|
+
//- - Any attributes passed via attrs are applied to the <input> (useful for disabled, required, aria-*).
|
|
25
|
+
//-
|
|
26
|
+
//- Styling and accessibility
|
|
27
|
+
//- - Visual styling is controlled by .input-base, input[type="checkbox"], .check-control-label and .input-text in SCSS.
|
|
28
|
+
//- - Ensure id is unique to keep label association correct for screen readers.
|
|
29
|
+
//- - Prefer passing ARIA attributes via attrs for additional accessibility.
|
|
30
|
+
//-
|
|
31
|
+
//- Examples
|
|
32
|
+
//- // Basic checkbox
|
|
33
|
+
//- +inputCheckbox('agree', 'I agree to all terms')
|
|
34
|
+
//-
|
|
35
|
+
//- // With name and custom value
|
|
36
|
+
//- +inputCheckbox('subscribe', 'Subscribe', 'newsletter', 'yes')
|
|
37
|
+
//-
|
|
38
|
+
//- // Checked by default and disabled
|
|
39
|
+
//- +inputCheckbox('updates', 'Receive updates', 'updates', 'true', true, {disabled: true})
|
|
40
|
+
//-
|
|
41
|
+
mixin inputCheckbox(id, label, name='', value='on', checked=false)
|
|
42
|
+
label.check-control-label(for=id)
|
|
43
|
+
input.input-base(
|
|
44
|
+
type='checkbox',
|
|
45
|
+
id=id,
|
|
46
|
+
name=name,
|
|
47
|
+
value=value,
|
|
48
|
+
checked=checked,
|
|
49
|
+
)
|
|
50
|
+
span.input-text(data-i18n=label)= label
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//- inputRadio mixin
|
|
2
|
+
//- Renders a configurable radio input with label for use in radio groups.
|
|
3
|
+
//-
|
|
4
|
+
//- Summary
|
|
5
|
+
//- Renders a single radio control paired with a clickable label.
|
|
6
|
+
//- Intended for groups where multiple radios share the same name.
|
|
7
|
+
//-
|
|
8
|
+
//- Parameters
|
|
9
|
+
//- id - **String** — Unique id for the <input>. Also used by <label for>.
|
|
10
|
+
//- Required; example: 'contact-email'.
|
|
11
|
+
//- label - **String** — Visible label text shown next to the radio.
|
|
12
|
+
//- Required; example: 'Email'.
|
|
13
|
+
//- name - **String** — Name attribute to group radios. Required; example: 'contact'.
|
|
14
|
+
//- value - **String** — Value submitted when selected. Required; example: 'email'.
|
|
15
|
+
//- checked - **Boolean** — Whether this radio is selected by default.
|
|
16
|
+
//- Optional; default: false.
|
|
17
|
+
//-
|
|
18
|
+
//- Behavior
|
|
19
|
+
//- - Wraps input and label in a single inline control (.check-control-label).
|
|
20
|
+
//- - Input uses .input-base so theme styles apply consistently.
|
|
21
|
+
//- - Radios with the same name are mutually exclusive; only one can be selected.
|
|
22
|
+
//- - If checked is true, the input is rendered with the checked attribute.
|
|
23
|
+
//- - Any attrs are applied to the <input> (useful for disabled, required, aria-*).
|
|
24
|
+
//-
|
|
25
|
+
//- Styling and accessibility
|
|
26
|
+
//- - Visual styling is controlled by .input-base, input[type="radio"], .check-control-label and .input-text in SCSS.
|
|
27
|
+
//- - Ensure id is unique to keep label association correct for screen readers.
|
|
28
|
+
//-
|
|
29
|
+
//- Examples
|
|
30
|
+
//- // Basic radio group
|
|
31
|
+
//- .form-group
|
|
32
|
+
//- .input-group
|
|
33
|
+
//- span.form-label.w-100 Communication method
|
|
34
|
+
//- +inputRadio('contact-email', 'Email', 'contact', 'email')
|
|
35
|
+
//- +inputRadio('contact-phone', 'Phone', 'contact', 'phone')
|
|
36
|
+
//-
|
|
37
|
+
//- // Vertical radio group
|
|
38
|
+
//- .form-group
|
|
39
|
+
//- .input-group.input-group--vertical
|
|
40
|
+
//- span.form-label.w-100 Gender
|
|
41
|
+
//- +inputRadio('gender-male', 'Male', 'gender', 'male', true)
|
|
42
|
+
//- +inputRadio('gender-female', 'Female', 'gender', 'female')
|
|
43
|
+
//-
|
|
44
|
+
mixin inputRadio(id, label, name, value, checked=false)
|
|
45
|
+
label.check-control-label(for=id)
|
|
46
|
+
input.input-base(
|
|
47
|
+
type='radio',
|
|
48
|
+
id=id,
|
|
49
|
+
name=name,
|
|
50
|
+
value=value,
|
|
51
|
+
checked=checked,
|
|
52
|
+
)
|
|
53
|
+
span.input-text(data-i18n=label)= label
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//- singleSelect mixin
|
|
2
|
+
//- Renders a configurable <select> element with label, options, and optional placeholder.
|
|
3
|
+
//- Supports both arrays of strings and arrays of objects with configurable
|
|
4
|
+
//- keys for label and value. Provides ability to set a default selected option.
|
|
5
|
+
//-
|
|
6
|
+
//- Parameters:
|
|
7
|
+
//- id - **String** — Unique identifier for the <select> element.
|
|
8
|
+
//- Also used as the `name` attribute.
|
|
9
|
+
//- Example: 'topic', 'country'.
|
|
10
|
+
//- Default: none (required).
|
|
11
|
+
//- label - **String | null** — Visible label text. If `null` or empty,
|
|
12
|
+
//- no label is rendered.
|
|
13
|
+
//- Example: 'Topic', 'Choose country'.
|
|
14
|
+
//- Default: null.
|
|
15
|
+
//- options - **Array** — Options to render. Can be:
|
|
16
|
+
//- - Array of strings: ['Support', 'Feedback', 'Other']
|
|
17
|
+
//- - Array of objects: [{value:'support', text:'Support'}, …]
|
|
18
|
+
//- - Array of objects with custom keys (see labelKey/valueKey).
|
|
19
|
+
//- Default: [].
|
|
20
|
+
//- labelKey - **String** — Key name in option objects for display text.
|
|
21
|
+
//- Example: 'text', 'label'.
|
|
22
|
+
//- Default: 'text'.
|
|
23
|
+
//- valueKey - **String** — Key name in option objects for option value.
|
|
24
|
+
//- Example: 'value', 'id'.
|
|
25
|
+
//- Default: 'value'.
|
|
26
|
+
//- selectedValue - **String** — Value of the option that should be selected
|
|
27
|
+
//- by default. If empty, no option is preselected.
|
|
28
|
+
//- Example: 'feedback', '2'.
|
|
29
|
+
//- Default: ''.
|
|
30
|
+
//- placeholder - **String** — Text for a placeholder option. Rendered only
|
|
31
|
+
//- if `label` is null and `selectedValue` is empty.
|
|
32
|
+
//- Example: 'Choose an option', 'Select country'.
|
|
33
|
+
//- Default: 'Choose an option'.
|
|
34
|
+
//-
|
|
35
|
+
//- Behavior:
|
|
36
|
+
//- - If `options` is an array of strings, each string is used as both
|
|
37
|
+
//- the option value and label.
|
|
38
|
+
//- - If `options` is an array of objects, the keys defined by `labelKey`
|
|
39
|
+
//- and `valueKey` are used.
|
|
40
|
+
//- - The option whose value matches `selectedValue` is rendered with
|
|
41
|
+
//- the `selected` attribute.
|
|
42
|
+
//- - If no label is provided and no selectedValue is set, a placeholder
|
|
43
|
+
//- option is rendered at the top (`disabled selected hidden`).
|
|
44
|
+
//- - The <select> element always has `.single-select` class for styling.
|
|
45
|
+
//- - The label, if provided, uses `.form-label` class.
|
|
46
|
+
//-
|
|
47
|
+
//- Notes about styling and classes:
|
|
48
|
+
//- - Visual appearance is controlled by `.single-select` and `.form-label`
|
|
49
|
+
//- styles in your SCSS.
|
|
50
|
+
//- - To add custom classes or attributes, extend the mixin or wrap it.
|
|
51
|
+
//- - Ensure `id` is unique to avoid duplicate `for`/`id` collisions.
|
|
52
|
+
//-
|
|
53
|
+
//- Examples:
|
|
54
|
+
//- // Simple array of strings
|
|
55
|
+
//- +singleSelect('topic1', 'Topic', ['Support', 'Feedback', 'Other'])
|
|
56
|
+
//-
|
|
57
|
+
//- // Array of objects with value/text
|
|
58
|
+
//- +singleSelect('topic2', 'Topic', [
|
|
59
|
+
//- {value:'support', text:'Support'},
|
|
60
|
+
//- {value:'feedback', text:'Feedback'},
|
|
61
|
+
//- {value:'other', text:'Other'}
|
|
62
|
+
//- ])
|
|
63
|
+
//-
|
|
64
|
+
//- // Array of objects with id/label, custom keys
|
|
65
|
+
//- +singleSelect('topic3', 'Topic', [
|
|
66
|
+
//- {id:'1', label:'Support'},
|
|
67
|
+
//- {id:'2', label:'Feedback'}
|
|
68
|
+
//- ], 'label', 'id')
|
|
69
|
+
//-
|
|
70
|
+
//- // With default selected value
|
|
71
|
+
//- +singleSelect('topic4', 'Topic', [
|
|
72
|
+
//- {value:'support', text:'Support'},
|
|
73
|
+
//- {value:'feedback', text:'Feedback'},
|
|
74
|
+
//- {value:'other', text:'Other'}
|
|
75
|
+
//- ], 'text', 'value', 'feedback')
|
|
76
|
+
//-
|
|
77
|
+
//- // Without label, with placeholder
|
|
78
|
+
//- +singleSelect('topic5', null, [
|
|
79
|
+
//- {value:'support', text:'Support'},
|
|
80
|
+
//- {value:'feedback', text:'Feedback'},
|
|
81
|
+
//- {value:'other', text:'Other'}
|
|
82
|
+
//- ], 'text', 'value', '', 'Please select a topic')
|
|
83
|
+
mixin singleSelect(id, label, options, labelKey='text', valueKey='value', selectedValue='', placeholder='Choose an option')
|
|
84
|
+
if label
|
|
85
|
+
label.form-label(for=id data-i18n=label)= label
|
|
86
|
+
select.single-select(id=id, name=id required)
|
|
87
|
+
if !label && !selectedValue
|
|
88
|
+
option(value='', disabled, selected, hidden data-i18n=placeholder)= placeholder
|
|
89
|
+
each opt in options
|
|
90
|
+
- let value = typeof opt === 'string' ? opt : opt[valueKey];
|
|
91
|
+
- let textKey = typeof opt === 'string' ? opt : opt[labelKey];
|
|
92
|
+
option(value=value, data-opt-value=value, selected=(value === selectedValue), data-i18n=textKey)= textKey
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@razerspine/pug-ui-kit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared Pug mixins for Webpack Starter templates",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"mixins",
|
|
8
|
+
"index.js",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"author": "Razerspine",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/Razerspine/webpack-starter-monorepo",
|
|
16
|
+
"directory": "packages/pug-ui-kit"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/Razerspine/webpack-starter-monorepo/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
}
|
|
25
|
+
}
|