mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat(design): adopt ScarfDesign system across Mac UI
Add a typed design-system package (Packages/ScarfDesign) with rust-tone color tokens, type scale, spacing/radius tokens, ScarfPageHeader and component primitives (ScarfCard, ScarfBadge, ScarfTextField, ScarfSectionHeader, ScarfDivider, four button styles). Both Mac and iOS targets `import ScarfDesign`. Sidebar redesigned per design/static-site/ui-kit/Sidebar.jsx — glassy translucent background, 224 px width, app-icon header with server pill, custom tokenized rows with rust accent-tint when active, footer with live Hermes-running indicator (wired to ServerLiveStatusRegistry). 14 mockup-backed feature screens redesigned: Settings, Dashboard, Sessions, Memory, Chat (visual sweep), Activity, Cron, Insights, MCPServers, Health, Logs, Tools (full); Projects light-touch. Non-mockup features inherit rust through AccentColor.colorset repoint. Mac AppIcon.appiconset replaced with the rust set. AccentColor.colorset repointed to BrandRust hex (light + dark variants). Visual sweep: every multi-button page-header / action-bar cluster now wraps in .fixedSize(horizontal: true, vertical: false) so labels can't wrap letter-by-letter at narrow widths (regression seen on the MCP detail pane with 4 buttons). Follow-ups landed: - Sidebar Hermes-running probe wired to per-window ServerLiveStatusRegistry (no more placeholder green). - Sessions: today filter predicate (isDateInToday(startedAt)); pill count reflects real count. Starred stays a no-op pending an upstream pinned/starred field on HermesSession. - Dashboard: Recent activity column rendered alongside Recent sessions in a ViewThatFits 2-col grid. Populated from HermesDataService.fetchRecentToolCalls(limit:) flattened to ActivityEntry. ActivityEntry gains a public memberwise init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// swift-tools-version: 6.0
|
||||
// Scarf Design System — typed token bridge + SwiftUI component primitives
|
||||
// for the macOS and iOS apps.
|
||||
//
|
||||
// Ships color tokens (rust/amber brand, surfaces, foregrounds, semantic, tool
|
||||
// kinds) as an Xcode asset catalog and a thin Swift API
|
||||
// (`ScarfColor`, `ScarfFont`, `ScarfSpace`, `ScarfRadius`, `ScarfShadow`,
|
||||
// `ScarfPrimaryButton`, `ScarfCard`, `ScarfBadge`, `ScarfTextField`,
|
||||
// `ScarfSectionHeader`, `ScarfDivider`).
|
||||
//
|
||||
// Both app targets (`scarf`, `scarf mobile`) link this package and `import
|
||||
// ScarfDesign`. The asset catalog is bundled as a `.process` resource so the
|
||||
// `Color(name, bundle: .module)` lookups in `ScarfTheme.swift` resolve at
|
||||
// runtime regardless of which app is hosting the bundle.
|
||||
//
|
||||
// Platform minimums match the rest of the workspace: macOS 14 + iOS 18.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ScarfDesign",
|
||||
defaultLocalization: "en",
|
||||
platforms: [
|
||||
.macOS(.v14),
|
||||
.iOS(.v18),
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "ScarfDesign",
|
||||
targets: ["ScarfDesign"]
|
||||
),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ScarfDesign",
|
||||
path: "Sources/ScarfDesign",
|
||||
resources: [
|
||||
.process("ScarfBrand.xcassets"),
|
||||
],
|
||||
swiftSettings: [
|
||||
// Match ScarfCore / ScarfIOS — Swift 5 language mode pending
|
||||
// the workspace-wide bump to strict Swift 6 concurrency.
|
||||
.swiftLanguageMode(.v5),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.376",
|
||||
"green": "0.576",
|
||||
"red": "0.910"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.475",
|
||||
"green": "0.659",
|
||||
"red": "0.941"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.165",
|
||||
"green": "0.353",
|
||||
"red": "0.761"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.376",
|
||||
"green": "0.576",
|
||||
"red": "0.910"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.078",
|
||||
"green": "0.180",
|
||||
"red": "0.478"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.267",
|
||||
"green": "0.471",
|
||||
"red": "0.847"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.078",
|
||||
"green": "0.180",
|
||||
"red": "0.478"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.118",
|
||||
"green": "0.282",
|
||||
"red": "0.651"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.118",
|
||||
"green": "0.282",
|
||||
"red": "0.651"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.475",
|
||||
"green": "0.659",
|
||||
"red": "0.941"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
},
|
||||
"properties": {
|
||||
"provides-namespace": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
},
|
||||
"properties": {
|
||||
"provides-namespace": true
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.482",
|
||||
"green": "0.522",
|
||||
"red": "0.549"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.400",
|
||||
"green": "0.435",
|
||||
"red": "0.459"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.357",
|
||||
"green": "0.392",
|
||||
"red": "0.416"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.573",
|
||||
"green": "0.612",
|
||||
"red": "0.639"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.078",
|
||||
"green": "0.094",
|
||||
"red": "0.102"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.878",
|
||||
"green": "0.910",
|
||||
"red": "0.929"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "1.000",
|
||||
"green": "1.000",
|
||||
"red": "1.000"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "1.000",
|
||||
"green": "1.000",
|
||||
"red": "1.000"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
},
|
||||
"properties": {
|
||||
"provides-namespace": true
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.310",
|
||||
"green": "0.325",
|
||||
"red": "0.851"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.392",
|
||||
"green": "0.408",
|
||||
"red": "0.890"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.859",
|
||||
"green": "0.596",
|
||||
"red": "0.204"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.890",
|
||||
"green": "0.686",
|
||||
"red": "0.357"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.463",
|
||||
"green": "0.659",
|
||||
"red": "0.165"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.537",
|
||||
"green": "0.745",
|
||||
"red": "0.239"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.306",
|
||||
"green": "0.678",
|
||||
"red": "0.941"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.443",
|
||||
"green": "0.745",
|
||||
"red": "0.957"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.965",
|
||||
"green": "0.976",
|
||||
"red": "0.984"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.059",
|
||||
"green": "0.075",
|
||||
"red": "0.082"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "1.000",
|
||||
"green": "1.000",
|
||||
"red": "1.000"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.094",
|
||||
"green": "0.110",
|
||||
"red": "0.122"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.925",
|
||||
"green": "0.945",
|
||||
"red": "0.957"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.106",
|
||||
"green": "0.125",
|
||||
"red": "0.137"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.145",
|
||||
"green": "0.165",
|
||||
"red": "0.176"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.922",
|
||||
"green": "0.973",
|
||||
"red": "1.000"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.145",
|
||||
"green": "0.165",
|
||||
"red": "0.176"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.922",
|
||||
"green": "0.973",
|
||||
"red": "1.000"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
},
|
||||
"properties": {
|
||||
"provides-namespace": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
},
|
||||
"properties": {
|
||||
"provides-namespace": true
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.463",
|
||||
"green": "0.659",
|
||||
"red": "0.165"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.537",
|
||||
"green": "0.745",
|
||||
"red": "0.239"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.859",
|
||||
"green": "0.596",
|
||||
"red": "0.204"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.890",
|
||||
"green": "0.686",
|
||||
"red": "0.357"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.851",
|
||||
"green": "0.424",
|
||||
"red": "0.357"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.890",
|
||||
"green": "0.549",
|
||||
"red": "0.494"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.306",
|
||||
"green": "0.678",
|
||||
"red": "0.941"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.443",
|
||||
"green": "0.745",
|
||||
"red": "0.957"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors": [
|
||||
{
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.788",
|
||||
"green": "0.357",
|
||||
"red": "0.557"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"appearances": [
|
||||
{
|
||||
"appearance": "luminosity",
|
||||
"value": "dark"
|
||||
}
|
||||
],
|
||||
"color": {
|
||||
"color-space": "srgb",
|
||||
"components": {
|
||||
"alpha": "1.000",
|
||||
"blue": "0.831",
|
||||
"green": "0.494",
|
||||
"red": "0.655"
|
||||
}
|
||||
},
|
||||
"idiom": "universal"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"author": "xcode",
|
||||
"version": 1
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// ScarfComponents.swift
|
||||
// Scarf Design System — opinionated SwiftUI component primitives.
|
||||
//
|
||||
// These mirror the buttons, cards, badges, and inputs used in the Scarf UI kit.
|
||||
// Keep them small. Reach for them instead of inlining the same `.padding()
|
||||
// .background() .clipShape()` chain across screens.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Buttons
|
||||
|
||||
public struct ScarfPrimaryButton: ButtonStyle {
|
||||
public init() {}
|
||||
public func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scarfStyle(.bodyEmph)
|
||||
.foregroundStyle(ScarfColor.onAccent)
|
||||
.padding(.horizontal, ScarfSpace.s4)
|
||||
.padding(.vertical, ScarfSpace.s2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.fill(configuration.isPressed ? ScarfColor.accentActive : ScarfColor.accent)
|
||||
)
|
||||
.scarfShadow(.sm)
|
||||
.opacity(configuration.isPressed ? 0.95 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ScarfSecondaryButton: ButtonStyle {
|
||||
public init() {}
|
||||
public func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scarfStyle(.bodyEmph)
|
||||
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
.padding(.horizontal, ScarfSpace.s4)
|
||||
.padding(.vertical, ScarfSpace.s2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.fill(configuration.isPressed
|
||||
? ScarfColor.borderStrong
|
||||
: ScarfColor.backgroundSecondary)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.strokeBorder(ScarfColor.borderStrong, lineWidth: 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ScarfGhostButton: ButtonStyle {
|
||||
public init() {}
|
||||
public func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scarfStyle(.bodyEmph)
|
||||
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
.padding(.horizontal, ScarfSpace.s3)
|
||||
.padding(.vertical, ScarfSpace.s2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.fill(configuration.isPressed
|
||||
? ScarfColor.accentTint
|
||||
: Color.clear)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ScarfDestructiveButton: ButtonStyle {
|
||||
public init() {}
|
||||
public func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.scarfStyle(.bodyEmph)
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, ScarfSpace.s4)
|
||||
.padding(.vertical, ScarfSpace.s2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.fill(ScarfColor.danger.opacity(configuration.isPressed ? 0.85 : 1.0))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Card
|
||||
|
||||
public struct ScarfCard<Content: View>: View {
|
||||
let padding: CGFloat
|
||||
let content: () -> Content
|
||||
|
||||
public init(padding: CGFloat = ScarfSpace.s4, @ViewBuilder content: @escaping () -> Content) {
|
||||
self.padding = padding
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
content()
|
||||
.padding(padding)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.xl, style: .continuous)
|
||||
.fill(ScarfColor.backgroundSecondary)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.xl, style: .continuous)
|
||||
.strokeBorder(ScarfColor.border, lineWidth: 1)
|
||||
)
|
||||
.scarfShadow(.sm)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Badge / Pill
|
||||
|
||||
public enum ScarfBadgeKind {
|
||||
case neutral, brand, success, danger, warning, info
|
||||
|
||||
var fill: Color {
|
||||
switch self {
|
||||
case .neutral: return ScarfColor.backgroundTertiary
|
||||
case .brand: return ScarfColor.accentTint
|
||||
case .success: return ScarfColor.success.opacity(0.16)
|
||||
case .danger: return ScarfColor.danger.opacity(0.16)
|
||||
case .warning: return ScarfColor.warning.opacity(0.18)
|
||||
case .info: return ScarfColor.info.opacity(0.16)
|
||||
}
|
||||
}
|
||||
var fg: Color {
|
||||
switch self {
|
||||
case .neutral: return ScarfColor.foregroundMuted
|
||||
case .brand: return ScarfColor.accent
|
||||
case .success: return ScarfColor.success
|
||||
case .danger: return ScarfColor.danger
|
||||
case .warning: return ScarfColor.warning
|
||||
case .info: return ScarfColor.info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ScarfBadge: View {
|
||||
let text: String
|
||||
let kind: ScarfBadgeKind
|
||||
|
||||
public init(_ text: String, kind: ScarfBadgeKind = .neutral) {
|
||||
self.text = text
|
||||
self.kind = kind
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Text(text)
|
||||
.scarfStyle(.captionStrong)
|
||||
.foregroundStyle(kind.fg)
|
||||
.padding(.horizontal, ScarfSpace.s2)
|
||||
.padding(.vertical, 3)
|
||||
.background(
|
||||
Capsule().fill(kind.fill)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Inputs
|
||||
|
||||
public struct ScarfTextField: View {
|
||||
let placeholder: String
|
||||
@Binding var text: String
|
||||
|
||||
public init(_ placeholder: String, text: Binding<String>) {
|
||||
self.placeholder = placeholder
|
||||
self._text = text
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
TextField(placeholder, text: $text)
|
||||
.textFieldStyle(.plain)
|
||||
.scarfStyle(.body)
|
||||
.padding(.horizontal, ScarfSpace.s3)
|
||||
.padding(.vertical, ScarfSpace.s2)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.fill(ScarfColor.backgroundSecondary)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.md, style: .continuous)
|
||||
.strokeBorder(ScarfColor.borderStrong, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Section header
|
||||
|
||||
public struct ScarfSectionHeader: View {
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
|
||||
public init(_ title: String, subtitle: String? = nil) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.scarfStyle(.captionUppercase)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.scarfStyle(.footnote)
|
||||
.foregroundStyle(ScarfColor.foregroundFaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Divider
|
||||
|
||||
public struct ScarfDivider: View {
|
||||
public init() {}
|
||||
public var body: some View {
|
||||
Rectangle()
|
||||
.fill(ScarfColor.border)
|
||||
.frame(height: 1)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Page header
|
||||
|
||||
/// Standard page-level title/subtitle/actions header used at the top of
|
||||
/// every feature route. Mirrors the `ContentHeader` component in the
|
||||
/// design system's static-site / ui-kit. Drops a hairline divider at the
|
||||
/// bottom so feature content can flush against it.
|
||||
public struct ScarfPageHeader<Trailing: View>: View {
|
||||
let title: String
|
||||
let subtitle: String?
|
||||
let trailing: Trailing
|
||||
|
||||
public init(_ title: String,
|
||||
subtitle: String? = nil,
|
||||
@ViewBuilder trailing: () -> Trailing = { EmptyView() }) {
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.trailing = trailing()
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(alignment: .top, spacing: ScarfSpace.s3) {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(title)
|
||||
.scarfStyle(.title2)
|
||||
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
if let subtitle {
|
||||
Text(subtitle)
|
||||
.scarfStyle(.footnote)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
trailing
|
||||
}
|
||||
.padding(.horizontal, ScarfSpace.s6)
|
||||
.padding(.top, ScarfSpace.s5)
|
||||
.padding(.bottom, ScarfSpace.s4)
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.fill(ScarfColor.border)
|
||||
.frame(height: 1),
|
||||
alignment: .bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// ScarfPreview.swift
|
||||
// Scarf Design System — quick component preview.
|
||||
//
|
||||
// Open this file in Xcode and the canvas (⌥⌘P) shows every component at once,
|
||||
// in light and dark. Use it to sanity-check the bundle works after install.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public struct ScarfPreviewGallery: View {
|
||||
@State private var query = ""
|
||||
@State private var draft = "Hello, Scarf"
|
||||
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: ScarfSpace.s8) {
|
||||
|
||||
// ── Header ──────────────────────────────────────────────
|
||||
VStack(alignment: .leading, spacing: ScarfSpace.s2) {
|
||||
Text("Scarf Design System")
|
||||
.scarfStyle(.largeTitle)
|
||||
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
Text("Component preview — light / dark resolves from the asset catalog.")
|
||||
.scarfStyle(.subhead)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
}
|
||||
|
||||
// ── Buttons ─────────────────────────────────────────────
|
||||
section("Buttons") {
|
||||
HStack(spacing: ScarfSpace.s3) {
|
||||
Button("Primary") {}.buttonStyle(ScarfPrimaryButton())
|
||||
Button("Secondary") {}.buttonStyle(ScarfSecondaryButton())
|
||||
Button("Ghost") {}.buttonStyle(ScarfGhostButton())
|
||||
Button("Delete") {}.buttonStyle(ScarfDestructiveButton())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Badges ──────────────────────────────────────────────
|
||||
section("Badges") {
|
||||
HStack(spacing: ScarfSpace.s2) {
|
||||
ScarfBadge("Neutral")
|
||||
ScarfBadge("Brand", kind: .brand)
|
||||
ScarfBadge("Success", kind: .success)
|
||||
ScarfBadge("Warning", kind: .warning)
|
||||
ScarfBadge("Danger", kind: .danger)
|
||||
ScarfBadge("Info", kind: .info)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Inputs ──────────────────────────────────────────────
|
||||
section("Inputs") {
|
||||
VStack(alignment: .leading, spacing: ScarfSpace.s3) {
|
||||
ScarfTextField("Search", text: $query)
|
||||
ScarfTextField("Compose a message", text: $draft)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Card ────────────────────────────────────────────────
|
||||
section("Card") {
|
||||
ScarfCard {
|
||||
VStack(alignment: .leading, spacing: ScarfSpace.s2) {
|
||||
ScarfSectionHeader("Connection", subtitle: "anthropic.com")
|
||||
ScarfDivider()
|
||||
HStack {
|
||||
Text("Status")
|
||||
.scarfStyle(.body)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
Spacer()
|
||||
ScarfBadge("Connected", kind: .success)
|
||||
}
|
||||
HStack {
|
||||
Text("Last run")
|
||||
.scarfStyle(.body)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
Spacer()
|
||||
Text("2 min ago")
|
||||
.scarfStyle(.bodyEmph)
|
||||
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tool kind swatches (chat) ───────────────────────────
|
||||
section("Tool kinds") {
|
||||
HStack(spacing: ScarfSpace.s3) {
|
||||
toolSwatch("Bash", ScarfColor.Tool.bash)
|
||||
toolSwatch("Edit", ScarfColor.Tool.edit)
|
||||
toolSwatch("Search", ScarfColor.Tool.search)
|
||||
toolSwatch("Web", ScarfColor.Tool.web)
|
||||
toolSwatch("Think", ScarfColor.Tool.think)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Brand gradient ──────────────────────────────────────
|
||||
section("Brand gradient") {
|
||||
RoundedRectangle(cornerRadius: ScarfRadius.xl, style: .continuous)
|
||||
.fill(ScarfGradient.brand)
|
||||
.frame(height: 80)
|
||||
.overlay(
|
||||
Text("amber → rust → deep")
|
||||
.scarfStyle(.subhead)
|
||||
.foregroundStyle(.white)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(ScarfSpace.s8)
|
||||
}
|
||||
.background(ScarfColor.backgroundPrimary.ignoresSafeArea())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func section<Content: View>(_ title: String,
|
||||
@ViewBuilder content: () -> Content) -> some View {
|
||||
VStack(alignment: .leading, spacing: ScarfSpace.s3) {
|
||||
Text(title)
|
||||
.scarfStyle(.captionUppercase)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
private func toolSwatch(_ name: String, _ color: Color) -> some View {
|
||||
VStack(spacing: ScarfSpace.s1) {
|
||||
Circle().fill(color).frame(width: 24, height: 24)
|
||||
Text(name)
|
||||
.scarfStyle(.caption)
|
||||
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Light") {
|
||||
ScarfPreviewGallery()
|
||||
.frame(width: 720, height: 900)
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
|
||||
#Preview("Dark") {
|
||||
ScarfPreviewGallery()
|
||||
.frame(width: 720, height: 900)
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// ScarfTheme.swift
|
||||
// Scarf Design System — Swift token bridge
|
||||
//
|
||||
// Mirrors colors_and_type.css. All colors resolve from ScarfBrand.xcassets,
|
||||
// so light/dark variants come from the asset catalog automatically.
|
||||
//
|
||||
// Usage:
|
||||
// Text("Hello").foregroundStyle(ScarfColor.foregroundPrimary)
|
||||
// RoundedRectangle(cornerRadius: ScarfRadius.lg)
|
||||
// .fill(ScarfColor.backgroundSecondary)
|
||||
// .overlay(RoundedRectangle(cornerRadius: ScarfRadius.lg)
|
||||
// .strokeBorder(ScarfColor.border, lineWidth: 1))
|
||||
//
|
||||
// Drop-in: add this file + ScarfBrand.xcassets to your target. Nothing else.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Colors
|
||||
|
||||
/// All Scarf brand colors. Resolves from ScarfBrand.xcassets (light + dark).
|
||||
public enum ScarfColor {
|
||||
fileprivate static func asset(_ name: String) -> Color {
|
||||
Color(name, bundle: .module)
|
||||
}
|
||||
|
||||
// Brand
|
||||
public static let brandRust = asset("Brand/BrandRust")
|
||||
public static let brandRustHover = asset("Brand/BrandRustHover")
|
||||
public static let brandRustActive = asset("Brand/BrandRustActive")
|
||||
public static let brandAmber = asset("Brand/BrandAmber")
|
||||
public static let brandRustDeep = asset("Brand/BrandRustDeep")
|
||||
|
||||
/// Semantic alias: the "primary" accent. Use this in component code,
|
||||
/// not `brandRust` directly — it lets you re-skin without a refactor.
|
||||
public static var accent: Color { brandRust }
|
||||
public static var accentHover: Color { brandRustHover }
|
||||
public static var accentActive: Color { brandRustActive }
|
||||
|
||||
/// Tinted accent for hover halos, selection backgrounds.
|
||||
public static var accentTint: Color { brandRust.opacity(0.10) }
|
||||
public static var accentTintStrong: Color { brandRust.opacity(0.18) }
|
||||
|
||||
// Surfaces
|
||||
public static let backgroundPrimary = asset("Surface/BackgroundPrimary")
|
||||
public static let backgroundSecondary = asset("Surface/BackgroundSecondary")
|
||||
public static let backgroundTertiary = asset("Surface/BackgroundTertiary")
|
||||
|
||||
/// Use at low alpha (0.04–0.10) for subtle fills/dividers.
|
||||
public static var border: Color { asset("Surface/Border").opacity(0.08) }
|
||||
public static var borderStrong: Color { asset("Surface/BorderStrong").opacity(0.14) }
|
||||
|
||||
// Foreground
|
||||
public static let foregroundPrimary = asset("Foreground/ForegroundPrimary")
|
||||
public static let foregroundMuted = asset("Foreground/ForegroundMuted")
|
||||
public static let foregroundFaint = asset("Foreground/ForegroundFaint")
|
||||
public static let onAccent = asset("Foreground/OnAccent")
|
||||
|
||||
// Semantic
|
||||
public static let success = asset("Semantic/SemanticSuccess")
|
||||
public static let danger = asset("Semantic/SemanticDanger")
|
||||
public static let warning = asset("Semantic/SemanticWarning")
|
||||
public static let info = asset("Semantic/SemanticInfo")
|
||||
|
||||
// Tool kinds (chat message decorations)
|
||||
public enum Tool {
|
||||
public static let bash = ScarfColor.asset("Tool/ToolBash")
|
||||
public static let edit = ScarfColor.asset("Tool/ToolEdit")
|
||||
public static let search = ScarfColor.asset("Tool/ToolSearch")
|
||||
public static let web = ScarfColor.asset("Tool/ToolWeb")
|
||||
public static let think = ScarfColor.asset("Tool/ToolThink")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Gradients
|
||||
|
||||
public enum ScarfGradient {
|
||||
/// Tri-stop amber → rust → deep rust. Used on app icon, hero buttons, brand splashes.
|
||||
public static let brand = LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.910, green: 0.576, blue: 0.376), // #E89360
|
||||
Color(red: 0.761, green: 0.353, blue: 0.165), // #C25A2A
|
||||
Color(red: 0.478, green: 0.180, blue: 0.078) // #7A2E14
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
|
||||
/// Soft amber wash for empty states, onboarding moments.
|
||||
public static let brandSoft = LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.965, green: 0.878, blue: 0.796), // #F6E0CB
|
||||
Color(red: 0.937, green: 0.773, blue: 0.620) // #EFC59E
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Radii / spacing / shadow
|
||||
|
||||
public enum ScarfRadius {
|
||||
public static let sm: CGFloat = 4
|
||||
public static let md: CGFloat = 6
|
||||
public static let lg: CGFloat = 8
|
||||
public static let xl: CGFloat = 12
|
||||
public static let xxl: CGFloat = 14
|
||||
public static let pill: CGFloat = 999
|
||||
}
|
||||
|
||||
public enum ScarfSpace {
|
||||
public static let s1: CGFloat = 4
|
||||
public static let s2: CGFloat = 8
|
||||
public static let s3: CGFloat = 12
|
||||
public static let s4: CGFloat = 16
|
||||
public static let s5: CGFloat = 20
|
||||
public static let s6: CGFloat = 24
|
||||
public static let s8: CGFloat = 32
|
||||
public static let s10: CGFloat = 40
|
||||
}
|
||||
|
||||
public struct ScarfShadow {
|
||||
public let color: Color
|
||||
public let radius: CGFloat
|
||||
public let x: CGFloat
|
||||
public let y: CGFloat
|
||||
|
||||
public static let sm = ScarfShadow(color: .black.opacity(0.05), radius: 2, x: 0, y: 1)
|
||||
public static let md = ScarfShadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 4)
|
||||
public static let lg = ScarfShadow(color: .black.opacity(0.10), radius: 24, x: 0, y: 8)
|
||||
public static let xl = ScarfShadow(color: .black.opacity(0.14), radius: 40, x: 0, y: 16)
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func scarfShadow(_ s: ScarfShadow) -> some View {
|
||||
self.shadow(color: s.color, radius: s.radius, x: s.x, y: s.y)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Motion
|
||||
|
||||
public enum ScarfDuration {
|
||||
public static let fast: Double = 0.12
|
||||
public static let base: Double = 0.20
|
||||
public static let slow: Double = 0.30
|
||||
}
|
||||
|
||||
public enum ScarfAnimation {
|
||||
/// "Smooth" spring matching the cubic-bezier(0.32, 0.72, 0, 1) easing in CSS.
|
||||
public static let smooth = Animation.spring(response: 0.35, dampingFraction: 0.85)
|
||||
public static let fast = Animation.easeOut(duration: ScarfDuration.fast)
|
||||
public static let base = Animation.easeOut(duration: ScarfDuration.base)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// ScarfTypography.swift
|
||||
// Scarf Design System — Apple HIG-aligned type scale
|
||||
//
|
||||
// Uses SF Pro (system) for UI text and SF Mono for code/transcripts.
|
||||
// Sizes mirror the CSS tokens (--text-caption ... --text-largeTitle).
|
||||
//
|
||||
// Usage:
|
||||
// Text("Settings").font(ScarfFont.title2)
|
||||
// Text(message).font(ScarfFont.body)
|
||||
// Text("v1.2.0").font(ScarfFont.caption).foregroundStyle(ScarfColor.foregroundMuted)
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public enum ScarfFont {
|
||||
// Display / titles — use rounded SF Pro Display (`.default` design + tight tracking).
|
||||
public static let largeTitle = Font.system(size: 34, weight: .semibold, design: .default)
|
||||
public static let title1 = Font.system(size: 28, weight: .semibold, design: .default)
|
||||
public static let title2 = Font.system(size: 22, weight: .semibold, design: .default)
|
||||
public static let title3 = Font.system(size: 20, weight: .semibold, design: .default)
|
||||
|
||||
// Body & labels
|
||||
public static let headline = Font.system(size: 17, weight: .semibold)
|
||||
public static let subhead = Font.system(size: 16, weight: .medium)
|
||||
public static let callout = Font.system(size: 15, weight: .regular)
|
||||
public static let body = Font.system(size: 14, weight: .regular)
|
||||
public static let bodyEmph = Font.system(size: 14, weight: .medium)
|
||||
public static let footnote = Font.system(size: 13, weight: .regular)
|
||||
public static let caption = Font.system(size: 12, weight: .regular)
|
||||
public static let captionStrong = Font.system(size: 12, weight: .semibold)
|
||||
public static let caption2 = Font.system(size: 10, weight: .medium)
|
||||
|
||||
// Code / mono — for transcripts, command output, file paths.
|
||||
public static let mono = Font.system(size: 13, weight: .regular, design: .monospaced)
|
||||
public static let monoSmall = Font.system(size: 12, weight: .regular, design: .monospaced)
|
||||
}
|
||||
|
||||
/// Convenience text styles. Apply with `.scarfStyle(.headline)`.
|
||||
public enum ScarfTextStyle {
|
||||
case largeTitle, title1, title2, title3
|
||||
case headline, subhead, body, bodyEmph, callout, footnote
|
||||
case caption, captionStrong, captionUppercase
|
||||
case mono, code
|
||||
|
||||
var font: Font {
|
||||
switch self {
|
||||
case .largeTitle: return ScarfFont.largeTitle
|
||||
case .title1: return ScarfFont.title1
|
||||
case .title2: return ScarfFont.title2
|
||||
case .title3: return ScarfFont.title3
|
||||
case .headline: return ScarfFont.headline
|
||||
case .subhead: return ScarfFont.subhead
|
||||
case .body: return ScarfFont.body
|
||||
case .bodyEmph: return ScarfFont.bodyEmph
|
||||
case .callout: return ScarfFont.callout
|
||||
case .footnote: return ScarfFont.footnote
|
||||
case .caption,
|
||||
.captionUppercase: return ScarfFont.caption
|
||||
case .captionStrong: return ScarfFont.captionStrong
|
||||
case .mono, .code: return ScarfFont.mono
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
/// Apply a Scarf type style. Handles font + tracking + (for `captionUppercase`) text-case.
|
||||
@ViewBuilder
|
||||
func scarfStyle(_ style: ScarfTextStyle) -> some View {
|
||||
switch style {
|
||||
case .largeTitle:
|
||||
self.font(style.font).tracking(-0.7)
|
||||
case .title1:
|
||||
self.font(style.font).tracking(-0.5)
|
||||
case .title2, .title3:
|
||||
self.font(style.font).tracking(-0.3)
|
||||
case .captionUppercase:
|
||||
self.font(ScarfFont.captionStrong)
|
||||
.textCase(.uppercase)
|
||||
.tracking(0.5)
|
||||
default:
|
||||
self.font(style.font)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user