├── fonts ├── JetBrainsMono-Medium.woff2 ├── FoundersGrotesk-Light.woff2 ├── FoundersGrotesk-Medium.woff2 ├── WorkSans-VariableFont_wght.ttf ├── JetBrainsMono-Medium-Italic.woff2 ├── FoundersGrotesk-Light-Italic.woff2 ├── FoundersGrotesk-Medium-Italic.woff2 └── WorkSans-Italic-VariableFont_wght.ttf ├── spacing ├── spacing.css └── README.md ├── components ├── boxes │ ├── README.md │ ├── boxes.css │ └── test.html ├── button │ ├── README.md │ └── button.css ├── dropdown │ ├── README.md │ ├── dropdown.css │ └── test.html ├── icons │ └── README.md ├── tags │ ├── README.md │ ├── test.html │ └── tags.css ├── modal │ ├── help-modal-template.html │ ├── modal.css │ ├── README.md │ └── modal.js ├── horizontal-cards │ ├── README.md │ ├── horizontal-cards.css │ └── test.html ├── numeric-slider │ ├── numeric-slider.css │ └── README.md └── input │ └── README.md ├── colors └── README.md ├── typography ├── README.md ├── typography.css └── test.html ├── test-server.js ├── LICENSE.md ├── README.md ├── llms.txt └── test.html /fonts/JetBrainsMono-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/JetBrainsMono-Medium.woff2 -------------------------------------------------------------------------------- /fonts/FoundersGrotesk-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/FoundersGrotesk-Light.woff2 -------------------------------------------------------------------------------- /fonts/FoundersGrotesk-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/FoundersGrotesk-Medium.woff2 -------------------------------------------------------------------------------- /fonts/WorkSans-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/WorkSans-VariableFont_wght.ttf -------------------------------------------------------------------------------- /fonts/JetBrainsMono-Medium-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/JetBrainsMono-Medium-Italic.woff2 -------------------------------------------------------------------------------- /fonts/FoundersGrotesk-Light-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/FoundersGrotesk-Light-Italic.woff2 -------------------------------------------------------------------------------- /fonts/FoundersGrotesk-Medium-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/FoundersGrotesk-Medium-Italic.woff2 -------------------------------------------------------------------------------- /fonts/WorkSans-Italic-VariableFont_wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/learn_bespoke-design-system/main/fonts/WorkSans-Italic-VariableFont_wght.ttf -------------------------------------------------------------------------------- /spacing/spacing.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --UI-Spacing-spacing-none: 0; 3 | --UI-Spacing-spacing-min: 2px; 4 | --UI-Spacing-spacing-xxs: 4px; 5 | --UI-Spacing-spacing-xs: 6px; 6 | --UI-Spacing-spacing-s: 8px; 7 | --UI-Spacing-spacing-mxs: 12px; 8 | --UI-Spacing-spacing-ms: 16px; 9 | --UI-Spacing-spacing-m: 18px; 10 | --UI-Spacing-spacing-ml: 20px; 11 | --UI-Spacing-spacing-mxl: 24px; 12 | --UI-Spacing-spacing-l: 28px; 13 | --UI-Spacing-spacing-xl: 32px; 14 | --UI-Spacing-spacing-xxl: 36px; 15 | --UI-Spacing-spacing-xxxl: 48px; 16 | --UI-Spacing-spacing-4xl: 60px; 17 | --UI-Spacing-spacing-max: 90px; 18 | 19 | --UI-Radius-radius-none: 0; 20 | --UI-Radius-radius-min: 2px; 21 | --UI-Radius-radius-xxs: 4px; 22 | --UI-Radius-radius-xs: 6px; 23 | --UI-Radius-radius-s: 8px; 24 | --UI-Radius-radius-m: 12px; 25 | --UI-Radius-radius-ml: 16px; 26 | --UI-Radius-radius-mxl: 20px; 27 | --UI-Radius-radius-l: 24px; 28 | --UI-Radius-radius-xl: 32px; 29 | 30 | --UI-Input-min: 26px; 31 | --UI-Input-xs: 32px; 32 | --UI-Input-sm: 40px; 33 | --UI-Input-md: 48px; 34 | --UI-Input-lg: 60px; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /spacing/README.md: -------------------------------------------------------------------------------- 1 | # Spacing & Layout Design System 2 | 3 | This directory contains the spacing, radius, and sizing definitions for the application. 4 | 5 | ## Usage 6 | 7 | Import the CSS file in your HTML or CSS: 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | or 14 | 15 | ```css 16 | @import url('/design-system/spacing/spacing.css'); 17 | ``` 18 | 19 | ## Spacing Scale 20 | 21 | Use these variables for padding, margin, gap, and positioning. 22 | 23 | Pattern: `--UI-Spacing-spacing-[Size]` 24 | 25 | - `none`: 0 26 | - `min`: 2px 27 | - `xxs`: 4px 28 | - `xs`: 6px 29 | - `s`: 8px 30 | - `mxs`: 12px 31 | - `ms`: 16px 32 | - `m`: 18px 33 | - `ml`: 20px 34 | - `mxl`: 24px 35 | - `l`: 28px 36 | - `xl`: 32px 37 | - `xxl`: 36px 38 | - `xxxl`: 48px 39 | - `4xl`: 60px 40 | - `max`: 90px 41 | 42 | ## Border Radius 43 | 44 | Use these variables for border-radius properties. 45 | 46 | Pattern: `--UI-Radius-radius-[Size]` 47 | 48 | - `none`: 0 49 | - `min`: 2px 50 | - `xxs`: 4px 51 | - `xs`: 6px 52 | - `s`: 8px 53 | - `m`: 12px 54 | - `ml`: 16px 55 | - `mxl`: 20px 56 | - `l`: 24px 57 | - `xl`: 32px 58 | 59 | ## Input Sizing 60 | 61 | Standard heights for inputs, buttons, and interactive elements. 62 | 63 | Pattern: `--UI-Input-[Size]` 64 | 65 | - `min`: 26px 66 | - `xs`: 32px 67 | - `sm`: 40px 68 | - `md`: 48px 69 | - `lg`: 60px 70 | 71 | -------------------------------------------------------------------------------- /components/boxes/README.md: -------------------------------------------------------------------------------- 1 | # Box Component 2 | 3 | A flexible container component matching the CodeSignal Design System. 4 | 5 | ## Usage 6 | 7 | Import the CSS file in your HTML or CSS: 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | or 14 | 15 | ```css 16 | @import url('/design-system/components/boxes/boxes.css'); 17 | ``` 18 | 19 | ## Classes 20 | 21 | ### Base Class 22 | - `.box`: The base class required for all box containers. Provides default padding, radius, and background. 23 | 24 | ### Variants 25 | - `.box.selected`: Selected state with a primary color border. 26 | - `.box.emphasized`: Emphasized state with a neutral border (useful for active states). 27 | - `.box.shadowed`: Adds a soft shadow (`--Colors-Shadow-Soft`). 28 | - `.box.card`: Adds a card-style shadow (`--Colors-Shadow-Card`). 29 | - `.box.input-group`: Input group variant with reduced padding and gap spacing, designed for grouping input elements together. 30 | 31 | ### States 32 | The component supports standard pseudo-classes (`:hover`, `:focus`, `:active`) and utility classes for manual state application: 33 | - `.hover` 34 | - `.focus` 35 | - `.selected` (acts as active/checked state) 36 | 37 | ## Examples 38 | 39 | ```html 40 | 41 |
This page explains how to use the : .
24 | 25 | 26 |To begin using the :
32 |Description of feature 1 and how to use it.
47 | 48 |Description of feature 2 and how to use it.
50 | 51 |Description of feature 3 and how to use it.
53 | 54 | 55 | 56 |Here's the typical workflow for using this application:
62 |Answer to common question 1 with helpful details.
96 |Answer to common question 2 with helpful details.
101 |Answer to common question 3 with helpful details.
106 |Place image files in the help/img/ directory and reference them with relative paths like <img src="./img/example.png" alt="Description">
Yes, the app automatically saves your work. You'll see status messages indicating when saves occur.
116 |This is a default box with content inside.
61 |This is a default box with content inside. (hover)
64 |This is a default box with content inside. (focus)
67 |This is a default box with content inside. (selected)
74 |This is a default box with content inside. (selected hover)
77 |This is a default box with content inside. (selected focus)
80 |This is a default box with content inside. (emphasized)
87 |This is a default box with content inside. (emphasized hover)
90 |This is a default box with content inside. (emphasized focus)
93 |This is a default box with content inside. (shadowed)
100 |This is a default box with content inside. (shadowed hover)
103 |This is a default box with content inside. (shadowed focus)
106 |This is a default box with content inside. (card)
113 |This is a default box with content inside. (card hover)
116 |This is a default box with content inside. (card focus)
119 |`, links, etc.). |
67 | | `description` | String | No | Card description text. Supports HTML content (e.g., ``, ``, ``, links, spans with styling, etc.). |
68 | | `actionPlaceholder` | String | No | Placeholder text for the action area (displays in a dashed border box). |
69 | | `actionHtml` | String | No | Custom HTML content for the action area. If provided, `actionPlaceholder` is ignored. |
70 |
71 | ## API Methods
72 |
73 | - **`getCurrentIndex()`**: Returns the current visible card index (0-based).
74 | - **`getCurrentCard()`**: Returns the current visible card object.
75 | - **`scrollToNext()`**: Scrolls to the next card.
76 | - **`scrollToPrevious()`**: Scrolls to the previous card.
77 | - **`scrollToIndex(index)`**: Scrolls to a specific card by index.
78 | - **`destroy()`**: Removes event listeners and clears the container.
79 |
80 | ## Features
81 |
82 | - **Smooth Scrolling**: Cards scroll smoothly when navigating.
83 | - **Visual Indicators**: Fade gradients appear on the left/right edges when content is scrollable.
84 | - **Keyboard Navigation**: Arrow keys (Left/Right) navigate between cards.
85 | - **Touch/Swipe Support**: Swipe gestures on touch devices.
86 | - **Responsive**: Adapts to different screen sizes.
87 | - **Accessibility**: Proper ARIA attributes and keyboard support.
88 | - **Dark Mode**: Automatically adapts to dark mode preferences.
89 |
90 | ## Examples
91 |
92 | ### Basic Usage
93 |
94 | ```javascript
95 | const cards = new HorizontalCards('#my-cards', {
96 | cards: [
97 | {
98 | title: 'Boss 1',
99 | description: 'Description text here',
100 | actionPlaceholder: 'Add label'
101 | },
102 | {
103 | title: 'Boss 2',
104 | description: 'More description text',
105 | actionPlaceholder: 'Add label'
106 | }
107 | ]
108 | });
109 | ```
110 |
111 | ### Without Navigation Buttons
112 |
113 | ```javascript
114 | const cards = new HorizontalCards('#my-cards', {
115 | cards: sampleCards,
116 | showNavigation: false
117 | });
118 | ```
119 |
120 | ### Custom Action Area
121 |
122 | ```javascript
123 | const cards = new HorizontalCards('#my-cards', {
124 | cards: [
125 | {
126 | title: 'Custom Card',
127 | description: 'Card with custom action',
128 | actionHtml: ''
129 | }
130 | ]
131 | });
132 | ```
133 |
134 | ### HTML Content in Title and Description
135 |
136 | ```javascript
137 | const cards = new HorizontalCards('#my-cards', {
138 | cards: [
139 | {
140 | title: 'Card with Bold Title',
141 | description: 'This description has bold text, italic text, and a link.',
142 | actionPlaceholder: 'Add label'
143 | },
144 | {
145 | title: 'Card with Italic and Code',
146 | description: 'You can use colored spans and other HTML elements.',
147 | actionPlaceholder: 'Add label'
148 | }
149 | ]
150 | });
151 | ```
152 |
153 | ### Programmatic Navigation
154 |
155 | ```javascript
156 | const cards = new HorizontalCards('#my-cards', {
157 | cards: sampleCards
158 | });
159 |
160 | // Scroll to specific card
161 | cards.scrollToIndex(2);
162 |
163 | // Get current card
164 | const currentCard = cards.getCurrentCard();
165 | console.log('Current card:', currentCard);
166 | ```
167 |
168 | ## Dependencies
169 |
170 | This component relies on variables from:
171 | - `design-system/colors/colors.css`
172 | - `design-system/spacing/spacing.css`
173 | - `design-system/typography/typography.css`
174 |
175 | ## Browser Support
176 |
177 | - Modern browsers (Chrome, Firefox, Safari, Edge)
178 | - Supports touch devices with swipe gestures
179 | - Keyboard navigation support
180 |
181 |
--------------------------------------------------------------------------------
/components/tags/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tags Component Test
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
54 |
55 |
56 | Tags Component Test
57 |
58 |
59 | Primary Tag
60 |
61 |
62 |
63 | Tag Label
64 |
65 |
66 | Hover
67 |
68 |
69 | Focus
70 |
71 |
72 | Active
73 |
74 |
75 |
76 | Secondary Tag
77 |
78 |
79 |
80 | Tag Label
81 |
82 |
83 | Hover
84 |
85 |
86 | Focus
87 |
88 |
89 | Active
90 |
91 |
92 |
93 | Outline Tag
94 |
95 |
96 |
97 | Tag Label
98 |
99 |
100 | Hover
101 |
102 |
103 | Focus
104 |
105 |
106 | Active
107 |
108 |
109 |
110 | Success Tag
111 |
112 |
113 |
114 | Tag Label
115 |
116 |
117 | Hover
118 |
119 |
120 | Focus
121 |
122 |
123 | Active
124 |
125 |
126 |
127 | Error Tag
128 |
129 |
130 |
131 | Tag Label
132 |
133 |
134 | Hover
135 |
136 |
137 | Focus
138 |
139 |
140 | Active
141 |
142 |
143 |
144 | Warning Tag
145 |
146 |
147 |
148 | Tag Label
149 |
150 |
151 | Hover
152 |
153 |
154 | Focus
155 |
156 |
157 | Active
158 |
159 |
160 |
161 | Info Tag
162 |
163 |
164 |
165 | Tag Label
166 |
167 |
168 | Hover
169 |
170 |
171 | Focus
172 |
173 |
174 | Active
175 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/components/horizontal-cards/horizontal-cards.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Horizontal Cards Component Styles
3 | * Matches CodeSignal Design System
4 | */
5 |
6 | :root {
7 | /* Horizontal Cards Variables */
8 | --Colors-Horizontal-Cards-Card-Background: var(--Colors-Backgrounds-Main-Top);
9 | --Colors-Horizontal-Cards-Card-Shadow: rgba(76, 90, 123, 0.09);
10 | --Colors-Horizontal-Cards-Nav-Button-Background: var(--Colors-Backgrounds-Main-Top);
11 | --Colors-Horizontal-Cards-Nav-Button-Border: var(--Colors-Stroke-Default);
12 | --Colors-Horizontal-Cards-Nav-Button-Hover-Border: var(--Colors-Stroke-Strong);
13 | --Colors-Horizontal-Cards-Nav-Button-Icon: var(--Colors-Icon-Default);
14 | --Colors-Horizontal-Cards-Nav-Button-Disabled: var(--Colors-Base-Neutral-300);
15 | --Colors-Horizontal-Cards-Fade-Gradient: rgba(255, 255, 255, 0);
16 | --Colors-Shadow-Medium-Soft: rgba(22, 44, 96, 0.12);
17 | }
18 |
19 | @media (prefers-color-scheme: dark) {
20 | :root {
21 | --Colors-Horizontal-Cards-Card-Background: var(--Colors-Backgrounds-Main-Medium);
22 | --Colors-Horizontal-Cards-Card-Shadow: rgba(13, 21, 40, 0.32);
23 | --Colors-Horizontal-Cards-Nav-Button-Background: var(--Colors-Backgrounds-Main-Medium);
24 | --Colors-Horizontal-Cards-Nav-Button-Border: rgba(45, 56, 85, 0.00);
25 | --Colors-Horizontal-Cards-Nav-Button-Hover-Border: var(--Colors-Stroke-Strong);
26 | --Colors-Horizontal-Cards-Nav-Button-Icon: var(--Colors-Icon-Default);
27 | --Colors-Horizontal-Cards-Nav-Button-Disabled: var(--Colors-Base-Neutral-1100);
28 | --Colors-Horizontal-Cards-Fade-Gradient: rgba(20, 28, 48, 0);
29 | --Colors-Shadow-Medium-Soft: rgba(13, 21, 40, 0.4);
30 | }
31 | }
32 |
33 | /* Container */
34 | .horizontal-cards-container {
35 | position: relative;
36 | width: 100%;
37 | display: flex;
38 | flex-direction: column;
39 | gap: var(--UI-Spacing-spacing-mxs);
40 | }
41 |
42 | /* Scrollable Cards Wrapper */
43 | .horizontal-cards-scroll {
44 | position: relative;
45 | width: 100%;
46 | overflow-x: auto;
47 | overflow-y: hidden;
48 | scroll-behavior: smooth;
49 | scrollbar-width: none; /* Firefox */
50 | -ms-overflow-style: none; /* IE and Edge */
51 | /* Hide scrollbar for Chrome, Safari and Opera */
52 | scrollbar-width: none;
53 | }
54 |
55 | .horizontal-cards-scroll::-webkit-scrollbar {
56 | display: none;
57 | }
58 |
59 | /* Cards Track */
60 | .horizontal-cards-track {
61 | display: flex;
62 | align-items: stretch;
63 | gap: var(--UI-Spacing-spacing-mxl);
64 | padding: var(--UI-Spacing-spacing-mxs) 0;
65 | width: max-content;
66 | min-width: 100%;
67 | position: relative;
68 | z-index: 0;
69 | }
70 |
71 | /* Individual Card */
72 | .horizontal-cards-card {
73 | flex-shrink: 0;
74 | width: 480px;
75 | min-width: 480px;
76 | background: var(--Colors-Horizontal-Cards-Card-Background);
77 | border-radius: var(--UI-Radius-radius-ml);
78 | padding: var(--UI-Spacing-spacing-ms);
79 | box-shadow: 0 3px 2px 0 var(--Colors-Horizontal-Cards-Card-Shadow);
80 | display: flex;
81 | flex-direction: column;
82 | gap: var(--UI-Spacing-spacing-none);
83 | transition: opacity 0.3s ease, transform 0.3s ease;
84 | align-self: stretch;
85 | }
86 |
87 | .horizontal-cards-card:not(.horizontal-cards-card-active) {
88 | opacity: 0.5;
89 | }
90 |
91 | /* Card Content */
92 | .horizontal-cards-card-content {
93 | display: flex;
94 | flex-direction: column;
95 | gap: 0;
96 | flex: 1 0 auto;
97 | padding: var(--UI-Spacing-spacing-xl) var(--UI-Spacing-spacing-mxl);
98 | min-height: min-content;
99 | }
100 |
101 | .horizontal-cards-card-title {
102 | /* Uses .heading-small and .strong classes from typography.css */
103 | color: var(--Colors-Text-Body-Strong);
104 | margin: 0;
105 | margin-bottom: 0;
106 | flex-shrink: 0;
107 | }
108 |
109 | .horizontal-cards-card-description {
110 | /* Uses .body-medium class from typography.css */
111 | color: var(--Colors-Text-Body-Default);
112 | margin: 0;
113 | margin-top: 32px;
114 | flex-shrink: 0;
115 | overflow-wrap: break-word;
116 | word-wrap: break-word;
117 | }
118 |
119 | .horizontal-cards-card-action {
120 | border: 1.5px dashed var(--Colors-Stroke-Default);
121 | border-radius: var(--UI-Radius-radius-s);
122 | min-height: 36px;
123 | display: flex;
124 | align-items: center;
125 | justify-content: center;
126 | padding: var(--UI-Spacing-spacing-s);
127 | background: transparent;
128 | margin-top: var(--UI-Spacing-spacing-xl);
129 | }
130 |
131 | .horizontal-cards-card-action-placeholder {
132 | font-family: var(--body-family);
133 | font-size: var(--Fonts-Body-Default-xxs);
134 | font-weight: 500;
135 | line-height: 1.35;
136 | letter-spacing: -0.13px;
137 | color: var(--Colors-Text-Body-Light);
138 | text-align: center;
139 | }
140 |
141 | /* Navigation Buttons */
142 | .horizontal-cards-nav {
143 | display: flex;
144 | gap: var(--UI-Spacing-spacing-xs);
145 | align-items: center;
146 | justify-content: flex-end;
147 | margin-top: var(--UI-Spacing-spacing-mxs);
148 | width: 100%;
149 | }
150 |
151 | .horizontal-cards-nav-button {
152 | width: 32px;
153 | height: 32px;
154 | background: var(--Colors-Horizontal-Cards-Nav-Button-Background);
155 | border: 1.5px solid var(--Colors-Horizontal-Cards-Nav-Button-Border);
156 | border-radius: var(--UI-Radius-radius-xs);
157 | display: flex;
158 | align-items: center;
159 | justify-content: center;
160 | cursor: pointer;
161 | transition: border-color 0.2s ease, background-color 0.2s ease;
162 | padding: 0;
163 | box-shadow: 0px 2px 3px 0px var(--Colors-Shadow-Medium-Soft);
164 | }
165 |
166 | .horizontal-cards-nav-button:last-child {
167 | margin-right: var(--UI-Spacing-spacing-mxl);
168 | }
169 |
170 | .horizontal-cards-nav-button:hover:not(:disabled) {
171 | border-color: var(--Colors-Horizontal-Cards-Nav-Button-Hover-Border);
172 | }
173 |
174 | .horizontal-cards-nav-button:focus {
175 | outline: 2px solid var(--Colors-Primary-Medium);
176 | outline-offset: 2px;
177 | }
178 |
179 | .horizontal-cards-nav-button:disabled {
180 | cursor: not-allowed;
181 | opacity: 0.45;
182 | border-color: var(--Colors-Horizontal-Cards-Nav-Button-Disabled);
183 | }
184 |
185 | .horizontal-cards-nav-button-icon {
186 | width: 16px;
187 | height: 16px;
188 | display: flex;
189 | align-items: center;
190 | justify-content: center;
191 | color: var(--Colors-Horizontal-Cards-Nav-Button-Icon);
192 | }
193 |
194 | .horizontal-cards-nav-button:disabled .horizontal-cards-nav-button-icon {
195 | color: var(--Colors-Horizontal-Cards-Nav-Button-Disabled);
196 | }
197 |
198 | /* Responsive adjustments */
199 | @media (max-width: 768px) {
200 | .horizontal-cards-card {
201 | width: 320px;
202 | min-width: 320px;
203 | }
204 |
205 | .horizontal-cards-track {
206 | padding: var(--UI-Spacing-spacing-mxs) var(--UI-Spacing-spacing-ms);
207 | }
208 | }
209 |
210 |
--------------------------------------------------------------------------------
/llms.txt:
--------------------------------------------------------------------------------
1 | # CodeSignal Design System - LLM Reference
2 |
3 | ## Quick Start
4 |
5 | Include these CSS files in order:
6 | 1. colors/colors.css
7 | 2. spacing/spacing.css
8 | 3. typography/typography.css
9 | 4. components/[component]/[component].css
10 |
11 | Also include Work Sans font from Google Fonts.
12 |
13 | ## Design Tokens
14 |
15 | ### Colors
16 | - Base scales: `--Colors-Base-[Family]-[Step]` (e.g., `--Colors-Base-Primary-700`)
17 | - Semantic: `--Colors-[Category]-[Context]` (e.g., `--Colors-Text-Body-Default`)
18 | - Prefer semantic tokens over base scales for theming support
19 |
20 | ### Spacing
21 | - Pattern: `--UI-Spacing-spacing-[size]` (none, min, xxs, xs, s, mxs, ms, m, ml, mxl, l, xl, xxl, xxxl, 4xl, max)
22 | - Radius: `--UI-Radius-radius-[size]` (none, min, xxs, xs, s, m, ml, mxl, l, xl)
23 | - Input heights: `--UI-Input-[size]` (min: 26px, xs: 32px, sm: 40px, md: 48px, lg: 60px)
24 |
25 | ### Typography
26 | - Classes: `.body-[size]`, `.heading-[size]`, `.label-[size]`
27 | - Fonts: Work Sans (body), Founders Grotesk (headings), JetBrains Mono (code)
28 |
29 | ## Components
30 |
31 | ### Button
32 | - Base: `.button`
33 | - Variants: `.button-primary`, `.button-secondary`, `.button-tertiary`, `.button-danger`, `.button-success`, `.button-text`, `.button-text-primary`
34 | - Sizes: `.button-xsmall`, `.button-small`, `.button-large` (default: medium/48px)
35 | - Example: ``
36 |
37 | ### Box
38 | - Base: `.box`
39 | - Variants: `.box.selected`, `.box.emphasized`, `.box.shadowed`, `.box.card`
40 | - Example: `Content`
41 |
42 | ### Input
43 | - Base: `.input`
44 | - Types: `type="text"` or `type="number"`
45 | - States: `.hover`, `.focus`, `:disabled`
46 | - Example: ``
47 |
48 | ### Checkbox
49 | - Base: `.input-checkbox` wrapper with `input[type="checkbox"]`
50 | - Structure: ``
51 | - Sizes: `.input-checkbox-small`, `.input-checkbox-xsmall` (default: 32px)
52 | - States: Default, Hover (blue border), Checked (blue background + white checkmark), Disabled
53 | - Example: ``
54 |
55 | ### Radio
56 | - Base: `.input-radio` wrapper with `input[type="radio"]`
57 | - Structure: ``
58 | - Sizes: `.input-radio-small`, `.input-radio-xsmall` (default: 32px)
59 | - States: Default, Hover (blue border), Checked (blue filled circle + white inner dot), Disabled
60 | - Example: ``
61 |
62 | ### Tag
63 | - Base: `.tag` or `.tag.default`
64 | - Variants: `.tag.secondary`, `.tag.outline`, `.tag.success`, `.tag.error`, `.tag.warning`, `.tag.info`
65 | - States: `.hover`, `.focus`, `.active`
66 | - Example: `Completed`
67 |
68 | ### Icon
69 | - Base: `.icon`
70 | - Icon names: `.icon-[name]` (e.g., `.icon-jobs`, `.icon-academy`)
71 | - Sizes: `.icon-small` (16px), `.icon-medium` (24px, default), `.icon-large` (32px), `.icon-xlarge` (48px)
72 | - Colors: `.icon-primary`, `.icon-secondary`, `.icon-success`, `.icon-danger`, `.icon-warning`
73 | - Example: ``
74 |
75 | ### Dropdown (JS Component)
76 | - Import: `import Dropdown from '/design-system/components/dropdown/dropdown.js'`
77 | - Initialize: `new Dropdown(selector, { items, placeholder, onSelect, ... })`
78 | - Methods: `getValue()`, `setValue(value)`, `open()`, `close()`, `toggleOpen()`, `destroy()`
79 |
80 | ### Horizontal Cards (JS Component)
81 | - Import: `import HorizontalCards from '/design-system/components/horizontal-cards/horizontal-cards.js'`
82 | - Initialize: `new HorizontalCards(selector, { cards, showNavigation, onCardChange, ... })`
83 | - Cards: Array of objects with `title` (HTML), `description` (HTML), `actionHtml`, or `actionPlaceholder`
84 | - Methods: `getCurrentIndex()`, `getCurrentCard()`, `scrollToNext()`, `scrollToPrevious()`, `scrollToIndex(index)`, `destroy()`
85 | - Features: Horizontal scrolling, card centering, navigation buttons, keyboard/touch support
86 |
87 | ### Numeric Slider (JS Component)
88 | - Import: `import NumericSlider from '/design-system/components/numeric-slider/numeric-slider.js'`
89 | - Initialize: `new NumericSlider(selector, { type, min, max, value, showInputs, theme, ... })`
90 | - Types: `'single'` (one handle) or `'range'` (two handles)
91 | - Methods: `getValue()`, `setValue(value)`, `setDisabled(disabled)`, `destroy()`
92 | - Themes: `'default'` (neutral track, primary filled/handles) or `'primary'` (all primary)
93 | - Can override individual parts: `trackTheme`, `filledTheme`, `handleTheme`
94 |
95 | ### Modal (JS Component)
96 | - Import: `import Modal from '/design-system/components/modal/modal.js'`
97 | - Initialize: `new Modal({ size, title, content, footerButtons, ... })`
98 | - Sizes: `'small'` (400px), `'medium'` (600px), `'large'` (900px), `'xlarge'` (1200px)
99 | - Content: HTML string, DOM element, CSS selector (e.g., `'#my-content'`), or template content
100 | - Methods: `open()`, `close()`, `updateContent(content)`, `updateTitle(title)`, `destroy()`
101 | - Footer buttons: Array of `{label, type, onClick}` configs
102 | - Help Modal: `Modal.createHelpModal({ title, content, ... })` - Convenience method for help/documentation modals (xlarge size, footer with close button, styled for help content)
103 |
104 | ## Best Practices
105 |
106 | 1. Always use semantic color tokens (e.g., `--Colors-Text-Body-Default`) over base scales
107 | 2. Components support dark mode automatically via `prefers-color-scheme`
108 | 3. Use spacing tokens for consistent padding/margins
109 | 4. Include component CSS files after foundation CSS files
110 | 5. Icons use `mask-image` with `background-color` for color control
111 |
112 | ## File Structure
113 |
114 | ```
115 | design-system/
116 | ├── colors/colors.css
117 | ├── spacing/spacing.css
118 | ├── typography/typography.css
119 | └── components/
120 | ├── button/button.css
121 | ├── boxes/boxes.css
122 | ├── dropdown/dropdown.css + dropdown.js
123 | ├── horizontal-cards/horizontal-cards.css + horizontal-cards.js
124 | ├── icons/icons.css
125 | ├── input/input.css
126 | ├── modal/modal.css + modal.js
127 | ├── numeric-slider/numeric-slider.css + numeric-slider.js
128 | └── tags/tags.css
129 | ```
130 |
131 |
--------------------------------------------------------------------------------
/components/numeric-slider/numeric-slider.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Numeric Slider Component Styles
3 | * Matches CodeSignal Design System
4 | */
5 |
6 | :root {
7 | /* Slider Variables - Light Mode */
8 | --Colors-Slider-Track-Neutral: var(--Colors-Base-Neutral-200);
9 | --Colors-Slider-Track-Primary: var(--Colors-Base-Primary-100);
10 | --Colors-Slider-Thumb-Neutral: var(--Colors-Base-Neutral-650);
11 | --Colors-Slider-Thumb-Primary: var(--Colors-Base-Primary-600);
12 | --Colors-Slider-Handle-Neutral: var(--Colors-Base-Neutral-400);
13 | --Colors-Slider-Handle-Neutral-Inner: var(--Colors-Base-Neutral-100);
14 | --Colors-Slider-Handle-Primary: var(--Colors-Base-Primary-400);
15 | --Colors-Slider-Handle-Primary-Inner: var(--Colors-Base-Primary-100);
16 | --Colors-Slider-Handle-Marker: var(--Colors-Base-Primary-600);
17 | --Colors-Slider-Handle-Marker-Inner: var(--Colors-Base-Primary-400);
18 | --Colors-Slider-Handle-Hover: var(--Colors-Base-Primary-600);
19 | --Colors-Slider-Handle-Pressed: var(--Colors-Base-Primary-700);
20 | --Colors-Input-Border-Default: var(--Colors-Base-Neutral-200);
21 | }
22 |
23 | @media (prefers-color-scheme: dark) {
24 | :root {
25 | /* Slider Variables - Dark Mode */
26 | --Colors-Slider-Track-Neutral: rgba(6, 12, 28, 0.5);
27 | --Colors-Slider-Track-Primary: rgba(6, 12, 28, 0.5);
28 | --Colors-Slider-Thumb-Neutral: var(--Colors-Base-Neutral-800);
29 | --Colors-Slider-Thumb-Primary: var(--Colors-Base-Primary-800);
30 | --Colors-Slider-Handle-Neutral: var(--Colors-Base-Primary-600);
31 | --Colors-Slider-Handle-Neutral-Inner: var(--Colors-Base-Primary-350);
32 | --Colors-Slider-Handle-Primary: var(--Colors-Base-Primary-600);
33 | --Colors-Slider-Handle-Primary-Inner: var(--Colors-Base-Primary-350);
34 | --Colors-Slider-Handle-Marker: var(--Colors-Base-Primary-600);
35 | --Colors-Slider-Handle-Marker-Inner: var(--Colors-Base-Primary-350);
36 | --Colors-Slider-Handle-Hover: var(--Colors-Base-Primary-600);
37 | --Colors-Slider-Handle-Pressed: var(--Colors-Base-Primary-700);
38 | --Colors-Input-Border-Default: var(--Colors-Base-Neutral-1150);
39 | }
40 | }
41 |
42 | .numeric-slider-container {
43 | position: relative;
44 | display: flex;
45 | align-items: center;
46 | gap: var(--UI-Spacing-spacing-xs);
47 | width: 100%;
48 | }
49 |
50 | .numeric-slider-container.with-inputs {
51 | gap: var(--UI-Spacing-spacing-xs);
52 | }
53 |
54 | /* Input fields - extends input component styles */
55 | .numeric-slider-input {
56 | flex-shrink: 0;
57 | min-width: 28px;
58 | max-width: 30px;
59 | height: var(--UI-Input-sm);
60 | text-align: center;
61 | font-weight: 500;
62 | padding-left: var(--UI-Spacing-spacing-mxs);
63 | padding-right: var(--UI-Spacing-spacing-mxs);
64 | padding-top: var(--UI-Spacing-spacing-none);
65 | padding-bottom: var(--UI-Spacing-spacing-none);
66 | border: 1.5px solid var(--Colors-Input-Border-Default, #394563);
67 | }
68 |
69 | /* Hide spinner buttons for numeric slider inputs */
70 | /* WebKit browsers (Chrome, Safari, Edge) */
71 | .numeric-slider-input::-webkit-inner-spin-button,
72 | .numeric-slider-input::-webkit-outer-spin-button {
73 | -webkit-appearance: none;
74 | appearance: none;
75 | margin: 0;
76 | display: none;
77 | }
78 |
79 | /* Firefox */
80 | .numeric-slider-input {
81 | -moz-appearance: textfield;
82 | appearance: textfield;
83 | }
84 |
85 | /* Slider wrapper */
86 | .numeric-slider-wrapper {
87 | position: relative;
88 | flex: 1;
89 | height: 16px;
90 | display: flex;
91 | align-items: center;
92 | cursor: pointer;
93 | }
94 |
95 | /* Slider track */
96 | .numeric-slider-track {
97 | position: relative;
98 | width: 100%;
99 | height: 8px;
100 | border-radius: var(--UI-Radius-radius-s);
101 | background: var(--Colors-Slider-Track-Neutral);
102 | transition: background-color 0.2s ease;
103 | }
104 |
105 | .numeric-slider-track.theme-primary {
106 | background: var(--Colors-Slider-Track-Primary);
107 | }
108 |
109 | /* Filled track (between handles for range, or from start to handle for single) */
110 | .numeric-slider-filled {
111 | position: absolute;
112 | height: 8px;
113 | border-radius: var(--UI-Radius-radius-s);
114 | background: var(--Colors-Slider-Thumb-Neutral);
115 | transition: background-color 0.2s ease;
116 | pointer-events: none;
117 | }
118 |
119 | .numeric-slider-filled.theme-primary {
120 | background: var(--Colors-Slider-Thumb-Primary);
121 | }
122 |
123 | /* Slider handle */
124 | .numeric-slider-handle {
125 | position: absolute;
126 | width: 16px;
127 | height: 16px;
128 | top: -4px;
129 | border-radius: 50%;
130 | background: var(--Colors-Slider-Handle-Neutral);
131 | border: none;
132 | cursor: grab;
133 | transform: translateX(-50%);
134 | transition: transform 0.1s ease, background-color 0.2s ease, box-shadow 0.2s ease;
135 | z-index: 2;
136 | display: flex;
137 | align-items: center;
138 | justify-content: center;
139 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
140 | }
141 |
142 | .numeric-slider-handle::before {
143 | content: '';
144 | position: absolute;
145 | width: 4px;
146 | height: 4px;
147 | border-radius: 50%;
148 | background: var(--Colors-Slider-Handle-Neutral-Inner);
149 | }
150 |
151 | .numeric-slider-handle.theme-primary {
152 | background: var(--Colors-Slider-Handle-Primary);
153 | }
154 |
155 | .numeric-slider-handle.theme-primary::before {
156 | background: var(--Colors-Slider-Handle-Primary-Inner);
157 | }
158 |
159 | /* Marker style handles always use marker colors, regardless of theme */
160 | .numeric-slider-handle.style-marker {
161 | background: var(--Colors-Slider-Handle-Marker);
162 | }
163 |
164 | .numeric-slider-handle.style-marker::before {
165 | background: var(--Colors-Slider-Handle-Marker-Inner);
166 | }
167 |
168 | /* Ensure marker style overrides theme-primary */
169 | .numeric-slider-handle.style-marker.theme-primary {
170 | background: var(--Colors-Slider-Handle-Marker);
171 | }
172 |
173 | .numeric-slider-handle.style-marker.theme-primary::before {
174 | background: var(--Colors-Slider-Handle-Marker-Inner);
175 | }
176 |
177 | .numeric-slider-handle:hover {
178 | transform: translateX(-50%) scale(1.25);
179 | background: var(--Colors-Slider-Handle-Hover);
180 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
181 | }
182 |
183 | .numeric-slider-handle:active,
184 | .numeric-slider-handle.dragging {
185 | cursor: grabbing;
186 | transform: translateX(-50%) scale(1.25);
187 | background: var(--Colors-Slider-Handle-Pressed);
188 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
189 | }
190 |
191 | .numeric-slider-handle:focus {
192 | outline: 2px solid var(--Colors-Primary-Medium);
193 | outline-offset: 2px;
194 | }
195 |
196 | .numeric-slider-handle:focus:not(:focus-visible) {
197 | outline: none;
198 | }
199 |
200 | /* Disabled state */
201 | .numeric-slider-container.disabled {
202 | opacity: 0.5;
203 | pointer-events: none;
204 | cursor: not-allowed;
205 | }
206 |
207 | .numeric-slider-container.disabled .numeric-slider-handle {
208 | cursor: not-allowed;
209 | }
210 |
211 | /* Hide inputs when configured */
212 | .numeric-slider-container.hide-inputs .numeric-slider-input {
213 | display: none;
214 | }
215 |
216 |
217 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Design System Test Bed
7 |
8 |
9 |
10 |
11 |
12 |
94 |
95 |
96 |
97 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/typography/typography.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Founders Grotesk';
3 | src: url('/design-system/fonts/FoundersGrotesk-Light.woff2') format('woff2'),
4 | url('/fonts/FoundersGrotesk-Light.woff2') format('woff2'),
5 | url('../fonts/FoundersGrotesk-Light.woff') format('woff');
6 | font-weight: 300;
7 | font-style: normal;
8 | -webkit-font-smoothing: antialiased;
9 | }
10 |
11 | @font-face {
12 | font-family: 'Founders Grotesk';
13 | src: url('/design-system/fonts/FoundersGrotesk-Light-Italic.woff2') format('woff2'),
14 | url('/fonts/FoundersGrotesk-Light-Italic.woff2') format('woff2'),
15 | url('../fonts/FoundersGrotesk-Light-Italic.woff') format('woff');
16 | font-weight: 300;
17 | font-style: italic;
18 | -webkit-font-smoothing: antialiased;
19 | }
20 |
21 | @font-face {
22 | font-family: 'Founders Grotesk';
23 | src: url('/design-system/fonts/FoundersGrotesk-Medium.woff2') format('woff2'),
24 | url('/fonts/FoundersGrotesk-Medium.woff2') format('woff2'),
25 | url('../fonts/FoundersGrotesk-Medium.woff') format('woff');
26 | font-weight: 500;
27 | font-style: normal;
28 | -webkit-font-smoothing: antialiased;
29 | }
30 |
31 | @font-face {
32 | font-family: 'JetBrains Mono';
33 | src: url('/design-system/fonts/JetBrainsMono-Medium-Italic.woff2') format('woff2'),
34 | url('/fonts/JetBrainsMono-Medium-Italic.woff2') format('woff2'),
35 | url('../fonts/JetBrainsMono-Medium-Italic.woff') format('woff');
36 | font-weight: 500;
37 | font-style: italic;
38 | -webkit-font-smoothing: antialiased;
39 | }
40 |
41 | @font-face {
42 | font-family: 'Founders Grotesk';
43 | src: url('/design-system/fonts/FoundersGrotesk-Medium-Italic.woff2') format('woff2'),
44 | url('/fonts/FoundersGrotesk-Medium-Italic.woff2') format('woff2'),
45 | url('../fonts/FoundersGrotesk-Medium-Italic.woff') format('woff');
46 | font-weight: 500;
47 | font-style: italic;
48 | -webkit-font-smoothing: antialiased;
49 | }
50 |
51 | :root {
52 | --body-family: "Work Sans";
53 | --body-elegant-family: "Founders Grotesk";
54 | --heading-family: "Founders Grotesk";
55 | --label-family: "Work Sans";
56 | --label-number-family: "Work Sans";
57 | --code-family: "JetBrains Mono";
58 |
59 | --Fonts-Body-Default-xxs: 13px;
60 | --Fonts-Body-Default-xs: 14px;
61 | --Fonts-Body-Default-sm: 15px;
62 | --Fonts-Body-Default-md: 16px;
63 | --Fonts-Body-Default-lg: 17px;
64 | --Fonts-Body-Default-xl: 19px;
65 | --Fonts-Body-Default-xxl: 21px;
66 | --Fonts-Body-Default-xxxl: 24px;
67 |
68 | --Fonts-Body-Elegant-xxs: 22px;
69 | --Fonts-Body-Elegant-xs: 26px;
70 | --Fonts-Body-Elegant-sm: 32px;
71 | --Fonts-Body-Elegant-md: 38px;
72 |
73 | --Fonts-Headlines-xxl: 64px;
74 | --Fonts-Headlines-xl: 48px;
75 | --Fonts-Headlines-l: 38px;
76 | --Fonts-Headlines-md: 32px;
77 | --Fonts-Headlines-sm: 24px;
78 | --Fonts-Headlines-xsm: 22px;
79 | --Fonts-Headlines-xxsm: 22px;
80 | --Fonts-Headlines-xxxsm: 16px;
81 |
82 | --Fonts-Special-xl: 15px;
83 | --Fonts-Special-lg: 14px;
84 | --Fonts-Special-sm: 12px;
85 | --Fonts-Special-xs: 11px;
86 | --Fonts-Special-xxs: 10px;
87 | }
88 |
89 | /* Base body class with shared properties */
90 | .body,
91 | .body-xxsmall,
92 | .body-xsmall,
93 | .body-small,
94 | .body-medium,
95 | .body-large,
96 | .body-xlarge,
97 | .body-xxlarge,
98 | .body-xxxlarge {
99 | font-family: var(--body-family);
100 | font-style: normal;
101 | font-weight: 400;
102 | -webkit-font-smoothing: antialiased;
103 | }
104 |
105 | .body-xxsmall {
106 | font-size: var(--Fonts-Body-Default-xxs);
107 | line-height: 135%;
108 | letter-spacing: -0.13px;
109 | }
110 |
111 | .body-xsmall {
112 | font-size: var(--Fonts-Body-Default-xs);
113 | line-height: 135%;
114 | letter-spacing: -0.14px;
115 | }
116 |
117 | .body-small {
118 | font-size: var(--Fonts-Body-Default-sm);
119 | line-height: 135%;
120 | letter-spacing: -0.15px;
121 | }
122 |
123 | .body-medium {
124 | font-size: var(--Fonts-Body-Default-md);
125 | line-height: 145%;
126 | letter-spacing: -0.16px;
127 | }
128 |
129 | .body-large {
130 | font-size: var(--Fonts-Body-Default-lg);
131 | line-height: 140%;
132 | letter-spacing: -0.17px;
133 | }
134 |
135 | .body-xlarge {
136 | font-size: var(--Fonts-Body-Default-xl);
137 | line-height: 140%;
138 | letter-spacing: -0.19px;
139 | }
140 |
141 | .body-xxlarge {
142 | font-size: var(--Fonts-Body-Default-xxl);
143 | line-height: 140%;
144 | letter-spacing: -0.21px;
145 | }
146 |
147 | .body-xxxlarge {
148 | font-size: var(--Fonts-Body-Default-xxxl);
149 | line-height: 140%;
150 | letter-spacing: -0.24px;
151 | }
152 |
153 | .body-elegant,
154 | .body-elegant-xxsmall,
155 | .body-elegant-xsmall,
156 | .body-elegant-small,
157 | .body-elegant-medium {
158 | font-family: var(--body-elegant-family);
159 | font-style: normal;
160 | font-weight: 300;
161 | }
162 |
163 | .body-elegant-xxsmall {
164 | font-size: var(--Fonts-Body-Elegant-xxs);
165 | line-height: 138%;
166 | letter-spacing: -0.22px;
167 | }
168 |
169 | .body-elegant-xsmall {
170 | font-size: var(--Fonts-Body-Elegant-xs);
171 | line-height: 138%;
172 | letter-spacing: -0.26px;
173 | }
174 |
175 | .body-elegant-small {
176 | font-size: var(--Fonts-Body-Elegant-sm);
177 | line-height: 138%;
178 | letter-spacing: -0.32px;
179 | }
180 |
181 | .body-elegant-medium {
182 | font-size: var(--Fonts-Body-Elegant-md);
183 | line-height: 138%;
184 | letter-spacing: -0.38px;
185 | }
186 |
187 |
188 | .heading,
189 | .heading-xxlarge,
190 | .heading-xlarge,
191 | .heading-large,
192 | .heading-medium,
193 | .heading-small,
194 | .heading-xsmall,
195 | .heading-xxsmall {
196 | font-family: var(--heading-family);
197 | font-style: normal;
198 | font-weight: 500;
199 | -webkit-font-smoothing: antialiased;
200 | }
201 |
202 | .heading-xxlarge {
203 | font-size: var(--Fonts-Headlines-xxl);
204 | line-height: 100%;
205 | letter-spacing: -0.64px;
206 | }
207 |
208 | .heading-xlarge {
209 | font-size: var(--Fonts-Headlines-xl);
210 | line-height: 100%;
211 | letter-spacing: -0.48px;
212 | }
213 |
214 | .heading-large {
215 | font-size: var(--Fonts-Headlines-l);
216 | line-height: 110%;
217 | letter-spacing: -0.38px;
218 | }
219 |
220 | .heading-medium {
221 | font-size: var(--Fonts-Headlines-md);
222 | line-height: 116%;
223 | letter-spacing: -0.32px;
224 | }
225 |
226 | .heading-small {
227 | font-size: var(--Fonts-Headlines-sm);
228 | line-height: 120%;
229 | letter-spacing: -0.24px;
230 | }
231 |
232 | .heading-xsmall {
233 | font-size: var(--Fonts-Headlines-xsm);
234 | line-height: 120%;
235 | letter-spacing: -0.22px;
236 | }
237 |
238 | .heading-xxsmall {
239 | font-size: var(--Fonts-Headlines-xxsm);
240 | line-height: 120%;
241 | letter-spacing: -0.22px;
242 | }
243 |
244 | .heading-xxxsmall {
245 | font-size: var(--Fonts-Headlines-xxxsm);
246 | line-height: 120%;
247 | letter-spacing: -0.16px;
248 | }
249 |
250 | .label,
251 | .label-large,
252 | .label-medium,
253 | .label-small {
254 | font-family: var(--label-family);
255 | font-style: normal;
256 | font-weight: 600;
257 | text-transform: uppercase;
258 | -webkit-font-smoothing: antialiased;
259 | }
260 |
261 | .label-large {
262 | font-size: var(--Fonts-Special-lg);
263 | line-height: normal;
264 | letter-spacing: 1.4px;
265 | }
266 |
267 | .label-medium {
268 | font-size: var(--Fonts-Special-xs);
269 | line-height: normal;
270 | letter-spacing: 1.1px;
271 | }
272 |
273 | .label-small {
274 | font-size: var(--Fonts-Special-xxs);
275 | line-height: 82%;
276 | letter-spacing: 1px;
277 | }
278 |
279 | .label-number,
280 | .label-number-xsmall,
281 | .label-number-small,
282 | .label-number-medium,
283 | .label-number-large {
284 | font-family: var(--label-number-family);
285 | font-style: normal;
286 | font-weight: 500;
287 | line-height: 83%;
288 | }
289 |
290 | .label-number-xsmall {
291 | font-size: var(--Fonts-Special-xs);
292 | }
293 |
294 | .label-number-small {
295 | font-size: var(--Fonts-Special-sm, 12px);
296 | }
297 |
298 | .label-number-medium {
299 | font-size: var(--Fonts-Special-lg);
300 | }
301 |
302 | .label-number-large {
303 | font-size: var(--Fonts-Special-xl);
304 | }
305 |
306 | .code,
307 | .code-small,
308 | .code-medium,
309 | .code-large {
310 | color: var(--Colors-Text-Body-Medium);
311 | font-family: var(--code-family);
312 | font-style: normal;
313 | font-weight: 600;
314 | line-height: 140%;
315 | -webkit-font-smoothing: antialiased;
316 | }
317 |
318 | .code {
319 | font-size: var(--Fonts-Body-Default-xl);
320 | }
321 |
322 | .code-small {
323 | font-size: var(--Fonts-Special-xs);
324 | }
325 |
326 | .code-medium {
327 | font-size: var(--Fonts-Body-Default-md);
328 | }
329 |
330 | .code-large {
331 | font-size: var(--Fonts-Body-Default-lg);
332 | }
333 |
334 | .strong {
335 | font-weight: 500;
336 | }
337 |
--------------------------------------------------------------------------------
/components/dropdown/dropdown.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Dropdown Component Styles
3 | * Matches CodeSignal Design System - Light Mode Default Toggle
4 | */
5 |
6 | :root {
7 | /* Colors - Light Mode */
8 | --Colors-Dropdown-Toggle-Text: var(--Colors-Text-Body-Medium);
9 | --Colors-Dropdown-Icon: var(--Colors-Base-Neutral-800);
10 | --Colors-Dropdown-Icon-Hover: var(--Colors-Icon-Medium);
11 | --Colors-Dropdown-Icon-Active: var(--Colors-Icon-Primary);
12 | --Colors-Dropdown-Panel: var(--Colors-Backgrounds-Main-Top);
13 | --Colors-Dropdown-Panel-Stroke: var(--Colors-Stroke-Default);
14 | --Colors-Dropdown-Toggle: var(--Colors-Backgrounds-Main-Top);
15 | --Colors-Dropdown-Toggle-Background: var(--Colors-Backgrounds-Main-Top);
16 | --Colors-Dropdown-Toggle-Stroke: var(--Colors-Stroke-Default);
17 | --Colors-Dropdown-Toggle-Hover: var(--Colors-Backgrounds-Main-Top);
18 | --Colors-Dropdown-Toggle-Hover-Border: var(--Colors-Stroke-Medium);
19 | --Colors-Dropdown-Panel-Stroke-Hover: var(--Colors-Stroke-Medium);
20 | --Colors-Shadow-Float: rgba(22, 44, 96, 0.12);
21 | --Colors-Shadow-Medium-Soft: rgba(22, 44, 96, 0.12);
22 | --Colors-Menu-Option-Hover: var(--Colors-Base-Neutral-20);
23 | --Colors-Menu-Option-Background: var(--Colors-Backgrounds-Main-Top);
24 | --Colors-Menu-Option-Background-Selected: var(--Colors-Primary-Lightest);
25 | --Colors-Menu-Tab-Active: var(--Colors-Backgrounds-Main-Top);
26 |
27 | /* Spacing */
28 | --Menu-Item-Padding-Horizontal: var(--UI-Spacing-spacing-mxs);
29 |
30 | /* Radius */
31 | --Menu-Item-Roundness: var(--UI-Radius-radius-s);
32 | }
33 |
34 | /* Dark Mode */
35 | @media (prefers-color-scheme: dark) {
36 | :root {
37 | /* Colors - Dark Mode */
38 | --Colors-Dropdown-Toggle-Text: var(--Colors-Text-Body-Default);
39 | --Colors-Dropdown-Icon-Hover: var(--Colors-Icon-Medium);
40 | --Colors-Dropdown-Icon-Active: var(--Colors-Icon-Primary);
41 | --Colors-Dropdown-Panel: var(--Colors-Backgrounds-Main-Medium);
42 | --Colors-Dropdown-Panel-Stroke: rgba(45, 56, 85, 0.00);
43 | --Colors-Dropdown-Toggle: var(--Colors-Base-Neutral-1200);
44 | --Colors-Dropdown-Toggle-Background: var(--Colors-Backgrounds-Main-Medium);
45 | --Colors-Dropdown-Toggle-Stroke: rgba(45, 56, 85, 0.00);
46 | --Colors-Dropdown-Toggle-Hover: var(--Colors-Base-Neutral-1050);
47 | --Colors-Dropdown-Toggle-Hover-Border: rgba(193, 199, 215, 0.35);
48 | --Colors-Dropdown-Panel-Stroke-Hover: var(--Colors-Stroke-Strong);
49 | --Colors-Shadow-Float: rgba(13, 21, 40, 0.3);
50 | --Colors-Shadow-Medium-Soft: rgba(13, 21, 40, 0.4);
51 | --Colors-Menu-Option-Hover: rgba(193, 199, 215, 0.1);
52 | --Colors-Menu-Option-Background: var(--Colors-Backgrounds-Main-Medium);
53 | --Colors-Menu-Option-Background-Selected: var(--Colors-Base-Neutral-1050);
54 | --Colors-Menu-Tab-Active: var(--Colors-Text-Body-Strong);
55 | }
56 | }
57 |
58 | .dropdown-container {
59 | position: relative;
60 | display: inline-block;
61 | width: 100%;
62 | }
63 |
64 | .dropdown-container.grow-to-fit {
65 | width: auto;
66 | min-width: 200px;
67 | }
68 |
69 | /* Toggle Button */
70 | .dropdown-toggle {
71 | width: 100%;
72 | height: var(--UI-Input-sm);
73 | background: var(--Colors-Dropdown-Toggle) !important;
74 | border: 1px solid var(--Colors-Dropdown-Toggle-Stroke) !important;
75 | border-radius: var(--UI-Radius-radius-s) !important;
76 | padding: 0;
77 | cursor: pointer;
78 | position: relative;
79 | transition: border-color 0.2s ease, box-shadow 0.2s ease;
80 | font-family: "Work Sans", ui-sans-serif, system-ui, sans-serif;
81 | box-shadow: 0px 2px 3px 0px var(--Colors-Shadow-Medium-Soft);
82 | }
83 |
84 | .dropdown-toggle:hover {
85 | background: var(--Colors-Dropdown-Toggle-Hover) !important;
86 | border-color: var(--Colors-Dropdown-Panel-Stroke-Hover) !important;
87 | box-shadow: 0 2px 8px rgba(76, 90, 123, 0.08);
88 | }
89 |
90 | .dropdown-toggle:focus {
91 | outline: none;
92 | }
93 |
94 | .dropdown-toggle:focus-visible {
95 | outline: 2px solid var(--Colors-Primary-Medium);
96 | outline-offset: 2px;
97 | }
98 |
99 | .dropdown-toggle-content {
100 | display: flex;
101 | align-items: center;
102 | justify-content: space-between;
103 | gap: var(--UI-Spacing-spacing-mxs);
104 | height: 100%;
105 | padding: var(--UI-Spacing-spacing-mxs) var(--UI-Spacing-spacing-mxs) var(--UI-Spacing-spacing-mxs) var(--UI-Spacing-spacing-ms);
106 | box-sizing: border-box;
107 | }
108 |
109 | .dropdown-toggle-label {
110 | flex: 1;
111 | text-align: left;
112 | font-weight: 500;
113 | vertical-align: middle;
114 | color: var(--Colors-Dropdown-Toggle-Text);
115 | white-space: nowrap;
116 | overflow: hidden;
117 | text-overflow: ellipsis;
118 | transition: color 0.2s ease;
119 | }
120 |
121 | .dropdown-container.grow-to-fit .dropdown-toggle-label {
122 | overflow: visible;
123 | text-overflow: clip;
124 | }
125 |
126 | .dropdown-toggle:hover .dropdown-toggle-label {
127 | color: var(--Colors-Text-Body-Strong);
128 | }
129 |
130 | .dropdown-container.open .dropdown-toggle-label {
131 | color: var(--Colors-Text-Body-Strong);
132 | }
133 |
134 | .dropdown-toggle-icon {
135 | display: block;
136 | flex-shrink: 0;
137 | width: var(--UI-Spacing-spacing-ml);
138 | height: var(--UI-Spacing-spacing-ml);
139 | position: relative;
140 | }
141 |
142 | .dropdown-toggle-icon svg {
143 | display: block;
144 | width: 10px;
145 | height: 5px;
146 | position: absolute;
147 | top: 7.5px;
148 | left: 5px;
149 | }
150 |
151 | .dropdown-toggle-icon svg path {
152 | transition: stroke 0.2s ease;
153 | }
154 |
155 | .dropdown-toggle:hover .dropdown-toggle-icon svg path {
156 | stroke: var(--Colors-Dropdown-Icon-Hover);
157 | }
158 |
159 | .dropdown-container.open .dropdown-toggle-icon svg path {
160 | stroke: var(--Colors-Dropdown-Icon-Active);
161 | }
162 |
163 | /* Dropdown Menu Panel */
164 | .dropdown-menu {
165 | position: absolute;
166 | top: calc(100% + 4px);
167 | left: 0;
168 | width: 240px;
169 | height: auto;
170 | max-height: 284px;
171 | background: var(--Colors-Dropdown-Panel);
172 | border: 1px solid var(--Colors-Dropdown-Panel-Stroke);
173 | border-radius: var(--UI-Radius-radius-s);
174 | box-shadow: 0px 12px 38px 0px var(--Colors-Shadow-Float);
175 | z-index: 1000;
176 | display: none;
177 | overflow: visible;
178 | opacity: 1;
179 | }
180 |
181 | .dropdown-container.grow-to-fit .dropdown-menu {
182 | width: 100%;
183 | min-width: 200px;
184 | }
185 |
186 | .dropdown-container.open .dropdown-menu {
187 | display: block;
188 | }
189 |
190 | .dropdown-menu-list {
191 | display: flex;
192 | flex-direction: column;
193 | gap: var(--UI-Spacing-spacing-xxs);
194 | padding: var(--UI-Spacing-spacing-mxs);
195 | overflow-y: visible;
196 | }
197 |
198 | /* Menu Items */
199 | .dropdown-menu-item {
200 | display: flex;
201 | align-items: center;
202 | width: 216px;
203 | height: 40px !important;
204 | min-height: 40px !important;
205 | flex-shrink: 0 !important;
206 | padding: var(--UI-Spacing-spacing-none) var(--Menu-Item-Padding-Horizontal) !important;
207 | gap: var(--UI-Spacing-spacing-s);
208 | background: var(--Colors-Dropdown-Panel) !important;
209 | border: none !important;
210 | border-radius: var(--Menu-Item-Roundness) !important;
211 | cursor: pointer;
212 | font-family: "Work Sans", ui-sans-serif, system-ui, sans-serif;
213 | font-size: 16px;
214 | font-weight: 400;
215 | line-height: 24px;
216 | letter-spacing: -0.24px;
217 | color: var(--Colors-Text-Body-Default);
218 | text-align: left;
219 | transition: background-color 0.15s ease;
220 | box-sizing: border-box;
221 | opacity: 1;
222 | }
223 |
224 | .dropdown-container.grow-to-fit .dropdown-menu-item {
225 | width: 100%;
226 | min-width: 216px;
227 | }
228 |
229 | .dropdown-menu-item:hover:not(.selected) {
230 | background: var(--Colors-Menu-Option-Hover) !important;
231 | }
232 |
233 | .dropdown-menu-item:focus {
234 | outline: 2px solid var(--Colors-Primary-Medium);
235 | outline-offset: -2px;
236 | }
237 |
238 | .dropdown-menu-item.selected {
239 | width: 216px;
240 | height: 40px;
241 | border-radius: var(--Menu-Item-Roundness);
242 | padding: var(--UI-Spacing-spacing-none) var(--Menu-Item-Padding-Horizontal) !important;
243 | gap: var(--UI-Spacing-spacing-s);
244 | opacity: 1;
245 | background: var(--Colors-Menu-Option-Background-Selected) !important;
246 | font-family: "Work Sans", ui-sans-serif, system-ui, sans-serif;
247 | font-weight: 500;
248 | font-style: normal;
249 | font-size: 16px;
250 | line-height: 24px;
251 | letter-spacing: -0.24px;
252 | color: var(--Colors-Text-Body-Strong) !important;
253 | }
254 |
255 | .dropdown-menu-item.selected .dropdown-menu-item-label {
256 | color: var(--Colors-Text-Body-Strong) !important;
257 | font-weight: 500;
258 | }
259 |
260 | .dropdown-menu-item-content {
261 | display: flex;
262 | align-items: center;
263 | gap: var(--UI-Spacing-spacing-s);
264 | width: 100%;
265 | }
266 |
267 | .dropdown-menu-item-checkmark {
268 | display: flex;
269 | align-items: center;
270 | justify-content: center;
271 | flex-shrink: 0;
272 | width: 16px;
273 | height: 16px;
274 | position: relative;
275 | }
276 |
277 | .dropdown-menu-item-checkmark svg {
278 | display: block;
279 | width: 16px;
280 | height: 16px;
281 | position: relative;
282 | }
283 |
284 | .dropdown-menu-item-label {
285 | flex: 1;
286 | white-space: nowrap;
287 | overflow: hidden;
288 | text-overflow: ellipsis;
289 | }
290 |
291 | .dropdown-container.grow-to-fit .dropdown-menu-item-label {
292 | overflow: visible;
293 | text-overflow: clip;
294 | }
295 |
296 | /* Responsive adjustments */
297 | @media (max-width: 768px) {
298 | .dropdown-menu {
299 | width: 100%;
300 | min-width: 240px;
301 | }
302 | }
303 |
304 |
--------------------------------------------------------------------------------
/components/input/README.md:
--------------------------------------------------------------------------------
1 | # Input Component
2 |
3 | A versatile input component matching the CodeSignal Design System. Supports text and number inputs with various states and styling options.
4 |
5 | ## Usage
6 |
7 | Import the CSS file in your HTML or CSS:
8 |
9 | ```html
10 |
11 | ```
12 |
13 | or
14 |
15 | ```css
16 | @import url('/design-system/components/input/input.css');
17 | ```
18 |
19 | ## Classes
20 |
21 | ### Base Class
22 | - `.input`: The base class required for all input fields.
23 |
24 | ### Input Types
25 | - `type="text"`: Standard text input (default).
26 | - `type="number"`: Numeric input with styled spinner buttons.
27 | - `type="checkbox"`: Checkbox input with custom styling (see Checkbox section below).
28 | - `type="radio"`: Radio button input with custom styling (see Radio section below).
29 |
30 | ### States
31 | The component supports standard pseudo-classes (`:hover`, `:focus`, `:disabled`) and utility classes for manual state application:
32 | - `.hover`: Applies hover state styling (stronger border color).
33 | - `.focus`: Applies focus state styling (primary border color and focus ring).
34 | - `:disabled`: Disabled state (reduced opacity and not-allowed cursor).
35 |
36 | ## HTML Examples
37 |
38 | ```html
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | ## Checkbox Input
62 |
63 | The checkbox component provides a custom-styled checkbox input with support for multiple sizes and states.
64 |
65 | ### Checkbox Structure
66 |
67 | Checkboxes require a specific HTML structure:
68 |
69 | ```html
70 |
77 | ```
78 |
79 | ### Checkbox Sizes
80 |
81 | - **Default**: 32px checkbox box, large label text (17px)
82 | - `.input-checkbox-small`: 26px checkbox box, medium label text (16px)
83 | - `.input-checkbox-xsmall`: 20px checkbox box, small label text (14px)
84 |
85 | ### Checkbox States
86 |
87 | - **Default**: White background with gray border
88 | - **Hover**: Blue border (primary color)
89 | - **Checked**: Blue background with white checkmark icon
90 | - **Disabled**: Reduced opacity (0.45 for box, 0.2 for label)
91 |
92 | ### Checkbox Examples
93 |
94 | ```html
95 |
96 |
103 |
104 |
105 |
112 |
113 |
114 |
121 |
122 |
123 |
130 |
131 |
132 |
139 | ```
140 |
141 | ### Checkbox Features
142 |
143 | - **Custom Styling**: Native checkbox is hidden and replaced with a custom-styled box
144 | - **Checkmark Icon**: White checkmark appears when checked, using SVG mask for crisp rendering
145 | - **Focus Ring**: Accessible focus indicator with primary color ring
146 | - **Accessibility**: Proper label association and keyboard navigation support
147 | - **Responsive**: Adapts to dark mode automatically
148 |
149 | ## Radio Input
150 |
151 | The radio component provides a custom-styled radio button input with support for multiple sizes and states. Radio buttons are used when only one option can be selected from a group.
152 |
153 | ### Radio Structure
154 |
155 | Radio buttons require a specific HTML structure and must share the same `name` attribute to function as a group:
156 |
157 | ```html
158 |
165 | ```
166 |
167 | ### Radio Sizes
168 |
169 | - **Default**: 32px radio circle, large label text (17px)
170 | - `.input-radio-small`: 26px radio circle, medium label text (16px)
171 | - `.input-radio-xsmall`: 20px radio circle, small label text (14px)
172 |
173 | ### Radio States
174 |
175 | - **Default**: White background with gray border
176 | - **Hover**: Blue border (primary color)
177 | - **Checked**: Blue border with blue inner dot
178 | - **Disabled**: Reduced opacity (0.45 for circle, 0.2 for label)
179 |
180 | ### Radio Examples
181 |
182 | ```html
183 |
184 |
191 |
192 |
193 |
200 |
201 |
202 |
203 |
210 |
217 |
224 |
225 |
226 |
227 |
234 |
235 |
236 |
243 |
244 |
245 |
252 | ```
253 |
254 | ### Radio Features
255 |
256 | - **Custom Styling**: Native radio button is hidden and replaced with a custom-styled circle
257 | - **Inner Dot**: Blue dot appears when checked, indicating selection
258 | - **Focus Ring**: Accessible focus indicator with primary color ring
259 | - **Group Behavior**: Radio buttons with the same `name` attribute work as a group (only one can be selected)
260 | - **Accessibility**: Proper label association and keyboard navigation support
261 | - **Responsive**: Adapts to dark mode automatically
262 |
263 | ## Features
264 |
265 | ### Number Input Spinner Buttons
266 | Number inputs (`type="number"`) include styled spinner buttons that:
267 | - Are properly sized and positioned within the input
268 | - Have subtle opacity that increases on hover and focus
269 | - Work consistently across WebKit browsers (Chrome, Safari, Edge) and Firefox
270 | - Maintain the input's width without requiring extra padding
271 |
272 | ### Focus Ring
273 | When focused, inputs display a subtle focus ring using the primary color with reduced opacity for better accessibility.
274 |
275 | ## Dark Mode
276 |
277 | The component automatically adapts to dark mode via the `@media (prefers-color-scheme: dark)` query. All states are optimized for both light and dark themes.
278 |
279 | ## Dependencies
280 |
281 | This component relies on variables from:
282 | - `design-system/colors/colors.css`
283 | - `design-system/spacing/spacing.css`
284 | - `design-system/typography/typography.css`
285 |
286 |
--------------------------------------------------------------------------------
/components/tags/tags.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Tag Component Styles
3 | * Matches CodeSignal Design System
4 | */
5 |
6 | :root {
7 | /* Tag Variables */
8 | --UI-Radius-tag-medium: var(--UI-Radius-radius-s);
9 |
10 | --Colors-Tags-Primary: var(--Colors-Base-Primary-50);
11 | --Colors-Tags-Primary-Text: var(--Colors-Base-Primary-950);
12 | --Colors-Tags-Primary-Stroke: var(--Colors-Base-Primary-250);
13 | --Colors-Tags-Primary-Hover: var(--Colors-Base-Primary-100);
14 | --Colors-Tags-Primary-Focus: rgba(160, 193, 255, 0.20);
15 |
16 | --Colors-Tags-Secondary: var(--Colors-Base-Neutral-150);
17 | --Colors-Tags-Secondary-Text: var(--Colors-Base-Neutral-1200);
18 | --Colors-Tags-Secondary-Stroke: var(--Colors-Base-Neutral-400);
19 | --Colors-Tags-Secondary-Hover: var(--Colors-Base-Neutral-250);
20 | --Colors-Tags-Secondary-Focus: rgba(200, 205, 219, 0.25);
21 |
22 | --Colors-Tags-Outline: var(--Colors-Base-Neutral-00);
23 | --Colors-Tags-Outline-Text: var(--Colors-Base-Neutral-1200);
24 | --Colors-Tags-Outline-Stroke: var(--Colors-Base-Neutral-200);
25 | --Colors-Tags-Outline-Background-Hover: var(--Colors-Base-Neutral-20);
26 | --Colors-Tags-Outline-Hover: var(--Colors-Base-Neutral-400);
27 | --Colors-Tags-Outline-Focus: rgba(200, 205, 219, 0.25);
28 | --Colors-Tags-Outline-Selected: var(--Colors-Base-Neutral-550);
29 |
30 | --Colors-Tags-Success: var(--Colors-Base-Accent-Green-50);
31 | --Colors-Tags-Success-Text: var(--Colors-Base-Accent-Green-1000);
32 | --Colors-Tags-Success-Stroke: var(--Colors-Base-Accent-Green-300);
33 | --Colors-Tags-Success-Hover: var(--Colors-Base-Accent-Green-100);
34 | --Colors-Tags-Success-Focus: rgba(77, 217, 155, 0.16);
35 |
36 | --Colors-Tags-Error: var(--Colors-Base-Accent-Red-50);
37 | --Colors-Tags-Error-Text: var(--Colors-Base-Accent-Red-1000);
38 | --Colors-Tags-Error-Stroke: var(--Colors-Base-Accent-Red-100);
39 | --Colors-Tags-Error-Hover: var(--Colors-Base-Accent-Red-100);
40 | --Colors-Tags-Error-Focus: rgba(255, 169, 180, 0.20);
41 |
42 | --Colors-Tags-Warning: var(--Colors-Base-Accent-Yellow-50);
43 | --Colors-Tags-Warning-Text: var(--Colors-Base-Accent-Yellow-1000);
44 | --Colors-Tags-Warning-Stroke: var(--Colors-Base-Accent-Yellow-200);
45 | --Colors-Tags-Warning-Hover: var(--Colors-Base-Accent-Yellow-100);
46 | --Colors-Tags-Warning-Focus: rgba(255, 220, 102, 0.22);
47 |
48 | --Colors-Tags-Info: var(--Colors-Base-Accent-Sky-Blue-50);
49 | --Colors-Tags-Info-Text: var(--Colors-Base-Accent-Sky-Blue-1000);
50 | --Colors-Tags-Info-Stroke: var(--Colors-Base-Accent-Sky-Blue-300);
51 | --Colors-Tags-Info-Hover: var(--Colors-Base-Accent-Sky-Blue-200);
52 | --Colors-Tags-Info-Focus: rgba(131, 220, 255, 0.25);
53 | }
54 |
55 | @media (prefers-color-scheme: dark) {
56 | :root {
57 | --Colors-Tags-Primary: var(--Colors-Base-Primary-300);
58 | --Colors-Tags-Primary-Text: var(--Colors-Base-Primary-1300);
59 | --Colors-Tags-Primary-Stroke: var(--Colors-Base-Primary-150);
60 | --Colors-Tags-Primary-Hover: var(--Colors-Base-Primary-400);
61 | --Colors-Tags-Primary-Focus: var(--Colors-Base-Neutral-1150);
62 |
63 | --Colors-Tags-Secondary: var(--Colors-Base-Neutral-1150);
64 | --Colors-Tags-Secondary-Text: var(--Colors-Base-Neutral-400);
65 | --Colors-Tags-Secondary-Stroke: var(--Colors-Base-Neutral-950);
66 | --Colors-Tags-Secondary-Hover: var(--Colors-Base-Neutral-1200);
67 | --Colors-Tags-Secondary-Focus: var(--Colors-Base-Neutral-1150);
68 |
69 | --Colors-Tags-Outline: var(--Colors-Base-Neutral-1300);
70 | --Colors-Tags-Outline-Text: var(--Colors-Base-Neutral-450);
71 | --Colors-Tags-Outline-Stroke: var(--Colors-Base-Neutral-1100);
72 | --Colors-Tags-Outline-Background-Hover: var(--Colors-Base-Neutral-1250);
73 | --Colors-Tags-Outline-Hover: var(--Colors-Base-Neutral-900);
74 | --Colors-Tags-Outline-Focus: var(--Colors-Base-Neutral-1150);
75 | --Colors-Tags-Outline-Selected: var(--Colors-Base-Neutral-700);
76 |
77 | --Colors-Tags-Success: var(--Colors-Base-Accent-Green-200);
78 | --Colors-Tags-Success-Text: var(--Colors-Base-Neutral-1300);
79 | --Colors-Tags-Success-Stroke: var(--Colors-Base-Accent-Green-50);
80 | --Colors-Tags-Success-Hover: var(--Colors-Base-Accent-Green-300);
81 | --Colors-Tags-Success-Focus: var(--Colors-Base-Neutral-1150);
82 |
83 | --Colors-Tags-Error: var(--Colors-Base-Accent-Red-100);
84 | --Colors-Tags-Error-Text: var(--Colors-Base-Neutral-1300);
85 | --Colors-Tags-Error-Stroke: var(--Colors-Base-Accent-Red-50);
86 | --Colors-Tags-Error-Hover: var(--Colors-Base-Accent-Red-200);
87 | --Colors-Tags-Error-Focus: var(--Colors-Base-Neutral-1150);
88 |
89 | --Colors-Tags-Warning: var(--Colors-Base-Accent-Yellow-100);
90 | --Colors-Tags-Warning-Text: var(--Colors-Base-Neutral-1300);
91 | --Colors-Tags-Warning-Stroke: var(--Colors-Base-Accent-Yellow-50);
92 | --Colors-Tags-Warning-Hover: var(--Colors-Base-Accent-Yellow-200);
93 | --Colors-Tags-Warning-Focus: var(--Colors-Base-Neutral-1150);
94 |
95 | --Colors-Tags-Info: var(--Colors-Base-Accent-Sky-Blue-300);
96 | --Colors-Tags-Info-Text: var(--Colors-Base-Neutral-1300);
97 | --Colors-Tags-Info-Stroke: var(--Colors-Base-Accent-Sky-Blue-50);
98 | --Colors-Tags-Info-Hover: var(--Colors-Base-Accent-Sky-Blue-200);
99 | --Colors-Tags-Info-Focus: var(--Colors-Base-Neutral-1150);
100 | }
101 | }
102 |
103 | .tag,
104 | .tag.default {
105 | display: inline-flex;
106 | height: var(--UI-Input-sm);
107 | padding: var(--UI-Spacing-spacing-none) var(--UI-Spacing-spacing-ms);
108 | align-items: center;
109 | gap: var(--UI-Spacing-spacing-xs);
110 | flex-shrink: 0;
111 | border-radius: var(--UI-Radius-tag-medium);
112 | background: var(--Colors-Tags-Primary);
113 | border: 1.5px solid transparent;
114 |
115 | color: var(--Colors-Tags-Primary-Text);
116 |
117 | font-family: "Work Sans";
118 | font-size: var(--Fonts-Body-Default-md);
119 | font-style: normal;
120 | font-weight: 500;
121 | line-height: 145%; /* 23.2px */
122 | letter-spacing: -0.24px;
123 | }
124 |
125 | .tag:hover,
126 | .tag.hover,
127 | .tag.default:hover,
128 | .tag.default.hover {
129 | background: var(--Colors-Tags-Primary-Hover);
130 | }
131 |
132 | .tag:active,
133 | .tag.active,
134 | .tag.default:active,
135 | .tag.default.active {
136 | border: 1.5px solid var(--Colors-Tags-Primary-Stroke);
137 | }
138 |
139 | .tag:focus,
140 | .tag.focus,
141 | .tag.default:focus,
142 | .tag.default.focus {
143 | border: 1.5px solid var(--Colors-Tags-Primary-Stroke);
144 | box-shadow: 0 0 0 4px var(--Colors-Tags-Primary-Focus);
145 | }
146 |
147 | .tag.secondary {
148 | background: var(--Colors-Tags-Secondary);
149 | color: var(--Colors-Tags-Secondary-Text);
150 | }
151 |
152 | .tag.secondary:hover,
153 | .tag.secondary.hover {
154 | background: var(--Colors-Tags-Secondary-Hover);
155 | }
156 |
157 | .tag.secondary:active,
158 | .tag.secondary.active {
159 | border: 1.5px solid var(--Colors-Tags-Secondary-Stroke);
160 | }
161 |
162 | .tag.secondary:focus,
163 | .tag.secondary.focus {
164 | border: 1.5px solid var(--Colors-Tags-Secondary-Stroke);
165 | box-shadow: 0 0 0 4px var(--Colors-Tags-Secondary-Focus);
166 | }
167 |
168 | .tag.outline {
169 | background: var(--Colors-Tags-Outline);
170 | color: var(--Colors-Tags-Outline-Text);
171 | border: 1.5px solid var(--Colors-Tags-Outline-Stroke);
172 | }
173 |
174 | .tag.outline:hover,
175 | .tag.outline.hover {
176 | background: var(--Colors-Tags-Outline-Background-Hover);
177 | border: 1.5px solid var(--Colors-Tags-Outline-Hover);
178 | }
179 |
180 | .tag.outline:active,
181 | .tag.outline.active {
182 | border: 1.5px solid var(--Colors-Tags-Outline-Selected);
183 | }
184 |
185 | .tag.outline:focus,
186 | .tag.outline.focus {
187 | border: 1.5px solid var(--Colors-Tags-Outline-Selected);
188 | box-shadow: 0 0 0 4px var(--Colors-Tags-Outline-Focus);
189 | }
190 |
191 | .tag.success {
192 | background: var(--Colors-Tags-Success);
193 | color: var(--Colors-Tags-Success-Text);
194 | }
195 |
196 | .tag.success:hover,
197 | .tag.success.hover {
198 | background: var(--Colors-Tags-Success-Hover);
199 | }
200 |
201 | .tag.success:active,
202 | .tag.success.active {
203 | border: 1.5px solid var(--Colors-Tags-Success-Stroke);
204 | }
205 |
206 | .tag.success:focus,
207 | .tag.success.focus {
208 | border: 1.5px solid var(--Colors-Tags-Success-Stroke);
209 | box-shadow: 0 0 0 4px var(--Colors-Tags-Success-Focus);
210 | }
211 |
212 | .tag.error {
213 | background: var(--Colors-Tags-Error);
214 | color: var(--Colors-Tags-Error-Text);
215 | }
216 |
217 | .tag.error:hover,
218 | .tag.error.hover {
219 | background: var(--Colors-Tags-Error-Hover);
220 | }
221 |
222 | .tag.error:active,
223 | .tag.error.active {
224 | border: 1.5px solid var(--Colors-Tags-Error-Stroke);
225 | }
226 |
227 | .tag.error:focus,
228 | .tag.error.focus {
229 | border: 1.5px solid var(--Colors-Tags-Error-Stroke);
230 | box-shadow: 0 0 0 4px var(--Colors-Tags-Error-Focus);
231 | }
232 |
233 | .tag.warning {
234 | background: var(--Colors-Tags-Warning);
235 | color: var(--Colors-Tags-Warning-Text);
236 | }
237 |
238 | .tag.warning:hover,
239 | .tag.warning.hover {
240 | background: var(--Colors-Tags-Warning-Hover);
241 | }
242 |
243 | .tag.warning:active,
244 | .tag.warning.active {
245 | border: 1.5px solid var(--Colors-Tags-Warning-Stroke);
246 | }
247 |
248 | .tag.warning:focus,
249 | .tag.warning.focus {
250 | border: 1.5px solid var(--Colors-Tags-Warning-Stroke);
251 | box-shadow: 0 0 0 4px var(--Colors-Tags-Warning-Focus);
252 | }
253 |
254 | .tag.info {
255 | background: var(--Colors-Tags-Info);
256 | color: var(--Colors-Tags-Info-Text);
257 | }
258 |
259 | .tag.info:hover,
260 | .tag.info.hover {
261 | background: var(--Colors-Tags-Info-Hover);
262 | }
263 |
264 | .tag.info:active,
265 | .tag.info.active {
266 | border: 1.5px solid var(--Colors-Tags-Info-Stroke);
267 | }
268 |
269 | .tag.info:focus,
270 | .tag.info.focus {
271 | border: 1.5px solid var(--Colors-Tags-Info-Stroke);
272 | box-shadow: 0 0 0 4px var(--Colors-Tags-Info-Focus);
273 | }
274 |
--------------------------------------------------------------------------------
/components/modal/modal.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Modal Component Styles
3 | * Matches CodeSignal Design System
4 | */
5 |
6 | :root {
7 | /* Modal Variables - Light Mode */
8 | --Colors-Modal-Overlay: rgba(29, 38, 62, 0.4);
9 | --Colors-Modal-Background: var(--Colors-Base-Neutral-00);
10 | --Colors-Modal-Shadow-Float: rgba(22, 44, 96, 0.12);
11 | --Colors-Modal-Close-Icon: var(--Colors-Base-Neutral-700);
12 | --Colors-Modal-Close-Icon-Hover: var(--Colors-Base-Neutral-900);
13 | }
14 |
15 | @media (prefers-color-scheme: dark) {
16 | :root {
17 | /* Modal Variables - Dark Mode */
18 | --Colors-Modal-Overlay: rgba(13, 21, 40, 0.94);
19 | --Colors-Modal-Background: var(--Colors-Base-Neutral-1300);
20 | --Colors-Modal-Shadow-Float: rgba(13, 21, 40, 0.3);
21 | --Colors-Modal-Close-Icon: var(--Colors-Base-Neutral-700);
22 | --Colors-Modal-Close-Icon-Hover: var(--Colors-Base-Neutral-500);
23 | }
24 | }
25 |
26 | /* Modal Overlay */
27 | .modal-overlay {
28 | position: fixed;
29 | top: 0;
30 | left: 0;
31 | right: 0;
32 | bottom: 0;
33 | background: var(--Colors-Modal-Overlay);
34 | display: flex;
35 | align-items: center;
36 | justify-content: center;
37 | z-index: 10000;
38 | padding: var(--UI-Spacing-spacing-xxxl);
39 | opacity: 0;
40 | transition: opacity 0.2s ease;
41 | pointer-events: none;
42 | }
43 |
44 | .modal-overlay.open {
45 | opacity: 1;
46 | pointer-events: auto;
47 | }
48 |
49 | /* Modal Dialog */
50 | .modal-dialog {
51 | position: relative;
52 | background: var(--Colors-Modal-Background);
53 | border-radius: var(--UI-Radius-radius-s);
54 | padding: var(--UI-Spacing-spacing-none);
55 | box-shadow: 0px 0px 48px 0px var(--Colors-Modal-Shadow-Float),
56 | -35px 30px 38px 0px var(--Colors-Modal-Shadow-Float);
57 | max-width: 100%;
58 | max-height: calc(100vh - 2 * var(--UI-Spacing-spacing-xxxl));
59 | overflow: hidden;
60 | transform: scale(0.95);
61 | transition: transform 0.2s ease;
62 |
63 | display: flex;
64 | flex-direction: column;
65 | justify-content: flex-start;
66 | align-items: stretch;
67 | gap: var(--UI-Spacing-spacing-none);
68 | min-height: 0;
69 | }
70 |
71 | .modal-overlay.open .modal-dialog {
72 | transform: scale(1);
73 | }
74 |
75 | /* Modal Sizes */
76 | .modal-dialog.size-small {
77 | width: 400px;
78 | }
79 |
80 | .modal-dialog.size-medium {
81 | width: 600px;
82 | }
83 |
84 | .modal-dialog.size-large {
85 | width: 900px;
86 | }
87 |
88 | .modal-dialog.size-xlarge {
89 | width: 1200px;
90 | }
91 |
92 | /* Modal Header */
93 | .modal-header {
94 | display: flex;
95 | align-items: flex-end;
96 | height: 48px;
97 | position: relative;
98 | gap: var(--UI-Spacing-spacing-4xl);
99 | padding: var(--UI-Spacing-spacing-mxl) var(--UI-Spacing-spacing-xl) var(--UI-Spacing-spacing-mxl) var(--UI-Spacing-spacing-xl);
100 | align-self: stretch;
101 | }
102 |
103 | .modal-header:empty {
104 | display: none;
105 | }
106 |
107 | .modal-title {
108 | font-family: var(--heading-family);
109 | font-size: 20px;
110 | font-weight: 500;
111 | font-style: normal;
112 | line-height: 1.2;
113 | letter-spacing: -0.2px;
114 | color: var(--Colors-Text-Body-Strongest);
115 | margin: 0;
116 | flex: 1;
117 | }
118 |
119 | .modal-close-button {
120 | position: absolute;
121 | top: 4px;
122 | right: 4px;
123 | width: var(--UI-Input-md);
124 | height: var(--UI-Input-md);
125 | display: flex;
126 | align-items: center;
127 | justify-content: center;
128 | padding: var(--UI-Spacing-spacing-mxs);
129 | border: none;
130 | background: transparent;
131 | border-radius: var(--UI-Radius-radius-xs);
132 | cursor: pointer;
133 | transition: background-color 0.2s ease;
134 | }
135 |
136 | .modal-close-button:hover {
137 | background: var(--Colors-Backgrounds-Main-Medium);
138 | }
139 |
140 | .modal-close-button:focus {
141 | outline: 2px solid var(--Colors-Primary-Medium);
142 | outline-offset: 2px;
143 | }
144 |
145 | .modal-close-button:focus:not(:focus-visible) {
146 | outline: none;
147 | }
148 |
149 | .modal-close-icon {
150 | width: 24px;
151 | height: 24px;
152 | display: block;
153 | color: var(--Colors-Modal-Close-Icon);
154 | transition: color 0.2s ease;
155 | }
156 |
157 | .modal-close-button:hover .modal-close-icon {
158 | color: var(--Colors-Modal-Close-Icon-Hover);
159 | }
160 |
161 | .modal-close-icon svg {
162 | width: 100%;
163 | height: 100%;
164 | display: block;
165 | }
166 |
167 | /* Modal Content */
168 | .modal-content {
169 | display: flex;
170 | flex: 1 1 auto;
171 | padding: var(--UI-Spacing-spacing-mxl) var(--UI-Spacing-spacing-xl) var(--UI-Spacing-spacing-xxxl) var(--UI-Spacing-spacing-xl);
172 | overflow-y: auto;
173 | min-height: 0;
174 | flex-direction: column;
175 | align-items: center;
176 | gap: var(--UI-Spacing-spacing-xl);
177 | align-self: stretch;
178 | scroll-behavior: smooth;
179 | }
180 |
181 | .modal-content:empty {
182 | min-height: 200px;
183 | }
184 |
185 | /* Modal Footer */
186 | .modal-footer {
187 | padding: var(--UI-Spacing-spacing-mxl) var(--UI-Spacing-spacing-xl);
188 | min-height: 48px;
189 | display: flex;
190 | justify-content: flex-end;
191 | align-items: flex-start;
192 | align-self: stretch;
193 | gap: 12px;
194 | flex-shrink: 0;
195 | }
196 |
197 | .modal-footer-buttons {
198 | display: flex;
199 | flex: 1;
200 | gap: var(--UI-Spacing-spacing-mxs);
201 | align-items: flex-start;
202 | justify-content: flex-end;
203 | }
204 |
205 | .modal-footer-buttons .button {
206 | flex-shrink: 0;
207 | }
208 |
209 | /* Help Modal Styles */
210 | .modal-content .toc {
211 | background: var(--Colors-Backgrounds-Main-Light);
212 | border-radius: var(--UI-Radius-radius-s);
213 | padding: var(--UI-Spacing-spacing-mxl);
214 | margin-bottom: var(--UI-Spacing-spacing-mxs);
215 | border-left: 3px solid var(--Colors-Primary-Default);
216 | align-self: flex-start;
217 | width: 100%;
218 | box-sizing: border-box;
219 | }
220 |
221 | .modal-content .toc strong {
222 | display: block;
223 | font-family: var(--heading-family);
224 | font-size: 24px;
225 | font-weight: 500;
226 | color: var(--Colors-Text-Body-Strongest);
227 | margin: 0 0 var(--UI-Spacing-spacing-mxl) 0;
228 | padding-bottom: var(--UI-Spacing-spacing-mxs);
229 | border-bottom: 1px solid var(--Colors-Stroke-Default);
230 | }
231 |
232 | .modal-content .toc ul {
233 | list-style: none;
234 | padding: 0;
235 | margin: var(--UI-Spacing-spacing-mxs) 0 0 0;
236 | }
237 |
238 | .modal-content .toc li {
239 | margin: var(--UI-Spacing-spacing-xs) 0;
240 | }
241 |
242 | .modal-content .toc a {
243 | color: var(--Colors-Primary-Default);
244 | text-decoration: none;
245 | font-size: 14px;
246 | transition: color 0.2s ease;
247 | }
248 |
249 | .modal-content .toc a:hover {
250 | color: var(--Colors-Primary-Strong);
251 | text-decoration: underline;
252 | }
253 |
254 | .modal-content section {
255 | margin-bottom: var(--UI-Spacing-spacing-mxs);
256 | scroll-margin-top: var(--UI-Spacing-spacing-mxl);
257 | align-self: flex-start;
258 | width: 100%;
259 | }
260 |
261 | .modal-content section:last-child {
262 | margin-bottom: 0;
263 | }
264 |
265 | .modal-content section h2 {
266 | font-family: var(--heading-family);
267 | font-size: 24px;
268 | font-weight: 500;
269 | color: var(--Colors-Text-Body-Strongest);
270 | margin: 0 0 var(--UI-Spacing-spacing-mxl) 0;
271 | padding-bottom: var(--UI-Spacing-spacing-mxs);
272 | border-bottom: 1px solid var(--Colors-Stroke-Default);
273 | }
274 |
275 | .modal-content section h3 {
276 | font-family: var(--heading-family);
277 | font-size: 18px;
278 | font-weight: 500;
279 | color: var(--Colors-Text-Body-Strongest);
280 | margin: var(--UI-Spacing-spacing-xl) 0 var(--UI-Spacing-spacing-mxs) 0;
281 | }
282 |
283 | .modal-content section p {
284 | color: var(--Colors-Text-Body-Default);
285 | margin: var(--UI-Spacing-spacing-mxs) 0;
286 | line-height: 1.6;
287 | }
288 |
289 | .modal-content section ol,
290 | .modal-content section ul {
291 | margin: var(--UI-Spacing-spacing-mxs) 0;
292 | padding-left: var(--UI-Spacing-spacing-xl);
293 | color: var(--Colors-Text-Body-Default);
294 | }
295 |
296 | .modal-content section li {
297 | margin: var(--UI-Spacing-spacing-xs) 0;
298 | line-height: 1.6;
299 | }
300 |
301 | .modal-content section li strong {
302 | color: var(--Colors-Text-Body-Strongest);
303 | font-weight: 600;
304 | }
305 |
306 | .modal-content section details {
307 | margin: var(--UI-Spacing-spacing-mxs) 0;
308 | padding: var(--UI-Spacing-spacing-mxs);
309 | border-radius: var(--UI-Radius-radius-xs);
310 | transition: background-color 0.2s ease;
311 | }
312 |
313 | .modal-content section details:hover {
314 | background: var(--Colors-Backgrounds-Main-Light);
315 | }
316 |
317 | .modal-content section details summary {
318 | font-weight: 500;
319 | color: var(--Colors-Text-Body-Strong);
320 | cursor: pointer;
321 | padding: var(--UI-Spacing-spacing-xs);
322 | margin: calc(-1 * var(--UI-Spacing-spacing-xs));
323 | user-select: none;
324 | }
325 |
326 | .modal-content section details summary:hover {
327 | color: var(--Colors-Primary-Default);
328 | }
329 |
330 | .modal-content section details[open] summary {
331 | margin-bottom: var(--UI-Spacing-spacing-mxs);
332 | }
333 |
334 | .modal-content section details p {
335 | margin: var(--UI-Spacing-spacing-mxs) 0 0 0;
336 | padding-left: var(--UI-Spacing-spacing-mxs);
337 | }
338 |
339 | .modal-content section code {
340 | font-family: 'JetBrains Mono', monospace;
341 | font-size: 13px;
342 | background: var(--Colors-Backgrounds-Main-Medium);
343 | padding: 2px 6px;
344 | border-radius: var(--UI-Radius-radius-xxs);
345 | color: var(--Colors-Text-Body-Strong);
346 | }
347 |
348 | .modal-content section img {
349 | max-width: 100%;
350 | height: auto;
351 | border-radius: var(--UI-Radius-radius-s);
352 | margin: var(--UI-Spacing-spacing-mxl) 0;
353 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
354 | }
355 |
356 | /* Responsive adjustments */
357 | @media (max-width: 768px) {
358 | .modal-overlay {
359 | padding: var(--UI-Spacing-spacing-mxl);
360 | }
361 |
362 | .modal-dialog.size-small,
363 | .modal-dialog.size-medium,
364 | .modal-dialog.size-large,
365 | .modal-dialog.size-xlarge {
366 | width: 100%;
367 | max-width: 100%;
368 | }
369 |
370 | .modal-header,
371 | .modal-content,
372 | .modal-footer {
373 | padding-left: var(--UI-Spacing-spacing-mxl);
374 | padding-right: var(--UI-Spacing-spacing-mxl);
375 | }
376 |
377 | .modal-content section h2 {
378 | font-size: 20px;
379 | }
380 |
381 | .modal-content section h3 {
382 | font-size: 16px;
383 | }
384 |
385 | .modal-content .toc strong {
386 | font-size: 20px;
387 | }
388 | }
389 |
390 |
--------------------------------------------------------------------------------
/components/dropdown/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Component Tests - Dropdown
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
116 |
117 |
118 |
119 |
120 | Component Tests - Dropdown
121 |
122 |
123 |
124 |
125 | Basic Dropdown
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | Selected Value: None
134 |
135 |
136 |
137 |
138 |
139 | Dropdown with Pre-selected Value
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | Selected Value: option-2
148 |
149 |
150 |
151 |
152 |
153 | Different Widths
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | Custom Placeholder
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | Selected Value: None
181 |
182 |
183 |
184 |
185 |
186 | Many Options
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | Selected Value: None
195 |
196 |
197 |
198 |
199 |
200 | Grow to Fit Selected Value
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | Selected Value: None
209 |
210 |
211 | Select a long option to see the dropdown grow to fit the text.
212 |
213 |
214 |
215 |
216 |
217 |
315 |
316 |
317 |
318 |
--------------------------------------------------------------------------------
/components/modal/README.md:
--------------------------------------------------------------------------------
1 | # Modal Component
2 |
3 | A customizable modal component matching the CodeSignal Design System. Supports declarative content insertion and flexible configuration.
4 |
5 | ## Usage
6 |
7 | ### 1. Import Assets
8 |
9 | Include the CSS and JS files:
10 |
11 | ```html
12 |
13 |
17 | ```
18 |
19 | ### 2. Initialize Component
20 |
21 | ```javascript
22 | import Modal from './modal.js';
23 |
24 | const modal = new Modal({
25 | size: 'medium',
26 | title: 'Modal Title',
27 | content: 'Modal content goes here
',
28 | footerButtons: [
29 | { label: 'Cancel', type: 'secondary' },
30 | { label: 'Save', type: 'primary', onClick: () => console.log('Saved!') }
31 | ]
32 | });
33 |
34 | // Open the modal
35 | modal.open();
36 | ```
37 |
38 | ## Configuration Options
39 |
40 | | Option | Type | Default | Description |
41 | | :--- | :--- | :--- | :--- |
42 | | `size` | String | `'medium'` | Modal size: `'small'` (400px), `'medium'` (600px), `'large'` (900px), `'xlarge'` (1200px). |
43 | | `title` | String | `null` | Modal title text. If `null`, header is hidden. |
44 | | `content` | String/Element | `null` | Modal content. Can be HTML string, DOM element, or CSS selector. |
45 | | `showCloseButton` | Boolean | `true` | If `true`, displays close button (X) in header. |
46 | | `footerButtons` | Array | `null` | Array of button configs `[{label, type, onClick}]`. If `null`, footer is hidden. |
47 | | `closeOnOverlayClick` | Boolean | `true` | If `true`, clicking overlay closes modal. |
48 | | `closeOnEscape` | Boolean | `true` | If `true`, pressing Escape closes modal. |
49 | | `onOpen` | Function | `null` | Callback triggered when modal opens. Receives `(modal)` instance. |
50 | | `onClose` | Function | `null` | Callback triggered when modal closes. Receives `(modal)` instance. |
51 |
52 | ## Declarative Content
53 |
54 | The modal supports multiple ways to set content declaratively:
55 |
56 | ### HTML String
57 |
58 | ```javascript
59 | const modal = new Modal({
60 | title: 'Simple Modal',
61 | content: 'This is simple HTML content.
'
62 | });
63 | modal.open();
64 | ```
65 |
66 | ### DOM Element
67 |
68 | ```javascript
69 | const contentDiv = document.createElement('div');
70 | contentDiv.innerHTML = 'Content from DOM element
';
71 |
72 | const modal = new Modal({
73 | title: 'DOM Element Modal',
74 | content: contentDiv
75 | });
76 | modal.open();
77 | ```
78 |
79 | ### CSS Selector
80 |
81 | ```javascript
82 | // Use existing element in the page (cloned)
83 | const modal = new Modal({
84 | title: 'Selector Modal',
85 | content: '#my-content-element' // or '.my-content-class'
86 | });
87 | modal.open();
88 | ```
89 |
90 | ### Template Element
91 |
92 | ```html
93 |
94 |
95 |
96 | Template Content
97 | This content comes from a template.
98 |
99 |
100 |
101 |
111 | ```
112 |
113 | ## API Methods
114 |
115 | ### `open()`
116 |
117 | Opens the modal and locks body scroll.
118 |
119 | ```javascript
120 | modal.open();
121 | ```
122 |
123 | ### `close()`
124 |
125 | Closes the modal and restores body scroll.
126 |
127 | ```javascript
128 | modal.close();
129 | ```
130 |
131 | ### `updateContent(content)`
132 |
133 | Updates the modal content dynamically. Accepts same content types as constructor.
134 |
135 | ```javascript
136 | modal.updateContent('New content
');
137 | modal.updateContent(document.querySelector('#new-content'));
138 | ```
139 |
140 | ### `updateTitle(title)`
141 |
142 | Updates the modal title. Creates title element if it doesn't exist.
143 |
144 | ```javascript
145 | modal.updateTitle('New Title');
146 | ```
147 |
148 | ### `destroy()`
149 |
150 | Removes the modal from DOM and cleans up event listeners.
151 |
152 | ```javascript
153 | modal.destroy();
154 | ```
155 |
156 | ## Examples
157 |
158 | ### Basic Modal
159 |
160 | ```javascript
161 | const modal = new Modal({
162 | title: 'Basic Modal',
163 | content: 'This is a basic modal with just content.
'
164 | });
165 | modal.open();
166 | ```
167 |
168 | ### Modal with Footer Buttons
169 |
170 | ```javascript
171 | const modal = new Modal({
172 | size: 'large',
173 | title: 'Confirm Action',
174 | content: 'Are you sure you want to proceed?
',
175 | footerButtons: [
176 | {
177 | label: 'Cancel',
178 | type: 'secondary',
179 | onClick: () => {
180 | console.log('Cancelled');
181 | modal.close();
182 | }
183 | },
184 | {
185 | label: 'Confirm',
186 | type: 'primary',
187 | onClick: () => {
188 | console.log('Confirmed');
189 | modal.close();
190 | }
191 | }
192 | ]
193 | });
194 | modal.open();
195 | ```
196 |
197 | ### Modal with Form Content
198 |
199 | ```javascript
200 | const formHTML = `
201 |
211 | `;
212 |
213 | const modal = new Modal({
214 | title: 'User Information',
215 | content: formHTML,
216 | footerButtons: [
217 | { label: 'Cancel', type: 'secondary' },
218 | { label: 'Save', type: 'primary' }
219 | ]
220 | });
221 | modal.open();
222 | ```
223 |
224 | ### Modal Without Footer
225 |
226 | ```javascript
227 | const modal = new Modal({
228 | title: 'Information',
229 | content: 'This modal has no footer buttons.
',
230 | footerButtons: null
231 | });
232 | modal.open();
233 | ```
234 |
235 | ### Modal Without Title
236 |
237 | ```javascript
238 | const modal = new Modal({
239 | title: null,
240 | content: 'This modal has no title.
',
241 | footerButtons: [
242 | { label: 'Close', type: 'primary' }
243 | ]
244 | });
245 | modal.open();
246 | ```
247 |
248 | ### Dynamic Content Update
249 |
250 | ```javascript
251 | const modal = new Modal({
252 | title: 'Dynamic Content',
253 | content: 'Initial content
'
254 | });
255 | modal.open();
256 |
257 | // Update content after 2 seconds
258 | setTimeout(() => {
259 | modal.updateContent('Updated content!
');
260 | }, 2000);
261 | ```
262 |
263 | ### Help Modal
264 |
265 | The Modal component includes a specialized `createHelpModal()` static method for creating help/documentation modals. It's a convenience method that sets sensible defaults (xlarge size, footer with close button) and applies appropriate styling for help content.
266 |
267 | **Use HTML `` Elements**
268 |
269 | Help modals should use HTML `` elements for content. This keeps your HTML organized and makes content reusable.
270 |
271 | ```html
272 |
273 |
274 |
283 |
284 |
285 | Overview
286 | This page explains how to use the application.
287 |
288 |
289 |
290 | Getting Started
291 |
292 | - First step...
293 | - Second step...
294 |
295 |
296 |
297 |
298 | Key Features
299 | Feature 1
300 | Description of feature 1.
301 |
302 |
303 |
304 | Troubleshooting / FAQ
305 |
306 | Common question?
307 | Answer to the question.
308 |
309 |
310 |
311 |
312 |
323 | ```
324 |
325 | #### With Custom Modal Options
326 |
327 | You can override the default modal options:
328 |
329 | ```javascript
330 | const template = document.querySelector('#help-template');
331 | const helpModal = Modal.createHelpModal({
332 | title: 'Help',
333 | content: template.content.cloneNode(true),
334 | size: 'large', // Override default xlarge size
335 | closeOnOverlayClick: false // Prevent closing on overlay click
336 | });
337 | helpModal.open();
338 | ```
339 |
340 | #### Help Modal Features
341 |
342 | - **Convenience Method**: Sets defaults optimized for help content (xlarge size, footer with close button)
343 | - **Styled Content**: Automatically applies styles for:
344 | - Table of Contents (`.toc`) with navigation links
345 | - Sections (``) with proper headings
346 | - Details/FAQ sections (``) with expandable content
347 | - Code blocks (``) with monospace styling
348 | - Images with responsive sizing
349 | - **Template Support**: Works well with HTML `` elements for reusable content
350 | - **Large Size**: Defaults to `xlarge` size (1200px) for better readability
351 | - **Footer Button**: Includes a "Close" button in the footer by default (can be customized or removed)
352 |
353 | #### Help Modal Template Structure
354 |
355 | Your help content template should follow this structure. See `help-modal-template.html` for a complete reference example.
356 |
357 | ```html
358 |
359 |
367 |
368 |
369 |
370 | Overview
371 | Content...
372 |
373 |
374 |
375 | Getting Started
376 |
377 | - Step 1...
378 | - Step 2...
379 |
380 |
381 |
382 |
383 |
384 | Troubleshooting / FAQ
385 |
386 | Question?
387 | Answer...
388 |
389 |
390 | ```
391 |
392 | ## Button Types
393 |
394 | Footer buttons support the following types:
395 |
396 | - `'primary'`: Primary button (default)
397 | - `'secondary'`: Secondary/outlined button
398 | - `'tertiary'`: Tertiary/ghost button
399 |
400 | ## Accessibility
401 |
402 | The modal component includes:
403 |
404 | - **ARIA Attributes**: `role="dialog"`, `aria-modal="true"`, `aria-labelledby`
405 | - **Focus Management**: Automatically focuses close button or title when opened
406 | - **Keyboard Navigation**: Escape key closes modal
407 | - **Focus Trap**: Focus is managed within the modal
408 | - **Body Scroll Lock**: Prevents background scrolling when modal is open
409 |
410 | ## Dependencies
411 |
412 | This component relies on variables from:
413 | - `design-system/colors/colors.css`
414 | - `design-system/spacing/spacing.css`
415 | - `design-system/typography/typography.css`
416 | - `design-system/components/button/button.css` (for footer buttons)
417 |
418 |
--------------------------------------------------------------------------------
/components/modal/modal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Modal Component
3 | * A customizable modal component matching the CodeSignal Design System
4 | */
5 |
6 | class Modal {
7 | constructor(options = {}) {
8 | // Configuration
9 | this.config = {
10 | size: options.size || 'medium', // 'small', 'medium', 'large', 'xlarge'
11 | title: options.title || null,
12 | content: options.content || null, // Can be HTML string, DOM element, or selector
13 | showCloseButton: options.showCloseButton !== undefined ? options.showCloseButton : true,
14 | footerButtons: options.footerButtons || null, // Array of button configs or null to hide footer
15 | closeOnOverlayClick: options.closeOnOverlayClick !== undefined ? options.closeOnOverlayClick : true,
16 | closeOnEscape: options.closeOnEscape !== undefined ? options.closeOnEscape : true,
17 | onOpen: options.onOpen || null,
18 | onClose: options.onClose || null,
19 | ...options
20 | };
21 |
22 | // State
23 | this.isOpen = false;
24 | this.previousActiveElement = null;
25 | this.bodyScrollLocked = false;
26 |
27 | // Create modal structure
28 | this.init();
29 | }
30 |
31 | init() {
32 | // Create overlay
33 | this.overlay = document.createElement('div');
34 | this.overlay.className = 'modal-overlay';
35 | this.overlay.setAttribute('role', 'dialog');
36 | this.overlay.setAttribute('aria-modal', 'true');
37 | this.overlay.setAttribute('aria-labelledby', 'modal-title');
38 |
39 | // Create dialog
40 | this.dialog = document.createElement('div');
41 | this.dialog.className = `modal-dialog size-${this.config.size}`;
42 |
43 | // Create header
44 | this.header = document.createElement('div');
45 | this.header.className = 'modal-header';
46 |
47 | if (this.config.title) {
48 | this.title = document.createElement('h2');
49 | this.title.className = 'modal-title';
50 | this.title.id = 'modal-title';
51 | this.title.textContent = this.config.title;
52 | this.header.appendChild(this.title);
53 | }
54 |
55 | if (this.config.showCloseButton) {
56 | this.closeButton = document.createElement('button');
57 | this.closeButton.className = 'modal-close-button';
58 | this.closeButton.setAttribute('type', 'button');
59 | this.closeButton.setAttribute('aria-label', 'Close modal');
60 | this.closeButton.innerHTML = this.getCloseIcon();
61 | this.header.appendChild(this.closeButton);
62 | }
63 |
64 | // Only append header if it has content
65 | if (this.config.title || this.config.showCloseButton) {
66 | // Header will be appended to dialog
67 | } else {
68 | // Hide header if no title and no close button
69 | this.header.style.display = 'none';
70 | }
71 |
72 | // Create content area
73 | this.content = document.createElement('div');
74 | this.content.className = 'modal-content';
75 | this.setContent(this.config.content);
76 |
77 | // Create footer
78 | this.footer = null;
79 | if (this.config.footerButtons) {
80 | this.footer = document.createElement('div');
81 | this.footer.className = 'modal-footer';
82 | const buttonsRow = document.createElement('div');
83 | buttonsRow.className = 'modal-footer-buttons';
84 | this.createFooterButtons(buttonsRow);
85 | this.footer.appendChild(buttonsRow);
86 | }
87 |
88 | // Assemble structure
89 | if (this.config.title || this.config.showCloseButton) {
90 | this.dialog.appendChild(this.header);
91 | }
92 | this.dialog.appendChild(this.content);
93 | if (this.footer) {
94 | this.dialog.appendChild(this.footer);
95 | }
96 | this.overlay.appendChild(this.dialog);
97 |
98 | // Bind events
99 | this.bindEvents();
100 |
101 | // Append to body (but keep hidden until opened)
102 | document.body.appendChild(this.overlay);
103 | }
104 |
105 | setContent(content) {
106 | // Clear existing content
107 | this.content.innerHTML = '';
108 |
109 | if (!content) {
110 | return;
111 | }
112 |
113 | // Handle different content types
114 | if (content instanceof HTMLElement) {
115 | // DOM element
116 | this.content.appendChild(content);
117 | } else if (typeof content === 'object' && content.nodeType) {
118 | // Node object
119 | this.content.appendChild(content);
120 | } else if (typeof content === 'string') {
121 | // Check if it's a CSS selector first
122 | if (content.startsWith('#') || content.startsWith('.')) {
123 | // CSS selector (ID or class)
124 | const element = document.querySelector(content);
125 | if (element) {
126 | // Clone the element to avoid moving it from its original location
127 | const cloned = element.cloneNode(true);
128 | // Remove any hidden/display:none classes that might prevent content from showing
129 | cloned.classList.remove('hidden-content');
130 | cloned.style.display = '';
131 | this.content.appendChild(cloned);
132 | }
133 | } else {
134 | // HTML string
135 | this.content.innerHTML = content;
136 | }
137 | }
138 | }
139 |
140 | createFooterButtons(container) {
141 | if (!this.config.footerButtons || !Array.isArray(this.config.footerButtons)) {
142 | return;
143 | }
144 |
145 | this.config.footerButtons.forEach((buttonConfig, index) => {
146 | const button = document.createElement('button');
147 | button.className = 'button';
148 |
149 | // Determine button type
150 | if (buttonConfig.type === 'secondary') {
151 | button.classList.add('button-secondary');
152 | } else if (buttonConfig.type === 'tertiary') {
153 | button.classList.add('button-tertiary');
154 | } else {
155 | button.classList.add('button-primary');
156 | }
157 |
158 | button.textContent = buttonConfig.label || 'Button';
159 |
160 | if (buttonConfig.onClick) {
161 | button.addEventListener('click', (e) => {
162 | buttonConfig.onClick(e, this);
163 | });
164 | } else {
165 | // Default: close modal on click
166 | button.addEventListener('click', () => {
167 | this.close();
168 | });
169 | }
170 |
171 | container.appendChild(button);
172 | });
173 | }
174 |
175 | bindEvents() {
176 | // Close button
177 | if (this.closeButton) {
178 | this.closeButton.addEventListener('click', () => {
179 | this.close();
180 | });
181 | }
182 |
183 | // Overlay click
184 | if (this.config.closeOnOverlayClick) {
185 | this.overlay.addEventListener('click', (e) => {
186 | if (e.target === this.overlay) {
187 | this.close();
188 | }
189 | });
190 | }
191 |
192 | // Escape key
193 | if (this.config.closeOnEscape) {
194 | this.escapeHandler = (e) => {
195 | if (e.key === 'Escape' && this.isOpen) {
196 | this.close();
197 | }
198 | };
199 | document.addEventListener('keydown', this.escapeHandler);
200 | }
201 |
202 | // Prevent dialog clicks from closing modal
203 | this.dialog.addEventListener('click', (e) => {
204 | e.stopPropagation();
205 | });
206 |
207 | // Handle anchor link clicks within modal content for smooth scrolling
208 | this.content.addEventListener('click', (e) => {
209 | const link = e.target.closest('a[href^="#"]');
210 | if (link && link.hash) {
211 | e.preventDefault();
212 | const targetId = link.hash.substring(1);
213 | const targetElement = this.content.querySelector(`#${targetId}`);
214 | if (targetElement) {
215 | targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
216 | }
217 | }
218 | });
219 | }
220 |
221 | open() {
222 | if (this.isOpen) return;
223 |
224 | this.isOpen = true;
225 | this.previousActiveElement = document.activeElement;
226 |
227 | // Show overlay
228 | this.overlay.classList.add('open');
229 | this.overlay.setAttribute('aria-hidden', 'false');
230 |
231 | // Lock body scroll
232 | this.lockBodyScroll();
233 |
234 | // Focus management
235 | if (this.closeButton) {
236 | this.closeButton.focus();
237 | } else if (this.title) {
238 | this.title.focus();
239 | }
240 |
241 | // Call onOpen callback
242 | if (this.config.onOpen) {
243 | this.config.onOpen(this);
244 | }
245 | }
246 |
247 | close() {
248 | if (!this.isOpen) return;
249 |
250 | this.isOpen = false;
251 |
252 | // Hide overlay
253 | this.overlay.classList.remove('open');
254 | this.overlay.setAttribute('aria-hidden', 'true');
255 |
256 | // Unlock body scroll
257 | this.unlockBodyScroll();
258 |
259 | // Restore focus
260 | if (this.previousActiveElement) {
261 | this.previousActiveElement.focus();
262 | }
263 |
264 | // Call onClose callback
265 | if (this.config.onClose) {
266 | this.config.onClose(this);
267 | }
268 | }
269 |
270 | lockBodyScroll() {
271 | if (this.bodyScrollLocked) return;
272 |
273 | const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
274 | document.body.style.overflow = 'hidden';
275 | if (scrollbarWidth > 0) {
276 | document.body.style.paddingRight = `${scrollbarWidth}px`;
277 | }
278 | this.bodyScrollLocked = true;
279 | }
280 |
281 | unlockBodyScroll() {
282 | if (!this.bodyScrollLocked) return;
283 |
284 | document.body.style.overflow = '';
285 | document.body.style.paddingRight = '';
286 | this.bodyScrollLocked = false;
287 | }
288 |
289 | updateContent(content) {
290 | this.setContent(content);
291 | }
292 |
293 | updateTitle(title) {
294 | if (this.title) {
295 | this.title.textContent = title;
296 | } else if (title) {
297 | // Create title if it doesn't exist
298 | this.title = document.createElement('h2');
299 | this.title.className = 'modal-title';
300 | this.title.id = 'modal-title';
301 | this.title.textContent = title;
302 | this.header.insertBefore(this.title, this.closeButton || null);
303 | }
304 | }
305 |
306 | getCloseIcon() {
307 | return ``;
312 | }
313 |
314 | destroy() {
315 | // Remove event listeners
316 | if (this.escapeHandler) {
317 | document.removeEventListener('keydown', this.escapeHandler);
318 | }
319 |
320 | // Unlock body scroll if still locked
321 | this.unlockBodyScroll();
322 |
323 | // Remove from DOM
324 | if (this.overlay && this.overlay.parentNode) {
325 | this.overlay.parentNode.removeChild(this.overlay);
326 | }
327 | }
328 |
329 | /**
330 | * Static method to create a Help Modal
331 | * Convenience method that creates a modal optimized for help/documentation content
332 | * with sensible defaults (xlarge size, footer with close button)
333 | *
334 | * @param {Object} options - Configuration options (same as Modal constructor)
335 | * @param {string} options.title - Modal title (default: 'Help')
336 | * @param {string|Element} options.content - Required: HTML content for the help modal
337 | * @param {Array} options.footerButtons - Optional: Footer buttons (default: single 'Close' button)
338 | * @param {Object} options.modalOptions - Optional: Additional modal options to override defaults
339 | * @returns {Modal} The created Modal instance
340 | */
341 | static createHelpModal(options = {}) {
342 | const {
343 | title = 'Help',
344 | content,
345 | footerButtons,
346 | ...modalOptions
347 | } = options;
348 |
349 | // Default to a single Close button if no footer buttons provided
350 | const defaultFooterButtons = footerButtons !== undefined
351 | ? footerButtons
352 | : [{ label: 'Close', type: 'primary' }];
353 |
354 | return new Modal({
355 | size: 'xlarge',
356 | title,
357 | content,
358 | footerButtons: defaultFooterButtons,
359 | showCloseButton: true,
360 | ...modalOptions
361 | });
362 | }
363 | }
364 |
365 | // Export for ES6 modules
366 | export default Modal;
367 |
368 | // Also make available globally
369 | if (typeof window !== 'undefined') {
370 | window.Modal = Modal;
371 | }
372 |
373 |
--------------------------------------------------------------------------------
/components/button/button.css:
--------------------------------------------------------------------------------
1 | /* ===== BUTTON COMPONENT ===== */
2 |
3 | :root {
4 | /* Button Variables */
5 | --Button-Height-Default: var(--UI-Input-md);
6 |
7 | --Colors-Shadow-Focus-Button: rgba(92, 148, 255, 0.20);
8 |
9 | --Colors-Buttons-Primary-Default: var(--Colors-Base-Primary-700);
10 | --Colors-Buttons-Primary-Default-Text: var(--Colors-Base-Neutral-00);
11 | --Colors-Buttons-Primary-Hover: var(--Colors-Base-Primary-700);
12 | --Colors-Buttons-Primary-Pressed: var(--Colors-Base-Primary-900);
13 | --Colors-Buttons-Primary-Stroke-Focus: var(--Colors-Base-Primary-900);
14 | --Colors-Buttons-Secondary-Default: var(--Colors-Base-Primary-700);
15 | --Colors-Buttons-Secondary-Default-Text: var(--Colors-Base-Primary-700);
16 | --Colors-Buttons-Secondary-Hover: var(--Colors-Base-Primary-800);
17 | --Colors-Buttons-Secondary-Hover-Text: var(--Colors-Base-Primary-800);
18 | --Colors-Buttons-Secondary-Pressed: var(--Colors-Base-Primary-900);
19 | --Colors-Buttons-Secondary-Pressed-Text: var(--Colors-Base-Primary-900);
20 | --Colors-Buttons-Tertiary-Default: var(--Colors-Base-Neutral-300);
21 | --Colors-Buttons-Tertiary-Default-Background: var(--Colors-Base-Neutral-00);
22 | --Colors-Buttons-Tertiary-Default-Text: var(--Colors-Base-Neutral-1000);
23 | --Colors-Buttons-Tertiary-Hover: var(--Colors-Base-Neutral-600);
24 | --Colors-Buttons-Tertiary-Hover-Text: var(--Colors-Base-Neutral-1200);
25 | --Colors-Buttons-Tertiary-Hover-Background: var(--Colors-Base-Neutral-00);
26 | --Colors-Buttons-Tertiary-Pressed: var(--Colors-Base-Neutral-750);
27 | --Colors-Buttons-Tertiary-Pressed-Text: var(--Colors-Base-Neutral-1200);
28 | --Colors-Button-Danger-Default: var(--Colors-Base-Accent-Red-600);
29 | --Colors-Buttons-Danger-Hover: var(--Colors-Base-Accent-Red-700);
30 | --Colors-Buttons-Danger-Stroke-Focus: var(--Colors-Base-Accent-Red-800);
31 | --Colors-Shadow-Focus-Button-Danger: rgba(184, 6, 44, 0.12);
32 | --Colors-Buttons-Danger-Pressed: var(--Colors-Base-Accent-Red-800);
33 | --Colors-Buttons-Success-Default: var(--Colors-Base-Accent-Green-600);
34 | --Colors-Buttons-Success-Hover: var(--Colors-Base-Accent-Green-700);
35 | --Colors-Buttons-Success-Stroke-Focus: var(--Colors-Base-Accent-Green-800);
36 | --Colors-Shadow-Focus-Button-Success: rgba(25, 158, 89, 0.14);
37 | --Colors-Buttons-Success-Pressed: var(--Colors-Base-Accent-Green-800);
38 |
39 | --Colors-Buttons-Text-Default: var(--Colors-Base-Neutral-1000);
40 | --Colors-Buttons-Text-Primary: var(--Colors-Base-Primary-700);
41 | --Colors-Buttons-Text-Primary-Hover: var(--Colors-Base-Primary-800);
42 | --Colors-Buttons-Text-Pressed: var(--Colors-Base-Neutral-1300);
43 | --Colors-Buttons-Text-Primary-Pressed: var(--Colors-Base-Primary-900);
44 | }
45 |
46 | @media (prefers-color-scheme: dark) {
47 | :root {
48 | --Colors-Buttons-Secondary-Default: var(--Colors-Base-Primary-600);
49 | --Colors-Buttons-Secondary-Default-Text: var(--Colors-Base-Primary-600);
50 | --Colors-Buttons-Secondary-Hover: var(--Colors-Base-Primary-500);
51 | --Colors-Buttons-Secondary-Hover-Text: var(--Colors-Base-Primary-500);
52 | --Colors-Buttons-Secondary-Pressed: var(--Colors-Base-Primary-650);
53 | --Colors-Buttons-Secondary-Pressed-Text: var(--Colors-Base-Primary-650);
54 | --Colors-Buttons-Tertiary-Default-Background: var(--Colors-Base-Neutral-Alphas-1200-25);
55 | --Colors-Buttons-Tertiary-Default-Text: var(--Colors-Base-Neutral-500);
56 | --Colors-Buttons-Tertiary-Hover: var(--Colors-Base-Neutral-1100);
57 | --Colors-Buttons-Tertiary-Hover-Text: var(--Colors-Base-Neutral-300);
58 | --Colors-Buttons-Tertiary-Hover-Background: var(--Colors-Base-Neutral-Alphas-1050-25);
59 | --Colors-Buttons-Tertiary-Pressed: var(--Colors-Base-Neutral-1000);
60 | --Colors-Buttons-Tertiary-Pressed-Text: var(--Colors-Base-Neutral-300);
61 |
62 | --Colors-Buttons-Text-Default: var(--Colors-Base-Neutral-700);
63 | --Colors-Buttons-Text-Primary: var(--Colors-Base-Primary-500);
64 | --Colors-Buttons-Text-Primary-Hover: var(--Colors-Base-Primary-400);
65 | --Colors-Buttons-Text-Pressed: var(--Colors-Base-Neutral-900);
66 | --Colors-Buttons-Text-Primary-Pressed: var(--Colors-Base-Primary-700);
67 | }
68 | }
69 |
70 | .button {
71 | display: inline-flex;
72 | align-items: center;
73 | justify-content: center;
74 | height: var(--Button-Height-Default);
75 | padding: 0 var(--UI-Spacing-spacing-ml);
76 | gap: var(--UI-Spacing-spacing-s);
77 |
78 | border-radius: var(--UI-Radius-radius-xs);
79 | border: none;
80 | cursor: pointer;
81 | transition: all 0.2s ease;
82 | text-decoration: none;
83 | box-sizing: border-box;
84 | white-space: nowrap;
85 |
86 | /* Typography - typically buttons match label style or specific body style */
87 | font-family: var(--body-family);
88 | font-size: var(--Fonts-Body-Default-lg);
89 | font-style: normal;
90 | font-weight: 500;
91 | line-height: 140%;
92 | letter-spacing: -0.17px;
93 | }
94 |
95 | .button:disabled,
96 | .button.disabled {
97 | cursor: not-allowed;
98 | pointer-events: none;
99 | opacity: 0.24;
100 | }
101 |
102 | /* Primary Button Variant */
103 | .button-primary {
104 | background-color: var(--Colors-Buttons-Primary-Default);
105 | color: var(--Colors-Buttons-Primary-Default-Text);
106 | }
107 |
108 | .button-primary:hover,
109 | .button-primary.hover {
110 | background-color: var(--Colors-Buttons-Primary-Hover);
111 | }
112 |
113 | .button-primary:active,
114 | .button-primary.active {
115 | background-color: var(--Colors-Buttons-Primary-Pressed);
116 | }
117 |
118 | .button-primary:focus
119 | .button-primary.focus {
120 | border: 1px solid var(--Colors-Buttons-Primary-Stroke-Focus);
121 | background: var(--Colors-Buttons-Primary-Hover);
122 | box-shadow: 0 0 0 4px var(--Colors-Shadow-Focus-Button);
123 | }
124 |
125 | .button-secondary {
126 | background-color: transparent;
127 | border: 1.5px solid var(--Colors-Buttons-Secondary-Default);
128 | color: var(--Colors-Buttons-Secondary-Default-Text);
129 | }
130 |
131 | .button-secondary:hover,
132 | .button-secondary.hover {
133 | border: 1.5px solid var(--Colors-Buttons-Secondary-Hover);
134 | color: var(--Colors-Buttons-Secondary-Hover-Text);
135 | }
136 |
137 | .button-secondary:focus,
138 | .button-secondary.focus {
139 | border: 1.5px solid var(--Colors-Buttons-Secondary-Hover);
140 | color: var(--Colors-Buttons-Secondary-Hover-Text);
141 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.25);
142 | }
143 |
144 | .button-secondary:active,
145 | .button-secondary.active {
146 | border: 1.5px solid var(--Colors-Buttons-Secondary-Pressed);
147 | color: var(--Colors-Buttons-Secondary-Pressed-Text);
148 | }
149 |
150 | .button-secondary:disabled,
151 | .button-secondary.disabled {
152 | opacity: 0.3;
153 | }
154 |
155 | .button-tertiary {
156 | border: 1.5px solid var(--Colors-Buttons-Tertiary-Default);
157 | background: var(--Colors-Buttons-Tertiary-Default-Background);
158 | color: var(--Colors-Buttons-Tertiary-Default-Text);
159 | }
160 |
161 | .button-tertiary:hover,
162 | .button-tertiary.hover {
163 | border: 1.5px solid var(--Colors-Buttons-Tertiary-Hover);
164 | background: var(--Colors-Buttons-Tertiary-Hover-Background);
165 | color: var(--Colors-Buttons-Tertiary-Hover-Text);
166 | }
167 |
168 | .button-tertiary:focus,
169 | .button-tertiary.focus {
170 | border: 1.5px solid var(--Colors-Buttons-Tertiary-Hover);
171 | background: var(--Colors-Buttons-Tertiary-Hover-Background);
172 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.25);
173 | color: var(--Colors-Buttons-Tertiary-Hover-Text);
174 | }
175 |
176 | .button-tertiary:active,
177 | .button-tertiary.active {
178 | border: 1.5px solid var(--Colors-Buttons-Tertiary-Pressed);
179 | color: var(--Colors-Buttons-Tertiary-Pressed-Text);
180 | }
181 |
182 | .button-tertiary:disabled,
183 | .button-tertiary.disabled {
184 | opacity: 0.3;
185 | }
186 |
187 | .button-danger {
188 | background-color: var(--Colors-Button-Danger-Default);
189 | color: var(--Colors-Buttons-Primary-Default-Text);
190 | }
191 |
192 | .button-danger:hover,
193 | .button-danger.hover {
194 | background: var(--Colors-Buttons-Danger-Hover);
195 | }
196 |
197 | .button-danger:focus,
198 | .button-danger.focus {
199 | border: 1px solid var(--Colors-Buttons-Danger-Stroke-Focus);
200 | background: var(--Colors-Buttons-Danger-Hover);
201 | box-shadow: 0 0 0 4px var(--Colors-Shadow-Focus-Button-Danger);
202 | }
203 |
204 | .button-danger:active,
205 | .button-danger.active {
206 | background: var(--Colors-Buttons-Danger-Pressed);
207 | }
208 |
209 | .button-success {
210 | background: var(--Colors-Buttons-Success-Default);
211 | color: var(--Colors-Buttons-Primary-Default-Text);
212 | }
213 |
214 | .button-success:hover,
215 | .button-success.hover {
216 | background: var(--Colors-Buttons-Success-Hover);
217 | }
218 |
219 | .button-success:focus,
220 | .button-success.focus {
221 | border: 1px solid var(--Colors-Buttons-Success-Stroke-Focus);
222 | background: var(--Colors-Buttons-Success-Hover);
223 | box-shadow: 0 0 0 4px var(--Colors-Shadow-Focus-Button-Success);
224 | }
225 |
226 | .button-success:active,
227 | .button-success.active {
228 | background: var(--Colors-Buttons-Success-Pressed);
229 | }
230 |
231 | .button-text,
232 | .button-text.button-medium,
233 | .button-text.button-large,
234 | .button-text-primary,
235 | .button-text-primary.button-medium,
236 | .button-text-primary.button-large {
237 | background: transparent;
238 | display: flex;
239 | gap: var(--UI-Spacing-spacing-xs);
240 | align-items: stretch;
241 | color: var(--Colors-Buttons-Text-Default);
242 | text-align: center;
243 | font-family: var(--body-family);
244 | font-size: var(--Fonts-Body-Default-md);
245 | font-style: normal;
246 | font-weight: 600;
247 | line-height: 145%;
248 | letter-spacing: -0.24px;
249 | height: initial;
250 | padding: 0;
251 | border-radius: 0;
252 | }
253 |
254 | .button-text.button-small,
255 | .button-text.button-xsmall,
256 | .button-text-primary.button-small,
257 | .button-text-primary.button-xsmall {
258 | height: initial;
259 | padding: 0;
260 | gap: var(--UI-Spacing-spacing-xs);
261 | flex-shrink: 0;
262 |
263 | font-size: var(--Fonts-Body-Default-xs);
264 | line-height: 135%;
265 | letter-spacing: -0.14px;
266 | }
267 |
268 | .button-text:hover,
269 | .button-text.hover {
270 | color: var(--Colors-Buttons-Text-Primary);
271 | }
272 |
273 | .button-text:focus,
274 | .button-text.focus {
275 | color: var(--Colors-Buttons-Text-Primary);
276 | border-bottom: 1px solid var(--Colors-Buttons-Text-Primary);
277 | }
278 |
279 | .button-text:active,
280 | .button-text.active {
281 | color: var(--Colors-Buttons-Text-Pressed);
282 | border-bottom: 1px solid var(--Colors-Buttons-Text-Pressed);
283 | }
284 |
285 | .button-text:disabled,
286 | .button-text.disabled {
287 | opacity: 0.3;
288 | }
289 |
290 | .button-text-primary,
291 | .button-text-primary.button-medium,
292 | .button-text-primary.button-large,
293 | .button-text-primary.button-xsmall,
294 | .button-text-primary.button-small {
295 | color: var(--Colors-Buttons-Text-Primary);
296 | }
297 |
298 | .button-text-primary:hover,
299 | .button-text-primary.hover {
300 | color: var(--Colors-Buttons-Text-Primary-Hover);
301 | }
302 |
303 | .button-text-primary:focus,
304 | .button-text-primary.focus {
305 | color: var(--Colors-Buttons-Text-Primary-Hover);
306 | border-bottom: 1px solid var(--Colors-Buttons-Text-Primary-Hover);
307 | }
308 |
309 | .button-text-primary:active,
310 | .button-text-primary.active {
311 | color: var(--Colors-Buttons-Text-Primary-Pressed);
312 | border-bottom: 1px solid var(--Colors-Buttons-Text-Primary-Pressed);
313 | }
314 |
315 | .button-text-primary:disabled,
316 | .button-text-primary.disabled {
317 | color: var(--Colors-Buttons-Primary-Default);
318 | opacity: 0.3;
319 | }
320 |
321 | .button-xsmall {
322 | height: var(--UI-Input-xs);
323 | padding: 0 var(--UI-Spacing-spacing-mxs);
324 | gap: var(--UI-Spacing-spacing-s);
325 | flex-shrink: 0;
326 |
327 | font-size: var(--Fonts-Body-Default-xs);
328 | line-height: 135%;
329 | letter-spacing: -0.14px;
330 | }
331 |
332 | .button-small {
333 | height: var(--UI-Input-sm);
334 | padding: 0 var(--UI-Spacing-spacing-ms);
335 | gap: var(--UI-Spacing-spacing-s);
336 |
337 | font-size: var(--Fonts-Body-Default-sm);
338 | line-height: 135%;
339 | letter-spacing: -0.15px;
340 | }
341 |
342 | .button-large {
343 | height: var(--UI-Input-lg);
344 | padding: 0 var(--UI-Spacing-spacing-mxl);
345 | gap: var(--UI-Spacing-spacing-s);
346 |
347 | font-size: var(--Fonts-Body-Default-xl);
348 | line-height: 140%;
349 | letter-spacing: -0.19px;
350 | }
351 |
--------------------------------------------------------------------------------
/components/numeric-slider/README.md:
--------------------------------------------------------------------------------
1 | # Numeric Slider Component
2 |
3 | A customizable numeric slider component matching the CodeSignal Design System. Supports both single value and range (dual-handle) modes, with optional input fields for direct value entry.
4 |
5 | ## Usage
6 |
7 | ### 1. Import Assets
8 |
9 | Include the CSS and JS files:
10 |
11 | ```html
12 |
13 |
14 |
18 | ```
19 |
20 | ### 2. Create Container
21 |
22 | Add a container element to your HTML where the slider will be rendered:
23 |
24 | ```html
25 |
26 | ```
27 |
28 | ### 3. Initialize Component
29 |
30 | #### Single Value Slider
31 |
32 | ```javascript
33 | const slider = new NumericSlider('#my-slider', {
34 | type: 'single',
35 | min: 0,
36 | max: 100,
37 | value: 50,
38 | showInputs: false,
39 | onChange: (value) => {
40 | console.log('Value changed:', value);
41 | }
42 | });
43 | ```
44 |
45 | #### Range Slider
46 |
47 | ```javascript
48 | const rangeSlider = new NumericSlider('#my-range-slider', {
49 | type: 'range',
50 | min: 0,
51 | max: 100,
52 | value: [20, 60],
53 | showInputs: true,
54 | onChange: (values) => {
55 | console.log('Range changed:', values[0], 'to', values[1]);
56 | }
57 | });
58 | ```
59 |
60 | ## Configuration Options
61 |
62 | | Option | Type | Default | Description |
63 | | :--- | :--- | :--- | :--- |
64 | | `type` | String | `'single'` | Slider type: `'single'` for one handle, `'range'` for two handles. |
65 | | `min` | Number | `0` | Minimum value of the slider. Must be less than `max`. |
66 | | `max` | Number | `100` | Maximum value of the slider. Must be greater than `min`. |
67 | | `step` | Number | `1` | Step increment for value changes. Values are automatically snapped to the nearest step. |
68 | | `value` | Number/Array | `null` | Initial value. For single: number (defaults to `min` if not provided). For range: `[minValue, maxValue]` array (defaults to `[min, max]` if not provided). Values are automatically clamped to `min`/`max` bounds. |
69 | | `showInputs` | Boolean | `false` | If `true`, displays input fields for direct value entry. For range sliders, inputs appear on either side of the slider. |
70 | | `theme` | String | `'default'` | Visual theme preset: `'default'` (neutral track, primary filled track, primary handles) or `'primary'` (primary track, primary filled track, primary handles). Ignored if individual theme overrides are provided. |
71 | | `trackTheme` | String | `null` | Override track color: `'neutral'` or `'primary'`. If provided, overrides the `theme` preset for the track. |
72 | | `filledTheme` | String | `null` | Override filled track color: `'neutral'` or `'primary'`. If provided, overrides the `theme` preset for the filled track. |
73 | | `handleTheme` | String | `null` | Override handle color: `'neutral'` or `'primary'`. If provided, overrides the `theme` preset for the handles. Note: Single value sliders use marker-style handles which are always primary colored. |
74 | | `continuousUpdates` | Boolean | `false` | If `true`, fires `onChange` continuously during dragging (throttled by `throttleMs`). If `false` (default), `onChange` only fires on drag end, track click, and keyboard interaction. Final value is always sent on drag end regardless of this setting. |
75 | | `throttleMs` | Number | `16` | Throttle interval in milliseconds for continuous updates during drag (~60fps at 16ms). Only applies when `continuousUpdates` is `true`. Lower values = more frequent updates but potentially lower performance. |
76 | | `disabled` | Boolean | `false` | If `true`, disables the slider and all input fields. |
77 | | `onChange` | Function | `null` | Callback triggered when value changes. Receives `(value, source)` where `value` is the new value(s) and `source` indicates the source of change (`'min'`, `'max'`, `'single'`, or `null`). By default, fires on drag end, track click, and keyboard interaction. If `continuousUpdates` is `true`, also fires during drag (throttled). |
78 | | `onInputChange` | Function | `null` | Callback triggered when value changes via input field. Receives `(value, source)` where `value` is the new value(s) and `source` indicates which input changed (`'min'`, `'max'`, or `'single'`). |
79 |
80 | ## API Methods
81 |
82 | ### `getValue()`
83 |
84 | Returns the current value(s) of the slider.
85 |
86 | **Returns:**
87 | - For single value sliders: `Number` - the current value
88 | - For range sliders: `Array` - `[minValue, maxValue]` array
89 |
90 | **Example:**
91 | ```javascript
92 | const slider = new NumericSlider('#slider', { type: 'single', value: 50 });
93 | console.log(slider.getValue()); // 50
94 |
95 | const rangeSlider = new NumericSlider('#range', { type: 'range', value: [20, 60] });
96 | console.log(rangeSlider.getValue()); // [20, 60]
97 | ```
98 |
99 | ### `setValue(value, source, triggerCallback)`
100 |
101 | Programmatically sets the slider value(s). Values are automatically clamped to `min`/`max` bounds and snapped to the nearest `step`.
102 |
103 | **Parameters:**
104 | - `value` (Number|Array): For single value sliders, a number. For range sliders, an array `[minValue, maxValue]`. Values are automatically clamped and validated.
105 | - `source` (String, optional): Source identifier for the change (`'min'`, `'max'`, `'single'`, or `null`). Defaults to `null`.
106 | - `triggerCallback` (Boolean, optional): Whether to trigger the `onChange` callback. Defaults to `true`.
107 |
108 | **Throws:**
109 | - `Error`: If called on a range slider without providing an array of two values.
110 |
111 | **Example:**
112 | ```javascript
113 | const slider = new NumericSlider('#slider', { type: 'single', min: 0, max: 100 });
114 | slider.setValue(75); // Sets value to 75
115 |
116 | const rangeSlider = new NumericSlider('#range', { type: 'range', min: 0, max: 100 });
117 | rangeSlider.setValue([25, 75]); // Sets range to 25-75
118 | rangeSlider.setValue([100, 0]); // Automatically corrects to [0, 100]
119 | ```
120 |
121 | ### `setDisabled(disabled)`
122 |
123 | Enables or disables the slider. When disabled, the slider and all input fields become non-interactive.
124 |
125 | **Parameters:**
126 | - `disabled` (Boolean): `true` to disable, `false` to enable.
127 |
128 | **Example:**
129 | ```javascript
130 | const slider = new NumericSlider('#slider', { value: 50 });
131 | slider.setDisabled(true); // Disables the slider
132 | slider.setDisabled(false); // Re-enables the slider
133 | ```
134 |
135 | ### `destroy()`
136 |
137 | Removes the slider component from the DOM and cleans up event listeners. Use this when removing the slider from the page.
138 |
139 | **Example:**
140 | ```javascript
141 | const slider = new NumericSlider('#slider', { value: 50 });
142 | // ... use slider ...
143 | slider.destroy(); // Clean up when done
144 | ```
145 |
146 | ## Examples
147 |
148 | ### Basic Single Value Slider
149 |
150 | ```javascript
151 | const slider = new NumericSlider('#slider', {
152 | type: 'single',
153 | value: 50
154 | });
155 | ```
156 |
157 | ### Range Slider with Inputs
158 |
159 | ```javascript
160 | const rangeSlider = new NumericSlider('#range-slider', {
161 | type: 'range',
162 | min: 0,
163 | max: 100,
164 | value: [20, 60],
165 | showInputs: true,
166 | onChange: (values) => {
167 | console.log(`Range: ${values[0]} - ${values[1]}`);
168 | }
169 | });
170 | ```
171 |
172 | ### Custom Range with Step
173 |
174 | ```javascript
175 | const customSlider = new NumericSlider('#custom-slider', {
176 | type: 'range',
177 | min: 0,
178 | max: 1000,
179 | step: 10,
180 | value: [200, 800],
181 | showInputs: true
182 | });
183 | ```
184 |
185 | ### Primary Theme
186 |
187 | ```javascript
188 | const primarySlider = new NumericSlider('#primary-slider', {
189 | type: 'single',
190 | theme: 'primary',
191 | value: 75
192 | });
193 | ```
194 |
195 | ### Custom Theme Overrides
196 |
197 | Control track, filled track, and handles separately:
198 |
199 | ```javascript
200 | // Custom: neutral track, primary filled, neutral handles
201 | const customSlider = new NumericSlider('#custom-slider', {
202 | type: 'range',
203 | trackTheme: 'neutral',
204 | filledTheme: 'primary',
205 | handleTheme: 'neutral',
206 | value: [20, 60]
207 | });
208 |
209 | // All primary
210 | const allPrimarySlider = new NumericSlider('#all-primary-slider', {
211 | type: 'single',
212 | theme: 'primary' // or use: trackTheme: 'primary', filledTheme: 'primary', handleTheme: 'primary'
213 | });
214 | ```
215 |
216 | ### Continuous Updates During Drag
217 |
218 | Enable continuous `onChange` callbacks while dragging (useful for live previews):
219 |
220 | ```javascript
221 | const liveSlider = new NumericSlider('#live-slider', {
222 | type: 'single',
223 | value: 50,
224 | continuousUpdates: true, // Fire onChange during drag
225 | throttleMs: 16, // ~60fps (default)
226 | onChange: (value) => {
227 | // This fires continuously while dragging (throttled)
228 | // AND on drag end (always)
229 | document.querySelector('#preview').textContent = value;
230 | }
231 | });
232 |
233 | // Heavier updates? Increase throttle interval
234 | const heavySlider = new NumericSlider('#heavy-slider', {
235 | type: 'range',
236 | value: [20, 80],
237 | continuousUpdates: true,
238 | throttleMs: 100, // 10 updates per second
239 | onChange: (values) => {
240 | // Expensive operation (e.g., chart re-render)
241 | updateComplexVisualization(values);
242 | }
243 | });
244 | ```
245 |
246 | ## Interaction
247 |
248 | ### Mouse/Touch
249 | - Click and drag handles to adjust values
250 | - Click on the track to move the nearest handle to that position
251 | - For range sliders, handles cannot cross each other
252 |
253 | ### Keyboard
254 | - **Arrow Left/Down**: Decrease value by step
255 | - **Arrow Right/Up**: Increase value by step
256 | - **Shift + Arrow**: Change value by 10x step
257 | - **Home**: Set to minimum value
258 | - **End**: Set to maximum value
259 |
260 | ### Input Fields
261 | - When `showInputs` is `true`, input fields allow direct value entry
262 | - For single value sliders: one input field appears on the left side of the slider
263 | - For range sliders: two input fields appear on either side of the slider (min on left, max on right)
264 | - Input fields have `id` and `name` attributes generated from the container element's ID (format: `{containerId}-min`, `{containerId}-max`, or `{containerId}-value`)
265 | - Values are automatically clamped to min/max bounds
266 | - For range sliders, min input cannot exceed max value and vice versa
267 | - Invalid input (non-numeric) is reset to the current value on blur
268 | - Input fields use the same styling as the input component
269 |
270 | ## Dependencies
271 |
272 | This component relies on variables from:
273 | - `design-system/colors/colors.css`
274 | - `design-system/spacing/spacing.css`
275 | - `design-system/typography/typography.css`
276 | - `design-system/components/input/input.css` (for input field styling)
277 |
278 | ## Value Validation
279 |
280 | The slider automatically validates and corrects values:
281 |
282 | - **Clamping**: Values are automatically clamped to the `min` and `max` bounds
283 | - **Step Snapping**: Values are snapped to the nearest `step` increment
284 | - **Range Validation**: For range sliders, if min > max, values are automatically swapped
285 | - **Input Validation**: Invalid input field values are reset to the current slider value on blur
286 |
287 | ## Accessibility
288 |
289 | The slider component includes comprehensive accessibility features:
290 |
291 | - **ARIA Attributes**:
292 | - `role="slider"` on the slider wrapper
293 | - `aria-valuemin`, `aria-valuemax`, `aria-valuenow` attributes
294 | - `aria-label` attributes on handles and wrapper for screen readers
295 | - **Keyboard Navigation**: Full keyboard support with arrow keys, Home, End, and Shift modifiers
296 | - **Focus Indicators**: Visible focus outlines for keyboard navigation
297 | - **Input Fields**: Proper `id` and `name` attributes for form integration and screen readers
298 | - **Disabled State**: Properly communicates disabled state to assistive technologies
299 |
300 | ## Error Handling
301 |
302 | - **Invalid Container**: Throws `Error` if container element is not found
303 | - **Invalid Range Value**: `setValue()` throws `Error` if called on a range slider without an array of two values
304 | - **Invalid Input**: Input fields reset to current value if invalid input is entered
305 |
306 | ## Browser Support
307 |
308 | The component supports all modern browsers:
309 | - Chrome/Edge (latest)
310 | - Firefox (latest)
311 | - Safari (latest)
312 | - Mobile browsers (iOS Safari, Chrome Mobile)
313 |
314 | ## Notes
315 |
316 | - Single value sliders use marker-style handles which are always primary colored, regardless of `handleTheme` setting
317 | - In dark mode, both neutral and primary tracks use the same color (`#060C1C` at 50% opacity)
318 | - Input fields are automatically sized and styled to match the design system
319 | - The component uses CSS custom properties for theming, supporting both light and dark modes automatically
320 |
321 |
--------------------------------------------------------------------------------
/typography/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CodeSignal Typography Reference
7 |
8 |
9 |
10 |
11 |
12 |
13 |
124 |
125 |
126 | CodeSignal Typography Reference
127 | Visual reference for all typography styles defined in typography.css
128 |
129 | Body Text Styles
130 | Default body text styles using Work Sans
131 |
132 |
133 |
134 |
135 |
136 | Body Elegant Styles
137 | Elegant body text styles using Founders Grotesk
138 |
139 |
140 |
141 |
142 |
143 | Heading Styles
144 | Heading styles using Founders Grotesk
145 |
146 |
147 |
148 |
149 |
150 | Label Styles
151 | Label styles using Work Sans (uppercase)
152 |
153 |
154 |
155 |
156 |
157 | Label Number Styles
158 | Label number styles using Work Sans
159 |
160 |
161 |
162 |
163 |
164 | Code Styles
165 | Code/monospace text styles using JetBrains Mono
166 |
167 |
168 |
169 |
170 |
171 |
323 |
324 |
325 |
--------------------------------------------------------------------------------
/components/horizontal-cards/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Component Tests - Horizontal Cards
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
120 |
121 |
122 |
123 |
124 | Component Tests - Horizontal Cards
125 |
126 |
127 |
128 |
129 | Basic Horizontal Cards
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | Current Index: 0
138 | Current Card: Boss 1
139 |
140 |
141 |
142 |
143 |
144 | Cards without Navigation Buttons
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 | Scroll horizontally or use arrow keys to navigate
154 |
155 |
156 |
157 |
158 |
159 |
160 | Many Cards
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | Current Index: 0
169 | Total Cards: 5
170 |
171 |
172 |
173 |
174 |
175 | Custom Card Content
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | Cards without Action Area
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | These cards should display without any action area at the bottom.
196 |
197 |
198 |
199 |
200 |
201 |
202 | Cards with HTML Content
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | These cards demonstrate HTML support in title and description fields, including links, bold text, and other formatting.
212 |
213 |
214 |
215 |
216 |
217 |
349 |
350 |
351 |
352 |
--------------------------------------------------------------------------------