cleo_design_tokens 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cleo_design_tokens/bucket.rb +2 -1
- data/lib/cleo_design_tokens/configuration.rb +57 -0
- data/lib/cleo_design_tokens/errors.rb +1 -0
- data/lib/cleo_design_tokens/lookup_builder.rb +43 -8
- data/lib/cleo_design_tokens/semantic_bucket.rb +2 -1
- data/lib/cleo_design_tokens.rb +16 -3
- data/package.json +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b26b7b458c43724454d2827bf71e276857f7948f46dfade6a22c0a78ed068f3
|
|
4
|
+
data.tar.gz: 13ff4a6a39778fe89c931a6cd1f01931e3d08b901262aa582bde3b054b366889
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa1dc9fec6338e93595ef7b0252bd96a7a427b810ef5a262669e89874ded5882377a2a2fa6318e13d335b721af4e45f1e3e959fb72355d9165101307998a96ad
|
|
7
|
+
data.tar.gz: ff7a54aa890e5ebf7a3dce4d1364db1ce7e9ac6312f04fa7ac949d4f49582fce02218ddbebef0a1e0e77c093f88c82a235dc93f1a80480589a4f307681ee067b
|
|
@@ -10,7 +10,8 @@ module CleoDesignTokens
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def fetch(key)
|
|
13
|
-
|
|
13
|
+
lookup = @lookup.respond_to?(:call) ? @lookup.call : @lookup
|
|
14
|
+
value = lookup.fetch(key) { raise UnknownTokenError, "unknown design token: #{key.inspect}" }
|
|
14
15
|
# `Hash#fetch` only guards a missing *key* — a key present with a nil
|
|
15
16
|
# value (malformed data: a primitive leaf with no `$value`) would
|
|
16
17
|
# otherwise return nil instead of raising.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require "thread"
|
|
2
|
+
require_relative "lookup_builder"
|
|
3
|
+
|
|
4
|
+
module CleoDesignTokens
|
|
5
|
+
class Configuration
|
|
6
|
+
Palette = Data.define(:primitives, :semantic)
|
|
7
|
+
CONTEXT_KEY = :cleo_design_tokens_palette
|
|
8
|
+
private_constant :Palette, :CONTEXT_KEY
|
|
9
|
+
|
|
10
|
+
attr_reader :default_palette
|
|
11
|
+
|
|
12
|
+
def initialize(primitives:, semantic:)
|
|
13
|
+
@primitives = primitives
|
|
14
|
+
@semantic = semantic
|
|
15
|
+
@default_palette = build_palette(:current)
|
|
16
|
+
@refresh_palette = nil
|
|
17
|
+
@mutex = Mutex.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def palette
|
|
21
|
+
# Thread#[] is fiber-local, unlike Thread#thread_variable_get.
|
|
22
|
+
Thread.current[CONTEXT_KEY] || default_palette
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def configure(experience:)
|
|
26
|
+
selected = case experience
|
|
27
|
+
when :current, "current" then nil
|
|
28
|
+
when :refresh, "refresh"
|
|
29
|
+
@mutex.synchronize { @refresh_palette ||= build_palette(:refresh) }
|
|
30
|
+
else
|
|
31
|
+
raise UnknownExperienceError, "unknown experience: #{experience.inspect}; expected :current or :refresh"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless block_given?
|
|
35
|
+
Thread.current[CONTEXT_KEY] = selected
|
|
36
|
+
return nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
previous = Thread.current[CONTEXT_KEY]
|
|
40
|
+
begin
|
|
41
|
+
Thread.current[CONTEXT_KEY] = selected
|
|
42
|
+
yield
|
|
43
|
+
ensure
|
|
44
|
+
Thread.current[CONTEXT_KEY] = previous
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def build_palette(experience)
|
|
51
|
+
Palette.new(
|
|
52
|
+
primitives: LookupBuilder.build_lookup(@primitives, experience: experience).freeze,
|
|
53
|
+
semantic: LookupBuilder.build_semantic_lookup(@semantic, experience: experience).freeze,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -9,14 +9,20 @@ module CleoDesignTokens
|
|
|
9
9
|
module LookupBuilder
|
|
10
10
|
# Flattens one tree of plain (theme-free) leaves — e.g. the parsed
|
|
11
11
|
# `primitives.json` — into a key -> hex lookup.
|
|
12
|
-
def self.build_lookup(tree)
|
|
13
|
-
flatten_tree(tree)
|
|
12
|
+
def self.build_lookup(tree, experience: :current)
|
|
13
|
+
flatten_tree(tree) do |leaf, key|
|
|
14
|
+
value = experience_leaf(leaf, experience, key)["$value"]
|
|
15
|
+
unless value.is_a?(String)
|
|
16
|
+
raise UnknownExperienceError, "design token #{key.inspect} has no primitive value for experience #{experience.inspect}"
|
|
17
|
+
end
|
|
18
|
+
value.freeze
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
# Flattens `semantic.json` into a key -> SemanticEntry lookup, one entry
|
|
17
23
|
# per role — the theme axis lives inside the entry, not in the key.
|
|
18
|
-
def self.build_semantic_lookup(tree)
|
|
19
|
-
flatten_tree(tree) { |leaf| semantic_entry(leaf) }
|
|
24
|
+
def self.build_semantic_lookup(tree, experience: :current)
|
|
25
|
+
flatten_tree(tree) { |leaf, key| semantic_entry(experience_leaf(leaf, experience, key), experience, key) }
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
# Walks `tree`, turning each leaf into a lookup entry via the block —
|
|
@@ -43,19 +49,48 @@ module CleoDesignTokens
|
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
origins[key] = path
|
|
46
|
-
lookup[key] = yield(leaf)
|
|
52
|
+
lookup[key] = yield(leaf, key)
|
|
47
53
|
end
|
|
48
54
|
lookup
|
|
49
55
|
end
|
|
50
56
|
private_class_method :flatten_tree
|
|
51
57
|
|
|
52
|
-
def self.
|
|
58
|
+
def self.experience_leaf(leaf, experience, key)
|
|
59
|
+
return leaf if experience.to_s == "current"
|
|
60
|
+
unless experience.to_s == "refresh"
|
|
61
|
+
raise UnknownExperienceError, "unknown experience: #{experience.inspect}; expected :current or :refresh"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
snapshots = leaf["$experiences"]
|
|
65
|
+
snapshot = snapshots["cleoRefresh"] if snapshots.is_a?(Hash)
|
|
66
|
+
unless snapshot.is_a?(Hash)
|
|
67
|
+
raise UnknownExperienceError, "experience :refresh is unavailable for design token #{key.inspect}; sync the Refresh colour data first"
|
|
68
|
+
end
|
|
69
|
+
snapshot
|
|
70
|
+
end
|
|
71
|
+
private_class_method :experience_leaf
|
|
72
|
+
|
|
73
|
+
def self.semantic_entry(leaf, experience, key)
|
|
53
74
|
themes = {}
|
|
54
|
-
|
|
75
|
+
raw_themes = leaf["$themes"] || {}
|
|
76
|
+
unless raw_themes.is_a?(Hash)
|
|
77
|
+
raise UnknownExperienceError, "design token #{key.inspect} has invalid themes for experience #{experience.inspect}"
|
|
78
|
+
end
|
|
79
|
+
raw_themes.each do |theme, override|
|
|
80
|
+
value = override.is_a?(Hash) ? override["$value"] : nil
|
|
81
|
+
unless value.is_a?(String)
|
|
82
|
+
raise UnknownExperienceError, "design token #{key.inspect} has no value for theme #{theme.inspect} in experience #{experience.inspect}"
|
|
83
|
+
end
|
|
84
|
+
themes[theme] = value.freeze
|
|
85
|
+
end
|
|
86
|
+
value = leaf["$value"]
|
|
87
|
+
if (leaf.key?("$value") && !value.is_a?(String)) || (value.nil? && themes.empty?)
|
|
88
|
+
raise UnknownExperienceError, "design token #{key.inspect} has no semantic values for experience #{experience.inspect}"
|
|
89
|
+
end
|
|
55
90
|
|
|
56
91
|
# `Data#new` freezes the instance itself; the Hash inside it still
|
|
57
92
|
# needs its own `.freeze` — freezing is shallow.
|
|
58
|
-
SemanticEntry.new(
|
|
93
|
+
SemanticEntry.new(value&.freeze, themes.freeze)
|
|
59
94
|
end
|
|
60
95
|
private_class_method :semantic_entry
|
|
61
96
|
end
|
|
@@ -16,7 +16,8 @@ module CleoDesignTokens
|
|
|
16
16
|
theme = theme.to_s
|
|
17
17
|
raise UnknownThemeError, "unknown theme: #{theme.inspect}" unless THEMES.include?(theme)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
lookup = @lookup.respond_to?(:call) ? @lookup.call : @lookup
|
|
20
|
+
entry = lookup.fetch(key) { raise UnknownTokenError, "unknown design token: #{key.inspect}" }
|
|
20
21
|
value = resolve(entry, theme)
|
|
21
22
|
if value.nil?
|
|
22
23
|
raise UnknownTokenError,
|
data/lib/cleo_design_tokens.rb
CHANGED
|
@@ -8,6 +8,7 @@ require_relative "cleo_design_tokens/version"
|
|
|
8
8
|
require_relative "cleo_design_tokens/lookup_builder"
|
|
9
9
|
require_relative "cleo_design_tokens/bucket"
|
|
10
10
|
require_relative "cleo_design_tokens/semantic_bucket"
|
|
11
|
+
require_relative "cleo_design_tokens/configuration"
|
|
11
12
|
|
|
12
13
|
# Reads Cleo's canonical colour design tokens (packages/tokens/tokens/color)
|
|
13
14
|
# into two frozen lookups, namespaced by token type then layer, with the
|
|
@@ -27,8 +28,13 @@ module CleoDesignTokens
|
|
|
27
28
|
# Built at load, into constants — not `@lookup ||=`, which would race under
|
|
28
29
|
# concurrent access. Frozen from the moment the gem loads, so every reader
|
|
29
30
|
# in every thread sees the same immutable lookup.
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
CONFIGURATION = Configuration.new(
|
|
32
|
+
primitives: JSON.parse(File.read(PRIMITIVES_PATH)),
|
|
33
|
+
semantic: JSON.parse(File.read(SEMANTIC_PATH)),
|
|
34
|
+
)
|
|
35
|
+
private_constant :CONFIGURATION
|
|
36
|
+
PRIMITIVES_LOOKUP = CONFIGURATION.default_palette.primitives
|
|
37
|
+
SEMANTIC_LOOKUP = CONFIGURATION.default_palette.semantic
|
|
32
38
|
|
|
33
39
|
# Namespaced by token type (`colors`), then by layer (`primitives`,
|
|
34
40
|
# `semantic`) — lowercase accessors, not `CleoDesignTokens::Colors::
|
|
@@ -40,12 +46,19 @@ module CleoDesignTokens
|
|
|
40
46
|
# no `.primitives` method. Plural `Colors`, not `Color` — an instance
|
|
41
47
|
# holds both buckets for the `colors` token type, not a single colour.
|
|
42
48
|
Colors = Data.define(:primitives, :semantic)
|
|
43
|
-
COLORS = Colors.new(
|
|
49
|
+
COLORS = Colors.new(
|
|
50
|
+
primitives: Bucket.new(-> { CONFIGURATION.palette.primitives }),
|
|
51
|
+
semantic: SemanticBucket.new(-> { CONFIGURATION.palette.semantic }),
|
|
52
|
+
)
|
|
44
53
|
|
|
45
54
|
def self.colors
|
|
46
55
|
COLORS
|
|
47
56
|
end
|
|
48
57
|
|
|
58
|
+
def self.configure(experience:, &block)
|
|
59
|
+
CONFIGURATION.configure(experience: experience, &block)
|
|
60
|
+
end
|
|
61
|
+
|
|
49
62
|
# Spacing is code-owned, not generated from Figma. Without a unit it is a
|
|
50
63
|
# numeric React Native-ready pixel value; CSS callers opt into a unit.
|
|
51
64
|
def self.spacing(multiplier, unit: nil)
|
data/package.json
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cleo_design_tokens
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cleo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Flattens Cleo's colour design tokens (packages/tokens/tokens/color) into
|
|
14
14
|
frozen colors.primitives / colors.semantic lookups, with the theme passed alongside
|
|
@@ -20,6 +20,7 @@ extra_rdoc_files: []
|
|
|
20
20
|
files:
|
|
21
21
|
- lib/cleo_design_tokens.rb
|
|
22
22
|
- lib/cleo_design_tokens/bucket.rb
|
|
23
|
+
- lib/cleo_design_tokens/configuration.rb
|
|
23
24
|
- lib/cleo_design_tokens/errors.rb
|
|
24
25
|
- lib/cleo_design_tokens/lookup_builder.rb
|
|
25
26
|
- lib/cleo_design_tokens/semantic_bucket.rb
|