kozenet_ui 0.1.4 → 0.1.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +79 -16
- data/app/assets/fonts/kozenet_ui/inter-latin.woff2 +0 -0
- data/app/assets/fonts/kozenet_ui/jetbrains-mono-latin.woff2 +0 -0
- data/app/assets/fonts/kozenet_ui/source-serif-4-latin.woff2 +0 -0
- data/app/assets/javascripts/kozenet_ui/index.js +226 -17
- data/app/assets/stylesheets/kozenet_ui/base.css +5 -0
- data/app/assets/stylesheets/kozenet_ui/components/avatar.css +4 -1
- data/app/assets/stylesheets/kozenet_ui/components/badge.css +11 -1
- data/app/assets/stylesheets/kozenet_ui/components/button.css +21 -1
- data/app/assets/stylesheets/kozenet_ui/components/header.css +227 -38
- data/app/assets/stylesheets/kozenet_ui/components/utilities.css +52 -1
- data/app/assets/stylesheets/kozenet_ui/fonts.css +37 -0
- data/app/assets/stylesheets/kozenet_ui/tokens.css +150 -53
- data/app/components/kozenet_ui/base_component.rb +21 -1
- data/app/components/kozenet_ui/header_component/action_button_component.html.erb +4 -2
- data/app/components/kozenet_ui/header_component/action_button_component.rb +78 -4
- data/app/components/kozenet_ui/header_component/nav_item_component.html.erb +1 -1
- data/app/components/kozenet_ui/header_component/nav_item_component.rb +6 -0
- data/app/components/kozenet_ui/header_component/user_menu_component.html.erb +5 -7
- data/app/components/kozenet_ui/header_component.html.erb +39 -30
- data/app/components/kozenet_ui/header_component.rb +26 -4
- data/app/helpers/kozenet_ui/component_helper.rb +82 -8
- data/app/helpers/kozenet_ui/icon_helper.rb +11 -7
- data/docs/README.md +25 -0
- data/docs/components/README.md +44 -0
- data/docs/components/avatar.md +73 -0
- data/docs/components/badge.md +74 -0
- data/docs/components/button.md +95 -0
- data/docs/components/header.md +199 -0
- data/docs/foundations/README.md +14 -0
- data/docs/foundations/fonts.md +136 -0
- data/lib/generators/kozenet_ui/install/install_generator.rb +94 -8
- data/lib/generators/kozenet_ui/install/templates/kozenet_ui.rb +3 -0
- data/lib/kozenet_ui/configuration.rb +35 -0
- data/lib/kozenet_ui/engine.rb +89 -14
- data/lib/kozenet_ui/theme/palette.rb +21 -7
- data/lib/kozenet_ui/theme/variants.rb +1 -0
- data/lib/kozenet_ui/version.rb +1 -1
- data/lib/kozenet_ui.rb +2 -0
- metadata +34 -13
- data/app/assets/images/kozenet_ui/icons/cart.svg +0 -1
- data/app/assets/images/kozenet_ui/icons/heart.svg +0 -1
|
@@ -28,28 +28,102 @@ module KozenetUi
|
|
|
28
28
|
|
|
29
29
|
# Include theme styles in layout
|
|
30
30
|
def kozenet_ui_stylesheet_tag
|
|
31
|
-
stylesheet_link_tag
|
|
31
|
+
stylesheet_link_tag(
|
|
32
|
+
"kozenet_ui/tokens",
|
|
33
|
+
"kozenet_ui/fonts",
|
|
34
|
+
"kozenet_ui/base",
|
|
35
|
+
"kozenet_ui/components/button",
|
|
36
|
+
"kozenet_ui/components/header",
|
|
37
|
+
"kozenet_ui/components/avatar",
|
|
38
|
+
"kozenet_ui/components/badge",
|
|
39
|
+
"kozenet_ui/components/utilities",
|
|
40
|
+
"data-turbo-track": "reload"
|
|
41
|
+
)
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
# Include theme JavaScript
|
|
35
45
|
def kozenet_ui_javascript_tag
|
|
36
|
-
javascript_include_tag "kozenet_ui/index", type: "module"
|
|
46
|
+
javascript_include_tag "kozenet_ui/index", type: "module", "data-turbo-track": "reload"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Include runtime tags once in the application layout.
|
|
50
|
+
#
|
|
51
|
+
# Stylesheets are loaded directly so Propshaft/Sprockets can emit digested
|
|
52
|
+
# asset URLs in production. Apps can pass stylesheets: false when bundling
|
|
53
|
+
# Kozenet UI CSS with their own build pipeline.
|
|
54
|
+
def kozenet_ui_head_tags(stylesheets: true, javascript: true)
|
|
55
|
+
tags = []
|
|
56
|
+
tags << kozenet_ui_stylesheet_tag if stylesheets
|
|
57
|
+
tags << kozenet_ui_config_tag
|
|
58
|
+
tags << kozenet_ui_theme_variables_tag
|
|
59
|
+
tags << kozenet_ui_javascript_tag if javascript
|
|
60
|
+
|
|
61
|
+
safe_join(tags, "\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def kozenet_ui_config_tag
|
|
65
|
+
tag.meta name: "kozenet-ui-stimulus-prefix", content: KozenetUi.configuration.stimulus_prefix
|
|
37
66
|
end
|
|
38
67
|
|
|
39
68
|
# Inject inline theme variables (CSP-compliant)
|
|
40
69
|
def kozenet_ui_theme_variables_tag
|
|
41
70
|
# rubocop:disable Rails/OutputSafety
|
|
42
|
-
content_tag(:style, nonce: content_security_policy_nonce)
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
content_tag(:style, kozenet_ui_theme_variables, nonce: content_security_policy_nonce)
|
|
72
|
+
# rubocop:enable Rails/OutputSafety
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def kozenet_ui_theme_variables
|
|
76
|
+
# rubocop:disable Rails/OutputSafety
|
|
77
|
+
palette = KozenetUi.configuration.palette
|
|
78
|
+
light_palette = palette.to_css_variables(mode: :light)
|
|
79
|
+
dark_palette = palette.to_css_variables(mode: :dark)
|
|
80
|
+
tokens = KozenetUi::Theme::Tokens.to_css_variables
|
|
45
81
|
|
|
82
|
+
case KozenetUi.configuration.theme
|
|
83
|
+
when :dark, "dark"
|
|
84
|
+
<<~CSS.html_safe
|
|
85
|
+
:root {
|
|
86
|
+
color-scheme: dark;
|
|
87
|
+
#{tokens}
|
|
88
|
+
#{dark_palette}
|
|
89
|
+
}
|
|
90
|
+
[data-theme="light"], .light {
|
|
91
|
+
color-scheme: light;
|
|
92
|
+
#{light_palette}
|
|
93
|
+
}
|
|
94
|
+
CSS
|
|
95
|
+
when :system, "system"
|
|
96
|
+
<<~CSS.html_safe
|
|
97
|
+
:root {
|
|
98
|
+
color-scheme: light;
|
|
99
|
+
#{tokens}
|
|
100
|
+
#{light_palette}
|
|
101
|
+
}
|
|
102
|
+
@media (prefers-color-scheme: dark) {
|
|
103
|
+
:root:not([data-theme="light"]) {
|
|
104
|
+
color-scheme: dark;
|
|
105
|
+
#{dark_palette}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
[data-theme="dark"], .dark {
|
|
109
|
+
color-scheme: dark;
|
|
110
|
+
#{dark_palette}
|
|
111
|
+
}
|
|
112
|
+
[data-theme="light"], .light {
|
|
113
|
+
color-scheme: light;
|
|
114
|
+
#{light_palette}
|
|
115
|
+
}
|
|
116
|
+
CSS
|
|
117
|
+
else
|
|
46
118
|
<<~CSS.html_safe
|
|
47
119
|
:root {
|
|
48
|
-
|
|
49
|
-
#{
|
|
120
|
+
color-scheme: light;
|
|
121
|
+
#{tokens}
|
|
122
|
+
#{light_palette}
|
|
50
123
|
}
|
|
51
124
|
[data-theme="dark"], .dark {
|
|
52
|
-
|
|
125
|
+
color-scheme: dark;
|
|
126
|
+
#{dark_palette}
|
|
53
127
|
}
|
|
54
128
|
CSS
|
|
55
129
|
end
|
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
module KozenetUi
|
|
4
4
|
# Helper methods for rendering SVG icons in Kozenet UI
|
|
5
5
|
module IconHelper
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
include RailsHeroicon::Helper
|
|
7
|
+
|
|
8
8
|
def kozenet_ui_icon(name, options = {})
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
heroicon(normalize_icon_name(name), **options)
|
|
10
|
+
rescue RailsHeroicon::UndefinedIcon => e
|
|
11
|
+
raise ArgumentError, "Unknown Heroicon `#{name}`. Use a valid icon name from Heroicons.", e.backtrace
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def normalize_icon_name(name)
|
|
17
|
+
name.to_s.tr("_", "-")
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
end
|
data/docs/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Kozenet UI Docs
|
|
2
|
+
|
|
3
|
+
This folder documents how to use Kozenet UI as a Rails gem.
|
|
4
|
+
|
|
5
|
+
## Foundations
|
|
6
|
+
|
|
7
|
+
Foundations are the shared UI base used by every component.
|
|
8
|
+
|
|
9
|
+
- [Fonts](./foundations/fonts.md)
|
|
10
|
+
|
|
11
|
+
## Components
|
|
12
|
+
|
|
13
|
+
Components are the public ViewComponent APIs used in Rails views.
|
|
14
|
+
|
|
15
|
+
- [Component Docs](./components/README.md)
|
|
16
|
+
- [Avatar](./components/avatar.md)
|
|
17
|
+
- [Badge](./components/badge.md)
|
|
18
|
+
- [Button](./components/button.md)
|
|
19
|
+
- [Header](./components/header.md)
|
|
20
|
+
|
|
21
|
+
## Adding New Docs
|
|
22
|
+
|
|
23
|
+
When adding a new component, add one Markdown file in `docs/components`.
|
|
24
|
+
|
|
25
|
+
When adding a shared system feature, add one Markdown file in `docs/foundations`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Component Docs
|
|
2
|
+
|
|
3
|
+
This folder is the public usage guide for Kozenet UI components. Each component gets one Markdown file with the same shape so the documentation can grow without becoming messy.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- [Avatar](./avatar.md)
|
|
8
|
+
- [Badge](./badge.md)
|
|
9
|
+
- [Button](./button.md)
|
|
10
|
+
- [Header](./header.md)
|
|
11
|
+
|
|
12
|
+
Shared UI foundations live in [../foundations](../foundations/README.md).
|
|
13
|
+
|
|
14
|
+
## Documentation Pattern
|
|
15
|
+
|
|
16
|
+
Each component page should include:
|
|
17
|
+
|
|
18
|
+
- Purpose: when to use the component.
|
|
19
|
+
- Quick usage: the recommended helper API.
|
|
20
|
+
- Direct render: the ViewComponent API.
|
|
21
|
+
- Options: accepted options and defaults.
|
|
22
|
+
- Examples: common production patterns.
|
|
23
|
+
- Best practices: how to keep apps clean, fast, and consistent.
|
|
24
|
+
|
|
25
|
+
## Rails Setup
|
|
26
|
+
|
|
27
|
+
Load Kozenet UI once in your application layout:
|
|
28
|
+
|
|
29
|
+
```erb
|
|
30
|
+
<%= javascript_importmap_tags %>
|
|
31
|
+
<%= kozenet_ui_head_tags %>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Configure global defaults in `config/initializers/kozenet_ui.rb`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
KozenetUi.configure do |config|
|
|
38
|
+
config.theme = :system
|
|
39
|
+
config.stimulus_prefix = "kz"
|
|
40
|
+
config.component :header, sticky: true, blur: true
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Per-render options should always win over initializer defaults.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
Use `AvatarComponent` for people, teams, authors, and account menu triggers.
|
|
4
|
+
|
|
5
|
+
## Quick Usage
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= kz_avatar(src: user.avatar_url, alt: user.name) %>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Direct Render
|
|
12
|
+
|
|
13
|
+
```erb
|
|
14
|
+
<%= render KozenetUi::AvatarComponent.new(initials: "KP", variant: :primary) %>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Options
|
|
18
|
+
|
|
19
|
+
| Option | Default | Description |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `src` | `nil` | Image URL. |
|
|
22
|
+
| `alt` | `"Avatar"` | Image alt text. |
|
|
23
|
+
| `initials` | `nil` | Text fallback when no image is present. |
|
|
24
|
+
| `variant` | `:primary` | Background style for initials/default state. |
|
|
25
|
+
| `size` | `:md` | Avatar size. |
|
|
26
|
+
| `html_options` | `{}` | Extra HTML attributes. |
|
|
27
|
+
|
|
28
|
+
## Image Avatar
|
|
29
|
+
|
|
30
|
+
```erb
|
|
31
|
+
<%= kz_avatar(src: current_user.avatar_url, alt: current_user.name, size: :lg) %>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Initials Avatar
|
|
35
|
+
|
|
36
|
+
```erb
|
|
37
|
+
<%= kz_avatar(initials: "JD", variant: :accent) %>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Default Icon Avatar
|
|
41
|
+
|
|
42
|
+
```erb
|
|
43
|
+
<%= kz_avatar %>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Sizes
|
|
47
|
+
|
|
48
|
+
```erb
|
|
49
|
+
<%= kz_avatar(initials: "XS", size: :xs) %>
|
|
50
|
+
<%= kz_avatar(initials: "SM", size: :sm) %>
|
|
51
|
+
<%= kz_avatar(initials: "MD", size: :md) %>
|
|
52
|
+
<%= kz_avatar(initials: "LG", size: :lg) %>
|
|
53
|
+
<%= kz_avatar(initials: "XL", size: :xl) %>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## With Extra Attributes
|
|
57
|
+
|
|
58
|
+
```erb
|
|
59
|
+
<%= kz_avatar(
|
|
60
|
+
initials: "KP",
|
|
61
|
+
html_options: {
|
|
62
|
+
title: "Kozenet Pro",
|
|
63
|
+
data: { testid: "author-avatar" }
|
|
64
|
+
}
|
|
65
|
+
) %>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Best Practices
|
|
69
|
+
|
|
70
|
+
- Always provide meaningful `alt` text for real user images.
|
|
71
|
+
- Use initials when the user has no uploaded image.
|
|
72
|
+
- Keep avatar size consistent inside repeated lists.
|
|
73
|
+
- Use the same variant for the same identity type across an app.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
Use `BadgeComponent` for statuses, labels, counters, categories, and small metadata.
|
|
4
|
+
|
|
5
|
+
## Quick Usage
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= kz_badge(variant: :success) { "Published" } %>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Direct Render
|
|
12
|
+
|
|
13
|
+
```erb
|
|
14
|
+
<%= render KozenetUi::BadgeComponent.new(variant: :warning, size: :sm) do %>
|
|
15
|
+
Pending
|
|
16
|
+
<% end %>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Options
|
|
20
|
+
|
|
21
|
+
| Option | Default | Description |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `variant` | `:primary` | Visual style. |
|
|
24
|
+
| `size` | `:md` | Badge size. |
|
|
25
|
+
| `pill` | `true` | Uses rounded pill shape. |
|
|
26
|
+
| `**html_options` | `{}` | Extra HTML attributes. |
|
|
27
|
+
|
|
28
|
+
## Variants
|
|
29
|
+
|
|
30
|
+
```erb
|
|
31
|
+
<%= kz_badge(variant: :primary) { "Primary" } %>
|
|
32
|
+
<%= kz_badge(variant: :secondary) { "Secondary" } %>
|
|
33
|
+
<%= kz_badge(variant: :accent) { "Accent" } %>
|
|
34
|
+
<%= kz_badge(variant: :success) { "Success" } %>
|
|
35
|
+
<%= kz_badge(variant: :warning) { "Warning" } %>
|
|
36
|
+
<%= kz_badge(variant: :error) { "Error" } %>
|
|
37
|
+
<%= kz_badge(variant: :info) { "Info" } %>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Count Badge
|
|
41
|
+
|
|
42
|
+
```erb
|
|
43
|
+
<%= kz_badge(variant: :primary, size: :sm) { "99+" } %>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Category Badge
|
|
47
|
+
|
|
48
|
+
```erb
|
|
49
|
+
<%= kz_badge(variant: :accent) { blog.category_name } %>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## With Icon
|
|
53
|
+
|
|
54
|
+
```erb
|
|
55
|
+
<%= kz_badge(variant: :warning) do |badge| %>
|
|
56
|
+
<% badge.with_icon do %>
|
|
57
|
+
<%= kozenet_ui_icon(:clock, size: 14) %>
|
|
58
|
+
<% end %>
|
|
59
|
+
Pending
|
|
60
|
+
<% end %>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Less Rounded
|
|
64
|
+
|
|
65
|
+
```erb
|
|
66
|
+
<%= kz_badge(variant: :secondary, pill: false) { "Draft" } %>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Best Practices
|
|
70
|
+
|
|
71
|
+
- Keep text short: one to three words.
|
|
72
|
+
- Use semantic variants for state: `:success`, `:warning`, `:error`.
|
|
73
|
+
- Use `:accent` for brand/category emphasis.
|
|
74
|
+
- Avoid using badges as buttons. Use `kz_button` for actions.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
Use `ButtonComponent` for primary actions, secondary actions, links that should look like buttons, and loading or disabled states.
|
|
4
|
+
|
|
5
|
+
## Quick Usage
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= kz_button { "Save changes" } %>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```erb
|
|
12
|
+
<%= kz_button(variant: :secondary, size: :lg) { "Preview" } %>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Direct Render
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<%= render KozenetUi::ButtonComponent.new(variant: :primary, size: :md) do %>
|
|
19
|
+
Save changes
|
|
20
|
+
<% end %>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
| Option | Default | Description |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `variant` | `:primary` | Visual style. |
|
|
28
|
+
| `size` | `:md` | Button size. |
|
|
29
|
+
| `type` | `:button` | Native button type. |
|
|
30
|
+
| `href` | `nil` | Renders an anchor when present. |
|
|
31
|
+
| `disabled` | `false` | Disables the action visually and semantically. |
|
|
32
|
+
| `loading` | `false` | Shows spinner and marks the button busy. |
|
|
33
|
+
| `full_width` | `false` | Makes the button fill the available width. |
|
|
34
|
+
| `html_options` | `{}` | Extra HTML attributes. |
|
|
35
|
+
|
|
36
|
+
## Variants
|
|
37
|
+
|
|
38
|
+
```erb
|
|
39
|
+
<%= kz_button(variant: :primary) { "Primary" } %>
|
|
40
|
+
<%= kz_button(variant: :secondary) { "Secondary" } %>
|
|
41
|
+
<%= kz_button(variant: :accent) { "Accent" } %>
|
|
42
|
+
<%= kz_button(variant: :success) { "Success" } %>
|
|
43
|
+
<%= kz_button(variant: :warning) { "Warning" } %>
|
|
44
|
+
<%= kz_button(variant: :error) { "Error" } %>
|
|
45
|
+
<%= kz_button(variant: :ghost) { "Ghost" } %>
|
|
46
|
+
<%= kz_button(variant: :outline) { "Outline" } %>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Sizes
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<%= kz_button(size: :xs) { "XS" } %>
|
|
53
|
+
<%= kz_button(size: :sm) { "SM" } %>
|
|
54
|
+
<%= kz_button(size: :md) { "MD" } %>
|
|
55
|
+
<%= kz_button(size: :lg) { "LG" } %>
|
|
56
|
+
<%= kz_button(size: :xl) { "XL" } %>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Link Button
|
|
60
|
+
|
|
61
|
+
```erb
|
|
62
|
+
<%= kz_button(href: pricing_path, variant: :ghost) { "View pricing" } %>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Loading State
|
|
66
|
+
|
|
67
|
+
```erb
|
|
68
|
+
<%= kz_button(loading: true) { "Saving..." } %>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## With Icon
|
|
72
|
+
|
|
73
|
+
```erb
|
|
74
|
+
<%= kz_button(variant: :secondary) do |button| %>
|
|
75
|
+
<% button.with_icon do %>
|
|
76
|
+
<%= kozenet_ui_icon(:arrow_down_tray, size: 18) %>
|
|
77
|
+
<% end %>
|
|
78
|
+
Download
|
|
79
|
+
<% end %>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Form Submit
|
|
83
|
+
|
|
84
|
+
```erb
|
|
85
|
+
<%= form_with model: @post do |form| %>
|
|
86
|
+
<%= kz_button(type: :submit, variant: :primary) { "Publish" } %>
|
|
87
|
+
<% end %>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Best Practices
|
|
91
|
+
|
|
92
|
+
- Use `:primary` for one main action per screen or section.
|
|
93
|
+
- Use `:secondary`, `:ghost`, or `:outline` for supporting actions.
|
|
94
|
+
- Use `loading: true` while a submission is in progress.
|
|
95
|
+
- Use `href:` only for navigation. Use button submit types for forms.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Header
|
|
2
|
+
|
|
3
|
+
Use `HeaderComponent` for primary application navigation. It supports brand, nav links, search, icon actions, CTA, user menu, and mobile menu slots.
|
|
4
|
+
|
|
5
|
+
## Quick Usage
|
|
6
|
+
|
|
7
|
+
```erb
|
|
8
|
+
<%= kz_header do |header| %>
|
|
9
|
+
<% header.with_brand(href: root_path) do %>
|
|
10
|
+
<span>Kozenet</span>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<% header.with_nav_item(href: posts_path, active: current_page?(posts_path)) { "Posts" } %>
|
|
14
|
+
<% header.with_nav_item(href: new_post_path) { "New" } %>
|
|
15
|
+
|
|
16
|
+
<% header.with_search(placeholder: "Search posts", action: posts_path, value: params[:q]) %>
|
|
17
|
+
<% header.with_action_button(href: saved_posts_path, icon: :heart, label: "Saved") %>
|
|
18
|
+
<% header.with_cta(href: new_post_path) { "New post" } %>
|
|
19
|
+
<% end %>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Direct Render
|
|
23
|
+
|
|
24
|
+
```erb
|
|
25
|
+
<%= render KozenetUi::HeaderComponent.new(sticky: true, blur: true) do |header| %>
|
|
26
|
+
<% header.with_brand(href: root_path) { "Brand" } %>
|
|
27
|
+
<% end %>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
|
|
32
|
+
| Option | Default | Description |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `sticky` | initializer default, fallback `true` | Keeps header sticky at top. |
|
|
35
|
+
| `blur` | initializer default, fallback `true` | Enables glass/blur treatment. |
|
|
36
|
+
| `**html_options` | `{}` | Extra HTML attributes. |
|
|
37
|
+
|
|
38
|
+
## Global Defaults
|
|
39
|
+
|
|
40
|
+
Set project defaults in `config/initializers/kozenet_ui.rb`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
KozenetUi.configure do |config|
|
|
44
|
+
config.component :header, sticky: true, blur: true
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Override for one render:
|
|
49
|
+
|
|
50
|
+
```erb
|
|
51
|
+
<%= kz_header(sticky: false, blur: false) do |header| %>
|
|
52
|
+
<% header.with_brand(href: root_path) { "Plain Header" } %>
|
|
53
|
+
<% end %>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Slots
|
|
57
|
+
|
|
58
|
+
| Slot | Purpose |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `with_brand` | Brand/logo link. |
|
|
61
|
+
| `with_nav_item` | Primary nav links. |
|
|
62
|
+
| `with_search` | Header search form. |
|
|
63
|
+
| `with_action_button` | Icon or compact text action button. |
|
|
64
|
+
| `with_cta` | Main call-to-action. |
|
|
65
|
+
| `with_user_menu` | Account/avatar menu. |
|
|
66
|
+
| `with_mobile_menu` | Mobile navigation panel. |
|
|
67
|
+
|
|
68
|
+
## Brand
|
|
69
|
+
|
|
70
|
+
```erb
|
|
71
|
+
<% header.with_brand(href: root_path) do %>
|
|
72
|
+
<span class="brand-mark">K</span>
|
|
73
|
+
<span>Kozenet <span class="accent">UI</span></span>
|
|
74
|
+
<% end %>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Nav Items
|
|
78
|
+
|
|
79
|
+
```erb
|
|
80
|
+
<% header.with_nav_item(href: dashboard_path, active: current_page?(dashboard_path)) { "Dashboard" } %>
|
|
81
|
+
<% header.with_nav_item(href: settings_path) { "Settings" } %>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Search
|
|
85
|
+
|
|
86
|
+
```erb
|
|
87
|
+
<% header.with_search(
|
|
88
|
+
placeholder: "Search",
|
|
89
|
+
name: :q,
|
|
90
|
+
value: params[:q],
|
|
91
|
+
action: posts_path,
|
|
92
|
+
method: :get
|
|
93
|
+
) %>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Action Button
|
|
97
|
+
|
|
98
|
+
Action buttons use Heroicons names. Ruby-style names are normalized:
|
|
99
|
+
|
|
100
|
+
```erb
|
|
101
|
+
<% header.with_action_button(href: notifications_path, icon: :bell, label: "Notifications") %>
|
|
102
|
+
<% header.with_action_button(href: cart_path, icon: :shopping_cart, label: "Cart") %>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`label` is used for accessibility and is visually hidden in the icon-only button.
|
|
106
|
+
|
|
107
|
+
By default, action buttons render in the end group, on the right side of the desktop header. Use `placement: :start` for actions that belong at the left/start edge, such as a sidebar toggle:
|
|
108
|
+
|
|
109
|
+
```erb
|
|
110
|
+
<% header.with_action_button(href: "#", icon: :bars_3, label: "Open menu", placement: :start) %>
|
|
111
|
+
<% header.with_action_button(href: saved_posts_path, icon: :heart, label: "Saved", placement: :end) %>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`position: :left` and `position: :right` are accepted as aliases, but `placement: :start` and `placement: :end` are preferred.
|
|
115
|
+
|
|
116
|
+
Header action buttons are desktop-only by default so mobile headers stay clean. Use `visible_on:` when an action should appear somewhere else:
|
|
117
|
+
|
|
118
|
+
```erb
|
|
119
|
+
<% header.with_action_button(href: "#", icon: :bars_3, label: "Open menu", placement: :start, visible_on: :mobile) %>
|
|
120
|
+
<% header.with_action_button(href: settings_path, icon: :cog_6_tooth, label: "Settings", visible_on: :desktop) %>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Accepted values are `:desktop`, `:mobile`, and `:always`. The default is `:desktop`.
|
|
124
|
+
|
|
125
|
+
Use `text:` when the action should be visible as a compact text button:
|
|
126
|
+
|
|
127
|
+
```erb
|
|
128
|
+
<% header.with_action_button(href: saved_posts_path, text: "Saved", label: "Saved posts") %>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
You can combine icon and text:
|
|
132
|
+
|
|
133
|
+
```erb
|
|
134
|
+
<% header.with_action_button(href: saved_posts_path, icon: :heart, text: "Saved", label: "Saved posts") %>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## CTA
|
|
138
|
+
|
|
139
|
+
```erb
|
|
140
|
+
<% header.with_cta(href: new_post_path) { "New post" } %>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## User Menu
|
|
144
|
+
|
|
145
|
+
```erb
|
|
146
|
+
<% header.with_user_menu(user_name: current_user.name, avatar_url: current_user.avatar_url) do %>
|
|
147
|
+
<%= link_to "Profile", profile_path, class: "menu-link" %>
|
|
148
|
+
<%= link_to "Settings", settings_path, class: "menu-link" %>
|
|
149
|
+
<% end %>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Mobile Menu
|
|
153
|
+
|
|
154
|
+
```erb
|
|
155
|
+
<% header.with_mobile_menu do %>
|
|
156
|
+
<nav aria-label="Mobile navigation">
|
|
157
|
+
<%= link_to "Posts", posts_path %>
|
|
158
|
+
<%= link_to "New post", new_post_path %>
|
|
159
|
+
</nav>
|
|
160
|
+
<% end %>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Full Example
|
|
164
|
+
|
|
165
|
+
```erb
|
|
166
|
+
<%= kz_header do |header| %>
|
|
167
|
+
<% header.with_brand(href: root_path) do %>
|
|
168
|
+
<span class="brand-mark">K</span>
|
|
169
|
+
<span>Kozenet <span class="accent">Blog</span></span>
|
|
170
|
+
<% end %>
|
|
171
|
+
|
|
172
|
+
<% header.with_nav_item(href: posts_path, active: current_page?(posts_path)) { "Posts" } %>
|
|
173
|
+
<% header.with_nav_item(href: new_post_path, active: current_page?(new_post_path)) { "New" } %>
|
|
174
|
+
<% header.with_search(placeholder: "Search posts", action: posts_path, value: params[:q]) %>
|
|
175
|
+
<% header.with_action_button(href: saved_posts_path, icon: :heart, label: "Saved") %>
|
|
176
|
+
|
|
177
|
+
<% header.with_user_menu(user_name: "Kozenet") do %>
|
|
178
|
+
<%= link_to "All posts", posts_path, class: "menu-link" %>
|
|
179
|
+
<%= link_to "New post", new_post_path, class: "menu-link" %>
|
|
180
|
+
<% end %>
|
|
181
|
+
|
|
182
|
+
<% header.with_mobile_menu do %>
|
|
183
|
+
<nav aria-label="Mobile navigation">
|
|
184
|
+
<%= link_to "Posts", posts_path %>
|
|
185
|
+
<%= link_to "New post", new_post_path %>
|
|
186
|
+
</nav>
|
|
187
|
+
<% end %>
|
|
188
|
+
<% end %>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Best Practices
|
|
192
|
+
|
|
193
|
+
- Load `kozenet_ui_head_tags` once in the layout, not per page.
|
|
194
|
+
- Prefer initializer defaults for app-wide header behavior.
|
|
195
|
+
- Keep per-page overrides close to the render call.
|
|
196
|
+
- Use Heroicons names for icon actions.
|
|
197
|
+
- Always provide labels for icon-only actions.
|
|
198
|
+
- Keep navigation items short so desktop and mobile layouts stay balanced.
|
|
199
|
+
- Keep mobile actions intentional; prefer one menu/search trigger and move secondary links into `with_mobile_menu`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Foundations
|
|
2
|
+
|
|
3
|
+
Foundations are the shared design and runtime pieces behind Kozenet UI components.
|
|
4
|
+
|
|
5
|
+
- [Fonts](./fonts.md)
|
|
6
|
+
|
|
7
|
+
Future foundation docs can live here too:
|
|
8
|
+
|
|
9
|
+
- Colors
|
|
10
|
+
- Theme
|
|
11
|
+
- Tokens
|
|
12
|
+
- Icons
|
|
13
|
+
- JavaScript
|
|
14
|
+
- Accessibility
|