@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 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
@@ -0,0 +1,3 @@
1
+ const path = require('path');
2
+
3
+ module.exports = path.join(__dirname, 'mixins');
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
+ }