Skip to main content

dioxus_ui_system/molecules/
qr_code.rs

1//! QR Code molecule component
2//!
3//! A QR code generator display component.
4
5use crate::styles::Style;
6use crate::theme::{use_style, use_theme};
7use dioxus::prelude::*;
8
9/// QR Code error correction level
10#[derive(Default, Clone, PartialEq, Debug)]
11pub enum QrCodeLevel {
12    /// Low (~7% correction)
13    Low,
14    /// Medium (~15% correction)
15    #[default]
16    Medium,
17    /// Quartile (~25% correction)
18    Quartile,
19    /// High (~30% correction)
20    High,
21}
22
23impl QrCodeLevel {
24    fn as_str(&self) -> &'static str {
25        match self {
26            QrCodeLevel::Low => "L",
27            QrCodeLevel::Medium => "M",
28            QrCodeLevel::Quartile => "Q",
29            QrCodeLevel::High => "H",
30        }
31    }
32}
33
34/// QR Code properties
35#[derive(Props, Clone, PartialEq)]
36pub struct QrCodeProps {
37    /// Value to encode
38    pub value: String,
39    /// Size in pixels
40    #[props(default = 200)]
41    pub size: u32,
42    /// Error correction level
43    #[props(default)]
44    pub level: QrCodeLevel,
45    /// Foreground color (default black)
46    #[props(default)]
47    pub fg_color: Option<String>,
48    /// Background color (default white)
49    #[props(default)]
50    pub bg_color: Option<String>,
51    /// Include margin/quiet zone
52    #[props(default = true)]
53    pub include_margin: bool,
54    /// Custom inline styles
55    #[props(default)]
56    pub style: Option<String>,
57    /// Custom class name
58    #[props(default)]
59    pub class: Option<String>,
60    /// Title/alt text for accessibility
61    #[props(default)]
62    pub title: Option<String>,
63}
64
65/// QR Code display component
66///
67/// Generates a QR code using an external API (for simplicity).
68/// In production, you might want to use a Rust QR library.
69///
70/// # Example
71/// ```rust,ignore
72/// use dioxus::prelude::*;
73/// use dioxus_ui_system::molecules::{QrCode, QrCodeLevel};
74///
75/// rsx! {
76///     QrCode {
77///         value: "https://example.com",
78///         size: 256,
79///         level: QrCodeLevel::High,
80///     }
81/// }
82/// ```
83#[component]
84pub fn QrCode(props: QrCodeProps) -> Element {
85    let _theme = use_theme();
86
87    let container_style = use_style(|_t| Style::new().inline_block().build());
88
89    let fg = props
90        .fg_color
91        .clone()
92        .unwrap_or_else(|| "000000".to_string());
93    let bg = props
94        .bg_color
95        .clone()
96        .unwrap_or_else(|| "FFFFFF".to_string());
97
98    // Generate QR code using Google Chart API
99    // For a pure Rust solution, you'd use the `qrcode` crate
100    let api_url = format!(
101        "https://api.qrserver.com/v1/create-qr-code/?size={}x{}&data={}&ecc={}&color={}&bgcolor={}&margin={}",
102        props.size,
103        props.size,
104        urlencode(&props.value),
105        props.level.as_str(),
106        fg,
107        bg,
108        if props.include_margin { 4 } else { 0 }
109    );
110
111    let alt_text = props
112        .title
113        .clone()
114        .unwrap_or_else(|| format!("QR Code: {}", props.value));
115
116    rsx! {
117        div {
118            style: "{container_style} {props.style.clone().unwrap_or_default()}",
119            class: "{props.class.clone().unwrap_or_default()}",
120
121            img {
122                src: "{api_url}",
123                width: "{props.size}",
124                height: "{props.size}",
125                alt: "{alt_text}",
126                style: "display: block; max-width: 100%; height: auto;",
127            }
128        }
129    }
130}
131
132/// SVG-based QR Code component (no external API)
133/// This is a simplified placeholder that renders a sample pattern
134#[derive(Props, Clone, PartialEq)]
135pub struct QrCodeSvgProps {
136    pub value: String,
137    #[props(default = 200)]
138    pub size: u32,
139    #[props(default)]
140    pub level: QrCodeLevel,
141    #[props(default)]
142    pub style: Option<String>,
143    #[props(default)]
144    pub class: Option<String>,
145}
146
147/// QR Code SVG component using inline SVG
148/// Note: This is a simplified placeholder. For real QR codes,
149/// use the `qrcode` crate to generate the actual matrix.
150#[component]
151pub fn QrCodeSvg(props: QrCodeSvgProps) -> Element {
152    let _theme = use_theme();
153    let module_count = 21; // Minimum QR version 1 size
154    let _module_size = props.size / module_count as u32;
155
156    // Generate a pseudo-random pattern based on the value hash
157    // In production, use the `qrcode` crate to generate the actual matrix
158    let pattern = generate_placeholder_pattern(&props.value, module_count);
159
160    let container_style = use_style(|_t| Style::new().inline_block().build());
161
162    rsx! {
163        div {
164            style: "{container_style} {props.style.clone().unwrap_or_default()}",
165            class: "{props.class.clone().unwrap_or_default()}",
166
167            svg {
168                width: "{props.size}",
169                height: "{props.size}",
170                view_box: "0 0 {module_count} {module_count}",
171                xmlns: "http://www.w3.org/2000/svg",
172
173                // Background
174                rect {
175                    x: "0",
176                    y: "0",
177                    width: "{module_count}",
178                    height: "{module_count}",
179                    fill: "white",
180                }
181
182                // QR pattern modules
183                for (row_idx, row) in pattern.iter().enumerate() {
184                    for (col_idx, &is_dark) in row.iter().enumerate() {
185                        if is_dark {
186                            rect {
187                                key: "{row_idx}-{col_idx}",
188                                x: "{col_idx}",
189                                y: "{row_idx}",
190                                width: "1",
191                                height: "1",
192                                fill: "black",
193                            }
194                        }
195                    }
196                }
197
198                // Position detection patterns (corners)
199                // Top-left
200                PositionPattern { x: 0, y: 0 }
201                // Top-right
202                PositionPattern { x: module_count - 7, y: 0 }
203                // Bottom-left
204                PositionPattern { x: 0, y: module_count - 7 }
205            }
206        }
207    }
208}
209
210#[derive(Props, Clone, PartialEq)]
211struct PositionPatternProps {
212    x: usize,
213    y: usize,
214}
215
216#[component]
217fn PositionPattern(props: PositionPatternProps) -> Element {
218    let x = props.x;
219    let y = props.y;
220
221    rsx! {
222        // Outer square
223        rect { x: "{x}", y: "{y}", width: "7", height: "7", fill: "black" }
224        // White border
225        rect { x: "{x + 1}", y: "{y + 1}", width: "5", height: "5", fill: "white" }
226        // Inner square
227        rect { x: "{x + 2}", y: "{y + 2}", width: "3", height: "3", fill: "black" }
228    }
229}
230
231/// Simple placeholder pattern generator
232/// In production, use the `qrcode` crate
233fn generate_placeholder_pattern(value: &str, size: usize) -> Vec<Vec<bool>> {
234    use std::collections::hash_map::DefaultHasher;
235    use std::hash::{Hash, Hasher};
236
237    let mut hasher = DefaultHasher::new();
238    value.hash(&mut hasher);
239    let hash = hasher.finish();
240
241    let mut pattern = vec![vec![false; size]; size];
242
243    // Generate pattern based on hash
244    for i in 0..size {
245        for j in 0..size {
246            // Skip position detection patterns
247            if (i < 7 && j < 7) || (i < 7 && j >= size - 7) || (i >= size - 7 && j < 7) {
248                continue;
249            }
250            // Skip timing patterns
251            if i == 6 || j == 6 {
252                pattern[i][j] = (i + j) % 2 == 0;
253                continue;
254            }
255            // Fill rest with pseudo-random data
256            let idx = i * size + j;
257            pattern[i][j] = ((hash >> (idx % 64)) & 1) == 1;
258        }
259    }
260
261    pattern
262}
263
264fn urlencode(s: &str) -> String {
265    s.chars()
266        .map(|c| match c {
267            'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(),
268            c => format!("%{:02X}", c as u8),
269        })
270        .collect()
271}