RadixUI-Swift

Radix-UI 是一个开源库,针对快速开发、易于维护和可访问性进行了优化。

Swift Version Last Release Last Stable Release Last Commit Date Contributors Stars Forks License

由于 Radix-UI 的开发者表示它不适用于移动开发(例如 SwiftUI),而且永远不会适用(参考),所以我决定自己动手,让它可用,因为我非常喜欢它。我这样做的灵感来自 Basics 网站以及 Radix-UI 简约而华丽的设计。

安装

  1. 在 Xcode 中,转到你的 Package Dependencies(软件包依赖项)
  2. 在 Searchbar(搜索栏)中输入 https://github.com/amirsaam/Radix-UI-Swift.git
  3. Dependency Rule(依赖规则)更改为 Up to Next Minor Version(高达下一个次版本)
  4. 将最低版本设置为你需要的版本
  5. 按下 Add Package(添加软件包)按钮

为什么选择 Minor(次要版本)?

由于 RadixUI for Swift 不会具有任何破坏性的 Semantic 次要版本,所以它的版本控制为 Epoch.Major.Minor * 100 + Patch。如果可能的话,它应该是 Epoch * 100 + Major,因此,为了获得与 Semantic Versioning 相同的正确行为,选择 Up to Next Minor Version 才是正确的方法。

使用示例

在你想要使用它的任何地方导入软件包,如下所示:

import RadixUI

颜色

你可以使用两种方式使用 Radix Colors(Radix 颜色)

Text("RadixUI-Swift")
  .foregroundColor(.crimson9) // Directly

@State private var color: RadixAutoColor = .crimson

Text("RadixUI-Swift")
  .foregroundColor(color.solid9) // From RadixAutoColor Enum

为了使用你自己的自定义调色板,请前往 Radix Pallete Generator 并创建你的调色板,然后将它们添加到你的 Asset Catalogue(资源目录)中,命名为 MyColor1, MyColor2, MyColor3, MyColor4, MyColor5, ... MyColor12,然后使用 RadixAutoColor 的 .custom 属性。

@State private var color: RadixAutoColor = .custom("myColor")
 // Reads all 12 levels of your custom pallete using the color's child variables.
// It will automatically capitalize the first letter for you.

Text("RadixUI-Swift")
  .foregroundColor(color.solid9)

重要提示:所有 RadixUI 修饰符和样式都使用 RadixAutoColor 枚举。

图标

Image 中使用图标,bundle(资源包)名称完全可自定义,有两种方式:

// Default SwiftUI's Image
Image("github-logo", bundle: .radixUI)
  .resizable()
  .aspectRatio(contentMode: .fit)
  .frame(width: anySize, height: anySize)
  .foregroundColor(.ruby9)

// Built-in Custom Extension
ResizableBundledImage(imageName: "vercel-logo", imageSize: 20, bundle: .radixUI)
  .foregroundColor(.ruby9)

对于在 Label 中使用,请使用内置的 extension(扩展),它使用 ResizableBundledImage

Label("Vercel", imageName: "vercel-logo", imageSize: 20, bundle: .radixUI)

徽章 (Badge)

你可以将任何 Text(文本)转换为 RadixUI 徽章。

Text("New")
    .font(.footnote)
    .radixBadge(variant: .outline, radius: .none, color: .grass)
// variant: outline, soft, solid, surface
// radius: none, large, full

按钮 (Button)

RadixUI 具有多种 ButtonStyles(按钮样式),如:radixGhost, radixOutline, radixSoft, radixSolid 和 radixSurface。注意 1:RadixUI 按钮在按下后可以具有加载状态,该状态会将按钮标签的图标更改为进度视图,直到 boolean 值再次变为 false。示例:

@State private var isLoading = false

// Surely you can go for < Button {} label: {} > approach too!
Button(action: functionName) {
    // Best for using Label with RadixUI icons
    Label(LocalizedStringKey, imageName: String, imageSize: CGFloat, bundle: Bundle)
    // or create one
    Label {
        Text(LocalizedStringKey)
    } icon: {
        Image(String)
        Image(systemName: String)
        Image(String, bundle: Bundle?)
    }
    // or to use Asset Catalog Image and SFSymbols
    Label(LocalizedStringKey, image: String)
    Label(LocalizedStringKey, systemImage: String)
}
.buttonStyle(
// for having the similarity to RadixUI buttons like scale and opacity and etc;
// does not change anything of the button but adds some effects.
    .radixCustom()
// or
    .radixGhost(
        layout: .leading,
        radius: .large,
        color: .grass
    ),
    isLoading: $isLoading
// or
    .radixOutline(
        layout: .leading,
        radius: .large,
        color: .grass
    ),
    isLoading: $isLoading
// or
    .radixSoft(
        layout: .leading,
        radius: .large,
        color: .grass
    ),
    isLoading: $isLoading
// or
    .radixSolid(
        layout: .leading,
        radius: .large,
        color: .grass
    ),
    isLoading: $isLoading
// or
    .radixSurface(
        layout: .leading,
        radius: .large,
        color: .grass
    ),
    isLoading: .constant(false) // for disabling the loading state change
)
// layout: icon, title, leading, trailing
// radius: none, large, full

注意 2:始终传递一个 Label() 作为按钮的 label(标签),不要只传递 Text 或 Image,如果你只想要其中一个,请使用 layout 变量的 .icon.title 情况。

标注 (Callout)

你可以将任何 Text(文本)转换为 RadixUI 标注。

let infoText = "You will need admin privileges to install and access this application."
let alertText = "Access denied. Please contact the network administrator to view this page."

Text(infoText)
    .radixInfoCallout(variant: .surface, color: .grass) // info callout, customizable color, `info-circled` icon

Text(alertText)
    .radixAlertCallout(variant: .surface) // alert callout, red color, `exclamation-triangle` icon

// variant: outline, soft, surface

单选组 (RadioGroup)

在底层,它只是一个循环的 Toggle(切换),但它只允许同时打开一个切换,需要传递一个选项数组,这些选项的模型符合某些协议。

// the radio option data options should conform to these protocols
struct Option: Identifiable, Equatable {
    let id = UUID()
    let title: LocalizedStringKey
}

@State private var selectedOption: Option? = nil
private let options = [
    Option(title: "Option 1"),
    Option(title: "Option 2"),
    Option(title: "Option 3")
]

RadixRadioGroup(
    options: options, // pass the array of data options
    selected: $selectedOption, // bound of selected option
    style: (variant: .surface, layout: .trailing, color: .grass)
) {
// what the options should show as label
    Text($0.title)
        .foregroundStyle(.grass12)
}
// variant: .soft, surface
// layout: .leading, .trailing

分段控件 (SegmentedControl)

为了自定义 SwiftUI 的 Segmented Picker(分段选择器)以匹配 Radix 样式,请在应用程序的 @main struct 中创建一个 init 来全局应用它,或者在任何你想要的视图中应用这样一个 init

struct PickerDemoView: View {

    init() {
        RadixSegmentedPicker(
            color: .grass,
            selectedFont: .systemFont(ofSize: 15, weight: .semibold),
            notSelectedFont: .systemFont(ofSize: 15, weight: .light)
        )
    }

    @State private var selected: RadixToggleType = .switch

    var body: some View {
        Picker(String(""), selection: $selected) {
            Text("Checkbox").tag(RadixToggleType.checkbox)
            Text("Radio").tag(RadixToggleType.radio)
            Text("Switch").tag(RadixToggleType.switch)
            Text("Toggle").tag(RadixToggleType.toggle)
            
        }
        .pickerStyle(.segmented)
        .padding()
        .frame(width: 400)
    }
}

重要提示:它目前在 macOS 中不起作用!

滑块 (Slider)

由于 SwiftUI 的 Slider(滑块)不接受像按钮那样的样式,所以 RadixUI for Swift 拥有自己的 RadixSlider View。

@State private var value = 5.0

RadixSlider(value: $value, step: 1, in: 0...10)
    .rxSliderStyle(
        .radixSoft(
            radius: .full,
            size: .medium,
            color: .grass
        )
// or
        .radixSurface(
            radius: .full,
            size: .medium,
            color: .grass
        )
    )
// radius: .none, .large, .full
// size: small, medium, large

文本框 (TextField)

RadixUI for Swift TextFieldStyle(文本框样式)就像 ButtonStyle 一样,具有加载状态,该状态会将 iconLabel 转换为 ProgressView,还可以通过传递一个函数来执行一个操作,该函数可以带或不带一个按钮,该按钮将在尾随侧显示 iconButton,并遵循文本框样式的样式。

@State private var input = ""
@State private var isLoading = false

TextField("Placeholder1", text: $input)
    .textFieldStyle(
        .radixSurface(
            radius: .large,
            color: .grass, // optional, default .blue
            iconLabel: Image("quote", bundle: .radixUI), // optional
            iconButton: Image("arrow-right", bundle: .radixUI), // optional
            action: functionName // optional action, does provide .onSubmit by default to textfield
        ),
        isLoading: $isLoading
// or
        .radixSoft(
            radius: .large,
            color: .grass, // optional, default .blue
            iconLabel: Image("quote", bundle: .radixUI),  // optional image
            iconButton: Image("arrow-right", bundle: .radixUI),  // optional image
            action: functionName  // optional action, does provide .onSubmit by default to textfield
        ),
        isLoading: $isLoading
    )
// radius: .none, .large, .full

Toast (提示框)

此软件包具有一个具有 RadixUI 风格的 toast(提示框)或应用内通知功能,遵循 Apple Design Guidelines(苹果设计指南)。它由一个 Binding<Bool> 变量触发,可以有一个作为 CTA(行动号召)的操作按钮,或者只是一个 dismiss(关闭)按钮,可以根据 toast 的显示位置向上或向下滑动。

@State private var presentInfoToast = false
@State private var presentActionToast = false

var body: some View {
    VStack { // << this is the highest container in the view, toast should applied to it
        HStack {

        }
    }
    .radixInfoToast( // this is a info toast, action is dismiss
        $presentInfoToast,
        variant: .surface,
        position: .bottom,
        color: .grass,
        duration: 3 // 0 == never, any other number auto dismisses the toast after the given number in seconds
    ) {
        Label(
            "This is a Radix Info Toast",
            imageName: "vercel-logo",
            imageSize: 20,
            bundle: .module
        ) // << pass a label to this part just like how explained in button part of this readme
    }
    .radixActionToast( // this is a action toast, you should define an action for it
        $presentActionToast,
        variant: .soft,
        position: .top,
        color: .grass,
        duration: 0
    ) {
        presentInfoToast = true // define the action you wnat here or just pass a function
    } buttonLabel: {
        Label(
            "Ignored Text",
            imageName: "avatar",
            imageSize: 20,
            bundle: .radixUI
        ) // pass a label as before for the button of the action, only shows the icon
    } toastLabel: {
        Label(
            "Button Presents Info Toast",
            imageName: "vercel-logo",
            imageSize: 20,
            bundle: .radixUI
        ) // pass a label as the toast's main content
    }
}
// variant: .soft, .surface
// position: .bottom, .top

切换 (Toggle)

RadixUI 有 4 种类型的 toggle(切换)样式,其中一种是 radixRadio,这部分被跳过了,因为它主要用于 RadixRadioGroup 中,并且将其作为 togglestyle 用于单个项目不是一个好方法。

Toggle(isOn: $toggleBinding) {
    ResizableBundledImage( // showed after toggle (checkbox( has been turned on (checked)
        imageName: "check",
        imageSize: 20,
        bundle: .radixUI
    )
}
.toggleStyle(
    .radixCheckbox(
        variant: .surface,
        color: .grass
    )
)
Toggle(isOn: $toggleBinding) {
    Text("Label Text")
        .foregroundStyle(radixColor.grass.text2)
}
.toggleStyle(
    .radixSwitch( // a switch style, just like swiftui's default with styling of radix ui
        variant: .surface,
        radius: .full,
        color: .grass
    )
)
Toggle(isOn: $toggleBinding) {
    ResizableBundledImage(
        imageName: "font-italic",
        imageSize: 20,
        bundle: .radixUI
    )
}
.toggleStyle(
    .radixToggle( // toggle as toggle, dimmer color when off, strong color when on
        color: .grass
    )
)
// variant: .soft, .surface
// radius: .none, .large, .full

阴影 (Shadow)

Radix 阴影以 ViewModifier 的形式提供,共有 6 个级别。

AnyView
.radixShadow1()
.radixShadow2()
.radixShadow3()
.radixShadow4()
.radixShadow5()
.radixShadow6()

待办事项 (To Do)

主要 (Main)

这些方面需要帮助,欢迎任何 PR。

依赖项 (Dependencies)

元信息 (Meta)

Amir Mohammadi – @amirsaam – amirsaam [at] me [dot] com

在 MIT 许可证下分发。有关更多信息,请参见 LICENSE

https://github.com/amirsaam/RadixUI-Swift

鸣谢 (Credits)