Created
January 22, 2026 11:49
-
-
Save sunmeat/931ceaf79d81bf96bb53bb673f900cdb to your computer and use it in GitHub Desktop.
приклад вбудованих компонентів + скоуп цсс
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @* ContactInfo.razor — демонстрація всіх основних вбудованих Blazor input-компонентів *@ | |
| <div class="contacts-wrapper"> | |
| <div class="contacts-container"> | |
| <div class="header-section"> | |
| <h3 class="title">Контактна інформація</h3> | |
| <div class="title-underline"></div> | |
| </div> | |
| <div class="contact-display"> | |
| <div class="contact-item email-item"> | |
| <span class="icon">✉️</span> | |
| <span class="label">Основний email:</span> | |
| <span class="value">@Email</span> | |
| </div> | |
| @if (ShowPhone) | |
| { | |
| <div class="contact-item phone-item fade-in"> | |
| <span class="icon">📞</span> | |
| <span class="label">Телефон:</span> | |
| <span class="value">@Phone</span> | |
| </div> | |
| } | |
| </div> | |
| <div class="form-section"> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputCheckbox — аналог <input type="checkbox"> --> | |
| <!-- Відмінність: автоматична інтеграція з EditForm + валідацією --> | |
| <!-- Підтримує @bind-Value, aria-атрибути автоматично --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group checkbox-group"> | |
| <label class="checkbox-label"> | |
| <InputCheckbox @bind-Value="ShowPhone" /> | |
| <span class="checkbox-text">Показати номер телефону</span> | |
| <span class="checkbox-custom"></span> | |
| </label> | |
| </div> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputText — аналог <input type="text"> --> | |
| <!-- Відмінність: двосторонній біндинг через @bind-Value --> | |
| <!-- Автоматично додає aria-invalid при помилках у формі --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group"> | |
| <label class="input-label"> | |
| <span class="label-text">Альтернативний email:</span> | |
| <InputText @bind-Value="AlternativeEmail" | |
| class="input-field" | |
| placeholder="наприклад, robota@example.com" /> | |
| <small class="input-hint"> | |
| @if (string.IsNullOrEmpty(AlternativeEmail)) | |
| { | |
| <span class="hint-empty">Поле не заповнено</span> | |
| } | |
| else | |
| { | |
| <span class="hint-filled">✓ Введено: @AlternativeEmail</span> | |
| } | |
| </small> | |
| </label> | |
| </div> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputTextArea — аналог <textarea> --> | |
| <!-- Відмінність: той самий @bind-Value, підтримує рядки --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group"> | |
| <label class="input-label"> | |
| <span class="label-text">Коротка біо / примітка:</span> | |
| <InputTextArea @bind-Value="Bio" | |
| rows="4" | |
| class="textarea-field" | |
| placeholder="Напишіть пару слів про себе..." /> | |
| <small class="char-counter">@Bio.Length / 500 символів</small> | |
| </label> | |
| </div> | |
| <div class="input-row-dual"> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputNumber<TValue> — аналог <input type="number"> --> | |
| <!-- Відмінність: типобезпечний (тут int), підтримує min/max/step --> | |
| <!-- У .NET 9+ також підтримує type="range" (слайдер) --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group half-width"> | |
| <label class="input-label"> | |
| <span class="label-text">Роки досвіду в .NET:</span> | |
| <div class="number-input-wrapper"> | |
| <InputNumber @bind-Value="YearsOfExperience" | |
| class="input-field number-field" | |
| min="0" max="50" step="1" /> | |
| <span class="number-badge">@YearsOfExperience років</span> | |
| </div> | |
| </label> | |
| </div> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputDate<TValue> — аналог <input type="date"> --> | |
| <!-- Відмінність: працює з DateTime/DateOnly, локалізація --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group half-width"> | |
| <label class="input-label"> | |
| <span class="label-text">Дата народження:</span> | |
| <InputDate @bind-Value="BirthDate" class="input-field date-field" /> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputSelect<TValue> — аналог <select> --> | |
| <!-- Відмінність: @bind-Value працює з будь-яким типом --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group"> | |
| <label class="input-label"> | |
| <span class="label-text">Улюблена технологія:</span> | |
| <div class="select-wrapper"> | |
| <InputSelect @bind-Value="FavoriteTech" class="input-field select-field"> | |
| <option value="">Оберіть технологію...</option> | |
| <option value="Blazor">🔥 Blazor</option> | |
| <option value="MAUI">📱 .NET MAUI</option> | |
| <option value="AspNet">🌐 ASP.NET Core</option> | |
| <option value="EfCore">💾 Entity Framework</option> | |
| </InputSelect> | |
| <span class="select-arrow">▼</span> | |
| </div> | |
| </label> | |
| </div> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <!-- InputRadioGroup + InputRadio — вибір з групи --> | |
| <!-- Відмінність: групує радіо-кнопки, @bind-Value один на групу --> | |
| <!-- Працює з рядками, enum тощо --> | |
| <!-- ─────────────────────────────────────────────── --> | |
| <div class="input-group radio-section"> | |
| <span class="label-text">Режим рендерингу:</span> | |
| <InputRadioGroup @bind-Value="RenderMode" class="radio-group"> | |
| <label class="radio-label"> | |
| <InputRadio Value="@("Server")" /> | |
| <span class="radio-custom"></span> | |
| <span class="radio-text"> | |
| <strong>Server</strong> | |
| <small>Рендеринг на сервері</small> | |
| </span> | |
| </label> | |
| <label class="radio-label"> | |
| <InputRadio Value="@("WebAssembly")" /> | |
| <span class="radio-custom"></span> | |
| <span class="radio-text"> | |
| <strong>WebAssembly</strong> | |
| <small>Виконання в браузері</small> | |
| </span> | |
| </label> | |
| <label class="radio-label"> | |
| <InputRadio Value="@("Auto")" /> | |
| <span class="radio-custom"></span> | |
| <span class="radio-text"> | |
| <strong>Auto</strong> | |
| <small>Автоматичний вибір</small> | |
| </span> | |
| </label> | |
| </InputRadioGroup> | |
| <div class="radio-indicator"> | |
| <span class="indicator-label">Обрано:</span> | |
| <span class="indicator-value">@RenderMode</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| @code { | |
| [Parameter] public string Email { get; set; } = "sunmeatrich@gmail.com"; | |
| [Parameter] public string Phone { get; set; } = "+380 (63) 03-000-35"; | |
| private bool ShowPhone { get; set; } = false; | |
| private string AlternativeEmail { get; set; } = ""; | |
| private string Bio { get; set; } = ""; | |
| private int YearsOfExperience { get; set; } = 5; | |
| private DateTime? BirthDate { get; set; } = new DateTime(1989, 3, 10); | |
| private string FavoriteTech { get; set; } = "Blazor"; | |
| private string RenderMode { get; set; } = "Auto"; | |
| } | |
| <style> | |
| <!-- ─────────────────────────────────────────────── | |
| // CSS ІЗОЛЯЦІЯ в Blazor (.razor.css або <style>) | |
| // ─────────────────────────────────────────────── | |
| // 1. Стилі автоматично скоуплені тільки до цього компонента | |
| // 2. Blazor додає унікальний атрибут типу b-abc123 до елементів | |
| // 3. Стилі НЕ протікають на інші компоненти → немає конфліктів | |
| // 4. Можна використовувати :global(...) якщо треба глобальний стиль | |
| // 5. У .NET 8+ рекомендовано виносити в окремий файл *.razor.css | |
| ───────────────────────────────────────────────── --> | |
| .contacts-wrapper { | |
| width: 100%; | |
| min-height: 100vh; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| padding: 2rem 1rem; | |
| display: flex; | |
| justify-content: center; | |
| align-items: flex-start; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .contacts-container { | |
| max-width: 800px; | |
| width: 100%; | |
| background: #ffffff; | |
| border-radius: 24px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), | |
| 0 0 0 1px rgba(255, 255, 255, 0.1); | |
| padding: 3rem; | |
| animation: slideIn 0.6s ease-out; | |
| } | |
| @@keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .header-section { | |
| text-align: center; | |
| margin-bottom: 2.5rem; | |
| } | |
| .title { | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin: 0; | |
| padding: 0; | |
| letter-spacing: -0.5px; | |
| } | |
| .title-underline { | |
| width: 80px; | |
| height: 4px; | |
| background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); | |
| margin: 1rem auto 0; | |
| border-radius: 2px; | |
| animation: expandWidth 0.8s ease-out; | |
| } | |
| @@keyframes expandWidth { | |
| from { | |
| width: 0; | |
| } | |
| to { | |
| width: 80px; | |
| } | |
| } | |
| .contact-display { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| margin-bottom: 2rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .contact-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| padding: 0.75rem; | |
| background: white; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| transition: transform 0.3s ease, box-shadow 0.3s ease; | |
| } | |
| .contact-item:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); | |
| } | |
| .contact-item .icon { | |
| font-size: 1.5rem; | |
| flex-shrink: 0; | |
| } | |
| .contact-item .label { | |
| font-weight: 600; | |
| color: #4a5568; | |
| flex-shrink: 0; | |
| } | |
| .contact-item .value { | |
| color: #2d3748; | |
| font-weight: 500; | |
| margin-left: auto; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.4s ease-out; | |
| } | |
| @@keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .form-section { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1.5rem; | |
| } | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| position: relative; | |
| } | |
| .input-label { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| width: 100%; | |
| } | |
| .label-text { | |
| font-weight: 600; | |
| color: #2d3748; | |
| font-size: 0.95rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .label-text::before { | |
| content: ''; | |
| width: 4px; | |
| height: 18px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 2px; | |
| } | |
| .input-field { | |
| padding: 0.875rem 1rem; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 12px; | |
| font-size: 1rem; | |
| color: #2d3748; | |
| background: white; | |
| transition: all 0.3s ease; | |
| font-family: inherit; | |
| outline: none; | |
| } | |
| .input-field:focus { | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); | |
| transform: translateY(-1px); | |
| } | |
| .input-field:hover:not(:focus) { | |
| border-color: #cbd5e0; | |
| } | |
| .input-field::placeholder { | |
| color: #a0aec0; | |
| } | |
| .checkbox-group { | |
| background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%); | |
| padding: 1.25rem; | |
| border-radius: 12px; | |
| margin-bottom: 0.5rem; | |
| } | |
| .checkbox-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| cursor: pointer; | |
| position: relative; | |
| user-select: none; | |
| } | |
| .checkbox-label input[type="checkbox"] { | |
| position: absolute; | |
| opacity: 0; | |
| cursor: pointer; | |
| width: 0; | |
| height: 0; | |
| } | |
| .checkbox-custom { | |
| width: 24px; | |
| height: 24px; | |
| border: 2px solid #2d3748; | |
| border-radius: 6px; | |
| background: white; | |
| position: relative; | |
| transition: all 0.3s ease; | |
| flex-shrink: 0; | |
| } | |
| .checkbox-label:hover .checkbox-custom { | |
| border-color: #667eea; | |
| transform: scale(1.1); | |
| } | |
| .checkbox-label input[type="checkbox"]:checked ~ .checkbox-custom { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-color: #667eea; | |
| } | |
| .checkbox-label input[type="checkbox"]:checked ~ .checkbox-custom::after { | |
| content: '✓'; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: white; | |
| font-weight: bold; | |
| font-size: 14px; | |
| } | |
| .checkbox-text { | |
| font-weight: 600; | |
| color: #2d3748; | |
| font-size: 1.05rem; | |
| } | |
| .textarea-field { | |
| padding: 0.875rem 1rem; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 12px; | |
| font-size: 1rem; | |
| color: #2d3748; | |
| background: white; | |
| transition: all 0.3s ease; | |
| font-family: inherit; | |
| resize: vertical; | |
| min-height: 100px; | |
| outline: none; | |
| } | |
| .textarea-field:focus { | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); | |
| } | |
| .char-counter { | |
| text-align: right; | |
| color: #718096; | |
| font-size: 0.85rem; | |
| font-style: italic; | |
| } | |
| .input-hint { | |
| font-size: 0.875rem; | |
| color: #718096; | |
| padding-left: 0.5rem; | |
| } | |
| .hint-empty { | |
| color: #a0aec0; | |
| } | |
| .hint-filled { | |
| color: #48bb78; | |
| font-weight: 500; | |
| } | |
| .input-row-dual { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1.5rem; | |
| } | |
| .half-width { | |
| width: 100%; | |
| } | |
| @@media (max-width: 640px) { | |
| .input-row-dual { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| .number-input-wrapper { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| } | |
| .number-field { | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .number-badge { | |
| padding: 0.5rem 1rem; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 20px; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| white-space: nowrap; | |
| box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); | |
| } | |
| .date-field { | |
| cursor: pointer; | |
| } | |
| .date-field::-webkit-calendar-picker-indicator { | |
| cursor: pointer; | |
| filter: invert(0.5); | |
| transition: filter 0.3s ease; | |
| } | |
| .date-field:hover::-webkit-calendar-picker-indicator { | |
| filter: invert(0.3); | |
| } | |
| .select-wrapper { | |
| position: relative; | |
| } | |
| .select-field { | |
| appearance: none; | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| width: 100%; | |
| cursor: pointer; | |
| padding-right: 3rem; | |
| } | |
| .select-arrow { | |
| position: absolute; | |
| right: 1rem; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| pointer-events: none; | |
| color: #667eea; | |
| font-size: 0.8rem; | |
| transition: transform 0.3s ease; | |
| } | |
| .select-field:focus ~ .select-arrow { | |
| transform: translateY(-50%) rotate(180deg); | |
| } | |
| .radio-section { | |
| background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); | |
| padding: 1.5rem; | |
| border-radius: 16px; | |
| } | |
| .radio-section > .label-text { | |
| margin-bottom: 1rem; | |
| font-size: 1.05rem; | |
| } | |
| .radio-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .radio-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| padding: 1rem; | |
| background: white; | |
| border: 2px solid transparent; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .radio-label:hover { | |
| border-color: #667eea; | |
| box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15); | |
| transform: translateX(4px); | |
| } | |
| .radio-label input[type="radio"] { | |
| position: absolute; | |
| opacity: 0; | |
| cursor: pointer; | |
| width: 0; | |
| height: 0; | |
| } | |
| .radio-custom { | |
| width: 24px; | |
| height: 24px; | |
| border: 2px solid #cbd5e0; | |
| border-radius: 50%; | |
| position: relative; | |
| flex-shrink: 0; | |
| transition: all 0.3s ease; | |
| background: white; | |
| } | |
| .radio-label input[type="radio"]:checked ~ .radio-custom { | |
| border-color: #667eea; | |
| border-width: 3px; | |
| } | |
| .radio-label input[type="radio"]:checked ~ .radio-custom::after { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| animation: scaleIn 0.2s ease-out; | |
| } | |
| @@keyframes scaleIn { | |
| from { | |
| transform: translate(-50%, -50%) scale(0); | |
| } | |
| to { | |
| transform: translate(-50%, -50%) scale(1); | |
| } | |
| } | |
| .radio-text { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.25rem; | |
| } | |
| .radio-text strong { | |
| color: #2d3748; | |
| font-size: 1rem; | |
| } | |
| .radio-text small { | |
| color: #718096; | |
| font-size: 0.85rem; | |
| } | |
| .radio-indicator { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| padding: 0.75rem 1rem; | |
| background: white; | |
| border-radius: 8px; | |
| border-left: 4px solid #667eea; | |
| } | |
| .indicator-label { | |
| font-weight: 600; | |
| color: #4a5568; | |
| font-size: 0.9rem; | |
| } | |
| .indicator-value { | |
| padding: 0.25rem 0.75rem; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-radius: 12px; | |
| font-weight: 600; | |
| font-size: 0.85rem; | |
| } | |
| @@media (max-width: 768px) { | |
| .contacts-container { | |
| padding: 2rem 1.5rem; | |
| border-radius: 16px; | |
| } | |
| .title { | |
| font-size: 2rem; | |
| } | |
| .contact-item { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 0.5rem; | |
| } | |
| .contact-item .value { | |
| margin-left: 0; | |
| } | |
| .radio-label { | |
| padding: 0.875rem; | |
| } | |
| .number-input-wrapper { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| .number-badge { | |
| text-align: center; | |
| } | |
| } | |
| @@media (max-width: 480px) { | |
| .contacts-wrapper { | |
| padding: 1rem 0.5rem; | |
| } | |
| .contacts-container { | |
| padding: 1.5rem 1rem; | |
| } | |
| .title { | |
| font-size: 1.75rem; | |
| } | |
| .input-field, | |
| .textarea-field { | |
| font-size: 0.95rem; | |
| } | |
| } | |
| .checkbox-label:focus-within .checkbox-custom, | |
| .radio-label:focus-within .radio-custom { | |
| outline: 2px solid #667eea; | |
| outline-offset: 2px; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| @@media (prefers-reduced-motion: reduce) { | |
| *, | |
| *::before, | |
| *::after { | |
| animation-duration: 0.01ms !important; | |
| animation-iteration-count: 1 !important; | |
| transition-duration: 0.01ms !important; | |
| } | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment