Coverage for fastblocks/adapters/styles/kelpui.py: 0%

124 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-09 00:47 -0700

1"""KelpUI styles adapter for FastBlocks with component system.""" 

2 

3from contextlib import suppress 

4from typing import Any 

5from uuid import UUID 

6 

7from acb.config import Settings 

8from acb.depends import depends 

9 

10from ._base import StylesBase 

11 

12 

13class KelpUISettings(Settings): # type: ignore[misc] 

14 """Settings for KelpUI styles adapter.""" 

15 

16 # Required ACB 0.19.0+ metadata 

17 MODULE_ID: UUID = UUID("01937d86-8b6d-a07e-c9fa-e8f9a0b1c2d3") # Static UUID7 

18 MODULE_STATUS: str = "stable" 

19 

20 # KelpUI configuration 

21 version: str = "latest" 

22 cdn_url: str = "https://cdn.jsdelivr.net/npm/kelpui" 

23 theme: str = "default" # default, dark, ocean, forest, sunset 

24 

25 # Color system 

26 primary_hue: int = 210 # Blue 

27 secondary_hue: int = 160 # Green 

28 accent_hue: int = 45 # Orange 

29 neutral_hue: int = 220 # Cool gray 

30 

31 # Spacing system (rem units) 

32 spacing_scale: list[str] = [ 

33 "0", 

34 "0.25", 

35 "0.5", 

36 "0.75", 

37 "1", 

38 "1.25", 

39 "1.5", 

40 "2", 

41 "2.5", 

42 "3", 

43 "4", 

44 "5", 

45 "6", 

46 "8", 

47 "10", 

48 "12", 

49 "16", 

50 "20", 

51 "24", 

52 ] 

53 

54 # Typography 

55 font_family_sans: str = "Inter, system-ui, -apple-system, sans-serif" 

56 font_family_mono: str = "JetBrains Mono, 'Fira Code', Consolas, monospace" 

57 font_scale: dict[str, str] = { 

58 "xs": "0.75rem", 

59 "sm": "0.875rem", 

60 "base": "1rem", 

61 "lg": "1.125rem", 

62 "xl": "1.25rem", 

63 "2xl": "1.5rem", 

64 "3xl": "1.875rem", 

65 "4xl": "2.25rem", 

66 "5xl": "3rem", 

67 "6xl": "3.75rem", 

68 } 

69 

70 # Border radius 

71 radius_scale: dict[str, str] = { 

72 "none": "0", 

73 "sm": "0.125rem", 

74 "base": "0.25rem", 

75 "md": "0.375rem", 

76 "lg": "0.5rem", 

77 "xl": "0.75rem", 

78 "2xl": "1rem", 

79 "3xl": "1.5rem", 

80 "full": "9999px", 

81 } 

82 

83 # Shadow system 

84 enable_shadows: bool = True 

85 enable_animations: bool = True 

86 

87 

88class KelpUIAdapter(StylesBase): 

89 """KelpUI styles adapter with modern component system.""" 

90 

91 # Required ACB 0.19.0+ metadata 

92 MODULE_ID: UUID = UUID("01937d86-8b6d-a07e-c9fa-e8f9a0b1c2d3") # Static UUID7 

93 MODULE_STATUS: str = "stable" 

94 

95 def __init__(self) -> None: 

96 """Initialize KelpUI adapter.""" 

97 super().__init__() 

98 self.settings: KelpUISettings | None = None 

99 

100 # Register with ACB dependency system 

101 with suppress(Exception): 

102 depends.set(self) 

103 

104 def get_stylesheet_links(self) -> list[str]: 

105 """Get KelpUI stylesheet links.""" 

106 if not self.settings: 

107 self.settings = KelpUISettings() 

108 

109 links = [] 

110 

111 # KelpUI base CSS (if available from CDN) 

112 # Note: KelpUI might be a custom framework, so we generate it inline 

113 kelp_css = self._generate_kelpui_css() 

114 links.append(f"<style>{kelp_css}</style>") 

115 

116 # Inter font for better typography 

117 links.extend( 

118 ( 

119 '<link rel="preconnect" href="https://fonts.googleapis.com">', 

120 '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>', 

121 '<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">', 

122 '<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800&display=swap" rel="stylesheet">', 

123 ) 

124 ) 

125 

126 return links 

127 

128 def _generate_kelpui_css(self) -> str: 

129 """Generate KelpUI CSS framework.""" 

130 if not self.settings: 

131 self.settings = KelpUISettings() 

132 

133 # Generate color variables based on HSL 

134 color_vars = self._generate_color_variables() 

135 spacing_vars = self._generate_spacing_variables() 

136 typography_vars = self._generate_typography_variables() 

137 radius_vars = self._generate_radius_variables() 

138 

139 css = f""" 

140/* KelpUI CSS Framework for FastBlocks */ 

141{color_vars} 

142{spacing_vars} 

143{typography_vars} 

144{radius_vars} 

145 

146/* Base Reset */ 

147*, *::before, *::after {{ 

148 box-sizing: border-box; 

149}} 

150 

151* {{ 

152 margin: 0; 

153 padding: 0; 

154}} 

155 

156html {{ 

157 scroll-behavior: smooth; 

158 -webkit-font-smoothing: antialiased; 

159 -moz-osx-font-smoothing: grayscale; 

160}} 

161 

162body {{ 

163 font-family: var(--kelp-font-sans); 

164 font-size: var(--kelp-text-base); 

165 line-height: 1.6; 

166 color: var(--kelp-gray-900); 

167 background-color: var(--kelp-gray-50); 

168 min-height: 100vh; 

169}} 

170 

171/* Layout System */ 

172.kelp-container {{ 

173 width: 100%; 

174 max-width: 1200px; 

175 margin: 0 auto; 

176 padding: 0 var(--kelp-space-4); 

177}} 

178 

179.kelp-container-sm {{ max-width: 640px; }} 

180.kelp-container-md {{ max-width: 768px; }} 

181.kelp-container-lg {{ max-width: 1024px; }} 

182.kelp-container-xl {{ max-width: 1280px; }} 

183.kelp-container-2xl {{ max-width: 1536px; }} 

184 

185/* Flexbox Grid */ 

186.kelp-flex {{ 

187 display: flex; 

188}} 

189 

190.kelp-flex-col {{ 

191 flex-direction: column; 

192}} 

193 

194.kelp-flex-wrap {{ 

195 flex-wrap: wrap; 

196}} 

197 

198.kelp-items-center {{ 

199 align-items: center; 

200}} 

201 

202.kelp-items-start {{ 

203 align-items: flex-start; 

204}} 

205 

206.kelp-items-end {{ 

207 align-items: flex-end; 

208}} 

209 

210.kelp-justify-center {{ 

211 justify-content: center; 

212}} 

213 

214.kelp-justify-between {{ 

215 justify-content: space-between; 

216}} 

217 

218.kelp-justify-around {{ 

219 justify-content: space-around; 

220}} 

221 

222.kelp-gap-1 {{ gap: var(--kelp-space-1); }} 

223.kelp-gap-2 {{ gap: var(--kelp-space-2); }} 

224.kelp-gap-3 {{ gap: var(--kelp-space-3); }} 

225.kelp-gap-4 {{ gap: var(--kelp-space-4); }} 

226.kelp-gap-6 {{ gap: var(--kelp-space-6); }} 

227.kelp-gap-8 {{ gap: var(--kelp-space-8); }} 

228 

229/* Grid System */ 

230.kelp-grid {{ 

231 display: grid; 

232}} 

233 

234.kelp-grid-cols-1 {{ grid-template-columns: repeat(1, minmax(0, 1fr)); }} 

235.kelp-grid-cols-2 {{ grid-template-columns: repeat(2, minmax(0, 1fr)); }} 

236.kelp-grid-cols-3 {{ grid-template-columns: repeat(3, minmax(0, 1fr)); }} 

237.kelp-grid-cols-4 {{ grid-template-columns: repeat(4, minmax(0, 1fr)); }} 

238.kelp-grid-cols-6 {{ grid-template-columns: repeat(6, minmax(0, 1fr)); }} 

239.kelp-grid-cols-12 {{ grid-template-columns: repeat(12, minmax(0, 1fr)); }} 

240 

241/* Component: Card */ 

242.kelp-card {{ 

243 background: white; 

244 border: 1px solid var(--kelp-gray-200); 

245 border-radius: var(--kelp-radius-lg); 

246 overflow: hidden; 

247 transition: all 0.2s ease; 

248}} 

249 

250.kelp-card:hover {{ 

251 box-shadow: var(--kelp-shadow-lg); 

252 transform: translateY(-2px); 

253}} 

254 

255.kelp-card-header {{ 

256 padding: var(--kelp-space-4) var(--kelp-space-6); 

257 border-bottom: 1px solid var(--kelp-gray-200); 

258 background: var(--kelp-gray-50); 

259}} 

260 

261.kelp-card-body {{ 

262 padding: var(--kelp-space-6); 

263}} 

264 

265.kelp-card-footer {{ 

266 padding: var(--kelp-space-4) var(--kelp-space-6); 

267 border-top: 1px solid var(--kelp-gray-200); 

268 background: var(--kelp-gray-50); 

269}} 

270 

271/* Component: Button */ 

272.kelp-btn {{ 

273 display: inline-flex; 

274 align-items: center; 

275 justify-content: center; 

276 gap: var(--kelp-space-2); 

277 padding: var(--kelp-space-3) var(--kelp-space-6); 

278 font-family: inherit; 

279 font-size: var(--kelp-text-sm); 

280 font-weight: 500; 

281 line-height: 1; 

282 border: 1px solid transparent; 

283 border-radius: var(--kelp-radius-md); 

284 cursor: pointer; 

285 transition: all 0.2s ease; 

286 text-decoration: none; 

287 white-space: nowrap; 

288}} 

289 

290.kelp-btn:focus {{ 

291 outline: 2px solid var(--kelp-primary-500); 

292 outline-offset: 2px; 

293}} 

294 

295.kelp-btn:disabled {{ 

296 opacity: 0.6; 

297 cursor: not-allowed; 

298}} 

299 

300.kelp-btn-primary {{ 

301 background: var(--kelp-primary-600); 

302 border-color: var(--kelp-primary-600); 

303 color: white; 

304}} 

305 

306.kelp-btn-primary:hover:not(:disabled) {{ 

307 background: var(--kelp-primary-700); 

308 border-color: var(--kelp-primary-700); 

309 transform: translateY(-1px); 

310 box-shadow: var(--kelp-shadow-md); 

311}} 

312 

313.kelp-btn-secondary {{ 

314 background: var(--kelp-secondary-600); 

315 border-color: var(--kelp-secondary-600); 

316 color: white; 

317}} 

318 

319.kelp-btn-secondary:hover:not(:disabled) {{ 

320 background: var(--kelp-secondary-700); 

321 border-color: var(--kelp-secondary-700); 

322 transform: translateY(-1px); 

323 box-shadow: var(--kelp-shadow-md); 

324}} 

325 

326.kelp-btn-outline {{ 

327 background: transparent; 

328 border-color: var(--kelp-gray-300); 

329 color: var(--kelp-gray-700); 

330}} 

331 

332.kelp-btn-outline:hover:not(:disabled) {{ 

333 background: var(--kelp-gray-50); 

334 border-color: var(--kelp-gray-400); 

335}} 

336 

337.kelp-btn-ghost {{ 

338 background: transparent; 

339 border-color: transparent; 

340 color: var(--kelp-gray-700); 

341}} 

342 

343.kelp-btn-ghost:hover:not(:disabled) {{ 

344 background: var(--kelp-gray-100); 

345}} 

346 

347/* Button Sizes */ 

348.kelp-btn-sm {{ 

349 padding: var(--kelp-space-2) var(--kelp-space-4); 

350 font-size: var(--kelp-text-xs); 

351}} 

352 

353.kelp-btn-lg {{ 

354 padding: var(--kelp-space-4) var(--kelp-space-8); 

355 font-size: var(--kelp-text-base); 

356}} 

357 

358/* Component: Form Controls */ 

359.kelp-form-group {{ 

360 margin-bottom: var(--kelp-space-4); 

361}} 

362 

363.kelp-label {{ 

364 display: block; 

365 margin-bottom: var(--kelp-space-2); 

366 font-size: var(--kelp-text-sm); 

367 font-weight: 500; 

368 color: var(--kelp-gray-700); 

369}} 

370 

371.kelp-input {{ 

372 width: 100%; 

373 padding: var(--kelp-space-3); 

374 font-family: inherit; 

375 font-size: var(--kelp-text-sm); 

376 border: 1px solid var(--kelp-gray-300); 

377 border-radius: var(--kelp-radius-md); 

378 background: white; 

379 color: var(--kelp-gray-900); 

380 transition: all 0.2s ease; 

381}} 

382 

383.kelp-input:focus {{ 

384 outline: none; 

385 border-color: var(--kelp-primary-500); 

386 box-shadow: 0 0 0 3px var(--kelp-primary-100); 

387}} 

388 

389.kelp-input:disabled {{ 

390 background: var(--kelp-gray-100); 

391 color: var(--kelp-gray-500); 

392 cursor: not-allowed; 

393}} 

394 

395.kelp-textarea {{ 

396 resize: vertical; 

397 min-height: 80px; 

398}} 

399 

400.kelp-select {{ 

401 background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e"); 

402 background-position: right 0.5rem center; 

403 background-repeat: no-repeat; 

404 background-size: 1.5em 1.5em; 

405 padding-right: 2.5rem; 

406}} 

407 

408/* Component: Alert */ 

409.kelp-alert {{ 

410 padding: var(--kelp-space-4); 

411 border-radius: var(--kelp-radius-md); 

412 border: 1px solid; 

413 margin-bottom: var(--kelp-space-4); 

414}} 

415 

416.kelp-alert-info {{ 

417 background: var(--kelp-primary-50); 

418 border-color: var(--kelp-primary-200); 

419 color: var(--kelp-primary-800); 

420}} 

421 

422.kelp-alert-success {{ 

423 background: var(--kelp-secondary-50); 

424 border-color: var(--kelp-secondary-200); 

425 color: var(--kelp-secondary-800); 

426}} 

427 

428.kelp-alert-warning {{ 

429 background: var(--kelp-accent-50); 

430 border-color: var(--kelp-accent-200); 

431 color: var(--kelp-accent-800); 

432}} 

433 

434.kelp-alert-error {{ 

435 background: #fef2f2; 

436 border-color: #fecaca; 

437 color: #991b1b; 

438}} 

439 

440/* Component: Badge */ 

441.kelp-badge {{ 

442 display: inline-flex; 

443 align-items: center; 

444 padding: var(--kelp-space-1) var(--kelp-space-2); 

445 font-size: var(--kelp-text-xs); 

446 font-weight: 500; 

447 border-radius: var(--kelp-radius-full); 

448 text-transform: uppercase; 

449 letter-spacing: 0.05em; 

450}} 

451 

452.kelp-badge-primary {{ 

453 background: var(--kelp-primary-100); 

454 color: var(--kelp-primary-800); 

455}} 

456 

457.kelp-badge-secondary {{ 

458 background: var(--kelp-secondary-100); 

459 color: var(--kelp-secondary-800); 

460}} 

461 

462.kelp-badge-gray {{ 

463 background: var(--kelp-gray-100); 

464 color: var(--kelp-gray-800); 

465}} 

466 

467/* Utility Classes */ 

468{self._generate_utility_classes()} 

469 

470/* Responsive Design */ 

471{self._generate_responsive_classes()} 

472 

473/* Animation System */ 

474{self._generate_animations()} 

475""" 

476 return css 

477 

478 def _generate_color_variables(self) -> str: 

479 """Generate CSS color variables based on HSL.""" 

480 if not self.settings: 

481 self.settings = KelpUISettings() 

482 

483 def hsl_colors(hue: int, prefix: str) -> str: 

484 """Generate HSL color scale.""" 

485 return f""" 

486 --kelp-{prefix}-50: hsl({hue}, 100%, 97%); 

487 --kelp-{prefix}-100: hsl({hue}, 100%, 94%); 

488 --kelp-{prefix}-200: hsl({hue}, 100%, 87%); 

489 --kelp-{prefix}-300: hsl({hue}, 100%, 80%); 

490 --kelp-{prefix}-400: hsl({hue}, 100%, 66%); 

491 --kelp-{prefix}-500: hsl({hue}, 100%, 50%); 

492 --kelp-{prefix}-600: hsl({hue}, 100%, 45%); 

493 --kelp-{prefix}-700: hsl({hue}, 100%, 35%); 

494 --kelp-{prefix}-800: hsl({hue}, 100%, 25%); 

495 --kelp-{prefix}-900: hsl({hue}, 100%, 15%);""" 

496 

497 return f""" 

498:root {{ 

499 /* Color System */ 

500 {hsl_colors(self.settings.primary_hue, "primary")} 

501 {hsl_colors(self.settings.secondary_hue, "secondary")} 

502 {hsl_colors(self.settings.accent_hue, "accent")} 

503 

504 /* Neutral Colors */ 

505 --kelp-gray-50: hsl({self.settings.neutral_hue}, 20%, 98%); 

506 --kelp-gray-100: hsl({self.settings.neutral_hue}, 20%, 95%); 

507 --kelp-gray-200: hsl({self.settings.neutral_hue}, 15%, 89%); 

508 --kelp-gray-300: hsl({self.settings.neutral_hue}, 10%, 78%); 

509 --kelp-gray-400: hsl({self.settings.neutral_hue}, 8%, 56%); 

510 --kelp-gray-500: hsl({self.settings.neutral_hue}, 6%, 45%); 

511 --kelp-gray-600: hsl({self.settings.neutral_hue}, 5%, 35%); 

512 --kelp-gray-700: hsl({self.settings.neutral_hue}, 5%, 25%); 

513 --kelp-gray-800: hsl({self.settings.neutral_hue}, 5%, 15%); 

514 --kelp-gray-900: hsl({self.settings.neutral_hue}, 5%, 9%); 

515 

516 /* Shadow System */ 

517 --kelp-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); 

518 --kelp-shadow-base: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 

519 --kelp-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 

520 --kelp-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); 

521 --kelp-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); 

522}}""" 

523 

524 def _generate_spacing_variables(self) -> str: 

525 """Generate spacing variables.""" 

526 if not self.settings: 

527 self.settings = KelpUISettings() 

528 

529 vars_css = "" 

530 for i, value in enumerate(self.settings.spacing_scale): 

531 vars_css += f" --kelp-space-{i}: {value}rem;\n" 

532 

533 return f""" 

534 /* Spacing System */ 

535{vars_css}""" 

536 

537 def _generate_typography_variables(self) -> str: 

538 """Generate typography variables.""" 

539 if not self.settings: 

540 self.settings = KelpUISettings() 

541 

542 font_vars = f""" 

543 /* Typography System */ 

544 --kelp-font-sans: {self.settings.font_family_sans}; 

545 --kelp-font-mono: {self.settings.font_family_mono}; 

546""" 

547 

548 for name, size in self.settings.font_scale.items(): 

549 font_vars += f" --kelp-text-{name}: {size};\n" 

550 

551 return font_vars 

552 

553 def _generate_radius_variables(self) -> str: 

554 """Generate border radius variables.""" 

555 if not self.settings: 

556 self.settings = KelpUISettings() 

557 

558 radius_vars = " /* Border Radius System */\n" 

559 for name, value in self.settings.radius_scale.items(): 

560 radius_vars += f" --kelp-radius-{name}: {value};\n" 

561 

562 return radius_vars 

563 

564 def _generate_utility_classes(self) -> str: 

565 """Generate utility classes.""" 

566 return """ 

567/* Text Utilities */ 

568.kelp-text-left { text-align: left; } 

569.kelp-text-center { text-align: center; } 

570.kelp-text-right { text-align: right; } 

571.kelp-text-justify { text-align: justify; } 

572 

573.kelp-font-sans { font-family: var(--kelp-font-sans); } 

574.kelp-font-mono { font-family: var(--kelp-font-mono); } 

575 

576.kelp-text-xs { font-size: var(--kelp-text-xs); } 

577.kelp-text-sm { font-size: var(--kelp-text-sm); } 

578.kelp-text-base { font-size: var(--kelp-text-base); } 

579.kelp-text-lg { font-size: var(--kelp-text-lg); } 

580.kelp-text-xl { font-size: var(--kelp-text-xl); } 

581.kelp-text-2xl { font-size: var(--kelp-text-2xl); } 

582.kelp-text-3xl { font-size: var(--kelp-text-3xl); } 

583 

584.kelp-font-light { font-weight: 300; } 

585.kelp-font-normal { font-weight: 400; } 

586.kelp-font-medium { font-weight: 500; } 

587.kelp-font-semibold { font-weight: 600; } 

588.kelp-font-bold { font-weight: 700; } 

589 

590/* Display Utilities */ 

591.kelp-block { display: block; } 

592.kelp-inline { display: inline; } 

593.kelp-inline-block { display: inline-block; } 

594.kelp-hidden { display: none; } 

595 

596/* Spacing Utilities */ 

597.kelp-m-0 { margin: var(--kelp-space-0); } 

598.kelp-m-1 { margin: var(--kelp-space-1); } 

599.kelp-m-2 { margin: var(--kelp-space-2); } 

600.kelp-m-3 { margin: var(--kelp-space-3); } 

601.kelp-m-4 { margin: var(--kelp-space-4); } 

602.kelp-m-6 { margin: var(--kelp-space-6); } 

603.kelp-m-8 { margin: var(--kelp-space-8); } 

604 

605.kelp-p-0 { padding: var(--kelp-space-0); } 

606.kelp-p-1 { padding: var(--kelp-space-1); } 

607.kelp-p-2 { padding: var(--kelp-space-2); } 

608.kelp-p-3 { padding: var(--kelp-space-3); } 

609.kelp-p-4 { padding: var(--kelp-space-4); } 

610.kelp-p-6 { padding: var(--kelp-space-6); } 

611.kelp-p-8 { padding: var(--kelp-space-8); } 

612 

613/* Color Utilities */ 

614.kelp-text-primary { color: var(--kelp-primary-600); } 

615.kelp-text-secondary { color: var(--kelp-secondary-600); } 

616.kelp-text-gray { color: var(--kelp-gray-600); } 

617.kelp-text-white { color: white; } 

618 

619.kelp-bg-primary { background-color: var(--kelp-primary-600); } 

620.kelp-bg-secondary { background-color: var(--kelp-secondary-600); } 

621.kelp-bg-gray { background-color: var(--kelp-gray-100); } 

622.kelp-bg-white { background-color: white; } 

623 

624/* Border Utilities */ 

625.kelp-border { border: 1px solid var(--kelp-gray-200); } 

626.kelp-border-0 { border: none; } 

627.kelp-rounded { border-radius: var(--kelp-radius-base); } 

628.kelp-rounded-md { border-radius: var(--kelp-radius-md); } 

629.kelp-rounded-lg { border-radius: var(--kelp-radius-lg); } 

630.kelp-rounded-full { border-radius: var(--kelp-radius-full); } 

631 

632/* Shadow Utilities */ 

633.kelp-shadow { box-shadow: var(--kelp-shadow-base); } 

634.kelp-shadow-md { box-shadow: var(--kelp-shadow-md); } 

635.kelp-shadow-lg { box-shadow: var(--kelp-shadow-lg); } 

636.kelp-shadow-none { box-shadow: none; }""" 

637 

638 def _generate_responsive_classes(self) -> str: 

639 """Generate responsive design classes.""" 

640 return """ 

641/* Responsive Design */ 

642@media (max-width: 640px) { 

643 .kelp-container { 

644 padding: 0 var(--kelp-space-2); 

645 } 

646 

647 .kelp-grid-cols-2 { 

648 grid-template-columns: repeat(1, minmax(0, 1fr)); 

649 } 

650 

651 .kelp-grid-cols-3 { 

652 grid-template-columns: repeat(1, minmax(0, 1fr)); 

653 } 

654 

655 .kelp-grid-cols-4 { 

656 grid-template-columns: repeat(2, minmax(0, 1fr)); 

657 } 

658} 

659 

660@media (max-width: 768px) { 

661 .kelp-md\\:hidden { 

662 display: none; 

663 } 

664 

665 .kelp-md\\:flex { 

666 display: flex; 

667 } 

668 

669 .kelp-md\\:grid-cols-1 { 

670 grid-template-columns: repeat(1, minmax(0, 1fr)); 

671 } 

672}""" 

673 

674 def _generate_animations(self) -> str: 

675 """Generate animation system.""" 

676 if not self.settings or not self.settings.enable_animations: 

677 return "" 

678 

679 return """ 

680/* Animation System */ 

681@keyframes kelp-fade-in { 

682 from { 

683 opacity: 0; 

684 transform: translateY(0.5rem); 

685 } 

686 to { 

687 opacity: 1; 

688 transform: translateY(0); 

689 } 

690} 

691 

692@keyframes kelp-slide-up { 

693 from { 

694 transform: translateY(1rem); 

695 opacity: 0; 

696 } 

697 to { 

698 transform: translateY(0); 

699 opacity: 1; 

700 } 

701} 

702 

703@keyframes kelp-scale-in { 

704 from { 

705 transform: scale(0.95); 

706 opacity: 0; 

707 } 

708 to { 

709 transform: scale(1); 

710 opacity: 1; 

711 } 

712} 

713 

714.kelp-animate-fade-in { 

715 animation: kelp-fade-in 0.3s ease-out; 

716} 

717 

718.kelp-animate-slide-up { 

719 animation: kelp-slide-up 0.4s ease-out; 

720} 

721 

722.kelp-animate-scale-in { 

723 animation: kelp-scale-in 0.2s ease-out; 

724} 

725 

726.kelp-transition { 

727 transition: all 0.2s ease; 

728} 

729 

730.kelp-transition-colors { 

731 transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; 

732}""" 

733 

734 def get_component_class(self, component: str) -> str: 

735 """Get KelpUI-specific classes.""" 

736 class_map = { 

737 # Layout 

738 "container": "kelp-container", 

739 "container-sm": "kelp-container-sm", 

740 "container-md": "kelp-container-md", 

741 "container-lg": "kelp-container-lg", 

742 "container-xl": "kelp-container-xl", 

743 "flex": "kelp-flex", 

744 "flex-col": "kelp-flex-col", 

745 "grid": "kelp-grid", 

746 # Components 

747 "card": "kelp-card", 

748 "card-header": "kelp-card-header", 

749 "card-body": "kelp-card-body", 

750 "card-footer": "kelp-card-footer", 

751 # Buttons 

752 "btn": "kelp-btn", 

753 "btn-primary": "kelp-btn kelp-btn-primary", 

754 "btn-secondary": "kelp-btn kelp-btn-secondary", 

755 "btn-outline": "kelp-btn kelp-btn-outline", 

756 "btn-ghost": "kelp-btn kelp-btn-ghost", 

757 "btn-sm": "kelp-btn kelp-btn-sm", 

758 "btn-lg": "kelp-btn kelp-btn-lg", 

759 # Forms 

760 "form-group": "kelp-form-group", 

761 "label": "kelp-label", 

762 "input": "kelp-input", 

763 "textarea": "kelp-input kelp-textarea", 

764 "select": "kelp-input kelp-select", 

765 # Alerts 

766 "alert": "kelp-alert", 

767 "alert-info": "kelp-alert kelp-alert-info", 

768 "alert-success": "kelp-alert kelp-alert-success", 

769 "alert-warning": "kelp-alert kelp-alert-warning", 

770 "alert-error": "kelp-alert kelp-alert-error", 

771 # Badges 

772 "badge": "kelp-badge", 

773 "badge-primary": "kelp-badge kelp-badge-primary", 

774 "badge-secondary": "kelp-badge kelp-badge-secondary", 

775 "badge-gray": "kelp-badge kelp-badge-gray", 

776 } 

777 

778 return class_map.get(component, f"kelp-{component}") 

779 

780 

781# Template function registration for FastBlocks 

782def _determine_component_tag(component_type: str, attributes: dict[str, Any]) -> str: 

783 """Determine HTML tag for KelpUI component type.""" 

784 if component_type in ( 

785 "btn", 

786 "btn-primary", 

787 "btn-secondary", 

788 "btn-outline", 

789 "btn-ghost", 

790 ): 

791 return "button" 

792 

793 if component_type in ("input", "textarea", "select"): 

794 if component_type == "input": 

795 attributes.setdefault("type", "text") 

796 return "input" 

797 return "textarea" if component_type == "textarea" else component_type 

798 

799 return "div" 

800 

801 

802def _build_kelp_component_html( 

803 tag: str, 

804 component_class: str, 

805 content: str, 

806 attributes: dict[str, Any], 

807) -> str: 

808 """Build KelpUI component HTML.""" 

809 attr_string = " ".join(f'{k}="{v}"' for k, v in attributes.items()) 

810 

811 if tag == "input": 

812 return f'<{tag} class="{component_class}" {attr_string}>' 

813 

814 return f'<{tag} class="{component_class}" {attr_string}>{content}</{tag}>' 

815 

816 

817def register_kelpui_functions(env: Any) -> None: 

818 """Register KelpUI functions for Jinja2 templates.""" 

819 

820 @env.global_("kelp_stylesheet_links") # type: ignore[misc] 

821 def kelp_stylesheet_links() -> str: 

822 """Global function for KelpUI stylesheet links.""" 

823 styles = depends.get("styles") 

824 if isinstance(styles, KelpUIAdapter): 

825 return "\n".join(styles.get_stylesheet_links()) 

826 return "" 

827 

828 @env.filter("kelp_class") # type: ignore[misc] 

829 def kelp_class_filter(component: str) -> str: 

830 """Filter for getting KelpUI component classes.""" 

831 styles = depends.get("styles") 

832 if isinstance(styles, KelpUIAdapter): 

833 return styles.get_component_class(component) 

834 return component 

835 

836 @env.global_("kelp_component") # type: ignore[misc] 

837 def kelp_component( 

838 component_type: str, content: str = "", **attributes: Any 

839 ) -> str: 

840 """Generate KelpUI component.""" 

841 styles = depends.get("styles") 

842 if not isinstance(styles, KelpUIAdapter): 

843 return f"<div>{content}</div>" 

844 

845 component_class = styles.get_component_class(component_type) 

846 

847 # Add custom classes 

848 if "class" in attributes: 

849 component_class += f" {attributes.pop('class')}" 

850 

851 # Determine tag and build HTML 

852 tag = _determine_component_tag(component_type, attributes) 

853 return _build_kelp_component_html(tag, component_class, content, attributes) 

854 

855 

856# ACB 0.19.0+ compatibility 

857__all__ = ["KelpUIAdapter", "KelpUISettings", "register_kelpui_functions"]