|
548 | 548 | <span class="config-meta-item">Modified: <strong>{formatDate(config.updated_at)}</strong></span> |
549 | 549 | {/if} |
550 | 550 | </div> |
551 | | - <div class="config-url" onclick={(e) => e.stopPropagation()}> |
| 551 | + <div class="config-url" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation"> |
552 | 552 | <code>curl -fsSL {getInstallUrl(config)} | bash</code> |
553 | 553 | <button class="copy-btn" onclick={() => copyToClipboard(`curl -fsSL https://${getInstallUrl(config)} | bash`, config.id)}>{copiedId === config.id ? 'Copied!' : 'Copy'}</button> |
554 | 554 | </div> |
555 | | - <div class="config-actions" onclick={(e) => e.stopPropagation()}> |
| 555 | + <div class="config-actions" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation"> |
556 | 556 | <Button variant="secondary" onclick={() => editConfig(config.slug)}>Edit</Button> |
557 | 557 | <Button variant="secondary" onclick={() => duplicateConfig(config.slug)}>Duplicate</Button> |
558 | 558 | <Button variant="secondary" onclick={() => shareConfig(config)}>Share</Button> |
|
566 | 566 | </main> |
567 | 567 |
|
568 | 568 | {#if showModal} |
569 | | - <div class="modal-overlay" onclick={closeModal}> |
570 | | - <div class="modal" onclick={(e) => e.stopPropagation()}> |
| 569 | + <div class="modal-overlay" onclick={closeModal} onkeydown={(e) => e.key === 'Escape' && closeModal()} role="dialog" tabindex="0"> |
| 570 | + <div class="modal" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation"> |
571 | 571 | <div class="modal-header"> |
572 | 572 | <h3 class="modal-title">{editingSlug ? 'Edit Configuration' : 'New Configuration'}</h3> |
573 | 573 | <button class="close-btn" onclick={closeModal}>×</button> |
|
577 | 577 | <div class="error-message">{error}</div> |
578 | 578 | {/if} |
579 | 579 |
|
580 | | - <div class="form-group"> |
581 | | - <label class="form-label">Name</label> |
582 | | - <input type="text" class="form-input" bind:value={formData.name} placeholder="e.g. Frontend Team" /> |
583 | | - <p class="form-hint">Will be used as the URL slug</p> |
584 | | - </div> |
| 580 | + <div class="form-group"> |
| 581 | + <label class="form-label" for="config-name">Name</label> |
| 582 | + <input id="config-name" type="text" class="form-input" bind:value={formData.name} placeholder="e.g. Frontend Team" /> |
| 583 | + <p class="form-hint">Will be used as the URL slug</p> |
| 584 | + </div> |
585 | 585 |
|
586 | | - <div class="form-group"> |
587 | | - <label class="form-label">Description</label> |
588 | | - <input type="text" class="form-input" bind:value={formData.description} placeholder="Optional description" /> |
589 | | - </div> |
| 586 | + <div class="form-group"> |
| 587 | + <label class="form-label" for="config-description">Description</label> |
| 588 | + <input id="config-description" type="text" class="form-input" bind:value={formData.description} placeholder="Optional description" /> |
| 589 | + </div> |
590 | 590 |
|
591 | | - <div class="form-group"> |
592 | | - <label class="form-label">Base Preset</label> |
593 | | - <select class="form-select" onchange={(e) => handlePresetChange(e.currentTarget.value)} value={formData.base_preset}> |
| 591 | + <div class="form-group"> |
| 592 | + <label class="form-label" for="config-preset">Base Preset</label> |
| 593 | + <select id="config-preset" class="form-select" onchange={(e) => handlePresetChange(e.currentTarget.value)} value={formData.base_preset}> |
594 | 594 | <option value="minimal">minimal - CLI essentials</option> |
595 | 595 | <option value="developer">developer - Ready-to-code setup</option> |
596 | 596 | <option value="full">full - Complete dev environment</option> |
|
604 | 604 | </label> |
605 | 605 | </div> |
606 | 606 |
|
607 | | - <div class="form-group"> |
608 | | - <label class="form-label">Short Alias (Optional)</label> |
609 | | - <div class="alias-input"> |
610 | | - <span class="alias-prefix">openboot.dev/</span> |
611 | | - <input type="text" class="form-input" bind:value={formData.alias} placeholder="e.g. myteam" /> |
612 | | - </div> |
613 | | - <p class="form-hint">2-20 characters, lowercase letters, numbers, and dashes only.</p> |
| 607 | + <div class="form-group"> |
| 608 | + <label class="form-label" for="config-alias">Short Alias (Optional)</label> |
| 609 | + <div class="alias-input"> |
| 610 | + <span class="alias-prefix">openboot.dev/</span> |
| 611 | + <input id="config-alias" type="text" class="form-input" bind:value={formData.alias} placeholder="e.g. myteam" /> |
614 | 612 | </div> |
| 613 | + <p class="form-hint">2-20 characters, lowercase letters, numbers, and dashes only.</p> |
| 614 | + </div> |
615 | 615 |
|
616 | | - <div class="form-group"> |
617 | | - <label class="form-label">Dotfiles Repository (Optional)</label> |
618 | | - <input type="text" class="form-input" bind:value={formData.dotfiles_repo} placeholder="https://github.com/username/dotfiles" /> |
619 | | - <p class="form-hint">After installing packages, OpenBoot will clone this repo and deploy configs via stow.</p> |
620 | | - </div> |
| 616 | + <div class="form-group"> |
| 617 | + <label class="form-label" for="config-dotfiles">Dotfiles Repository (Optional)</label> |
| 618 | + <input id="config-dotfiles" type="text" class="form-input" bind:value={formData.dotfiles_repo} placeholder="https://github.com/username/dotfiles" /> |
| 619 | + <p class="form-hint">After installing packages, OpenBoot will clone this repo and deploy configs via stow.</p> |
| 620 | + </div> |
621 | 621 |
|
622 | 622 | {#if true} |
623 | 623 | {@const grouped = getGroupedPackages()} |
|
747 | 747 | </div> |
748 | 748 | {/if} |
749 | 749 |
|
750 | | - <div class="form-group"> |
751 | | - <label class="form-label">Custom Post-Install Script (Optional)</label> |
752 | | - <textarea class="form-textarea" bind:value={formData.custom_script} placeholder="#!/bin/bash # Commands to run after installation"></textarea> |
753 | | - </div> |
| 750 | + <div class="form-group"> |
| 751 | + <label class="form-label" for="config-script">Custom Post-Install Script (Optional)</label> |
| 752 | + <textarea id="config-script" class="form-textarea" bind:value={formData.custom_script} placeholder="#!/bin/bash # Commands to run after installation"></textarea> |
| 753 | + </div> |
754 | 754 | </div> |
755 | 755 | <div class="modal-footer"> |
756 | 756 | <Button variant="secondary" onclick={closeModal}>Cancel</Button> |
|
761 | 761 | {/if} |
762 | 762 |
|
763 | 763 | {#if showImportModal} |
764 | | - <div class="modal-overlay" onclick={() => showImportModal = false}> |
765 | | - <div class="modal import-modal" onclick={(e) => e.stopPropagation()}> |
| 764 | + <div class="modal-overlay" onclick={() => showImportModal = false} onkeydown={(e) => e.key === 'Escape' && (showImportModal = false)} role="dialog" tabindex="0"> |
| 765 | + <div class="modal import-modal" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation"> |
766 | 766 | <div class="modal-header"> |
767 | 767 | <h3 class="modal-title">Import Brewfile</h3> |
768 | 768 | <button class="close-btn" onclick={() => showImportModal = false}>×</button> |
|
772 | 772 | <div class="error-message">{importError}</div> |
773 | 773 | {/if} |
774 | 774 |
|
775 | | - <div class="form-group"> |
776 | | - <label class="form-label">Paste your Brewfile content</label> |
777 | | - <textarea |
778 | | - class="form-textarea brewfile-input" |
779 | | - bind:value={brewfileContent} |
780 | | - placeholder={'tap "homebrew/cask"\nbrew "git"\nbrew "node"\ncask "visual-studio-code"\ncask "docker"'} |
781 | | - ></textarea> |
782 | | - <p class="form-hint">Supports tap, brew, and cask entries</p> |
783 | | - </div> |
| 775 | + <div class="form-group"> |
| 776 | + <label class="form-label" for="brewfile-input">Paste your Brewfile content</label> |
| 777 | + <textarea |
| 778 | + id="brewfile-input" |
| 779 | + class="form-textarea brewfile-input" |
| 780 | + bind:value={brewfileContent} |
| 781 | + placeholder={'tap "homebrew/cask"\nbrew "git"\nbrew "node"\ncask "visual-studio-code"\ncask "docker"'} |
| 782 | + ></textarea> |
| 783 | + <p class="form-hint">Supports tap, brew, and cask entries</p> |
| 784 | + </div> |
784 | 785 | </div> |
785 | 786 | <div class="modal-footer"> |
786 | 787 | <Button variant="secondary" onclick={() => showImportModal = false}>Cancel</Button> |
|
791 | 792 | {/if} |
792 | 793 |
|
793 | 794 | {#if showShareModal} |
794 | | - <div class="modal-overlay" onclick={closeShareModal} onkeydown={(e) => e.key === 'Escape' && closeShareModal()} role="dialog" tabindex="-1"> |
795 | | - <div class="modal share-modal" onclick={(e) => e.stopPropagation()}> |
| 795 | + <div class="modal-overlay" onclick={closeShareModal} onkeydown={(e) => e.key === 'Escape' && closeShareModal()} role="dialog" tabindex="0"> |
| 796 | + <div class="modal share-modal" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()} role="presentation"> |
796 | 797 | <div class="modal-header"> |
797 | 798 | <h3 class="modal-title">Share Configuration</h3> |
798 | 799 | <button class="close-btn" onclick={closeShareModal}>×</button> |
|
0 commit comments