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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7bf05dee064782565d442d3da54c6203328456debe4b7d183ec0e24d582b4a55
4
- data.tar.gz: 58af1f5a62cb959dcbadafb2ebece37fa53558b5cd9d6cba683d5a40efa51b16
3
+ metadata.gz: 4b26b7b458c43724454d2827bf71e276857f7948f46dfade6a22c0a78ed068f3
4
+ data.tar.gz: 13ff4a6a39778fe89c931a6cd1f01931e3d08b901262aa582bde3b054b366889
5
5
  SHA512:
6
- metadata.gz: 6a72721e29aad60328fc4b8a61a90ed9107355f2a55d34ffa6adf27e58c6c89449b67ad50f626c0ab56f5ac1290a9bcfa9178643350d6533f7cad3000c4ab179
7
- data.tar.gz: 15248d41bac918b3b03e4724182903de1d2d3f2ef4af133412335b563d6477d1ef6b6cd65eb84549a3ec1131d4ca2d407c886693dcc6a50314e2dba5777bcff6
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
- value = @lookup.fetch(key) { raise UnknownTokenError, "unknown design token: #{key.inspect}" }
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
@@ -1,5 +1,6 @@
1
1
  module CleoDesignTokens
2
2
  class UnknownTokenError < KeyError; end
3
3
  class UnknownThemeError < ArgumentError; end
4
+ class UnknownExperienceError < ArgumentError; end
4
5
  class DuplicateTokenError < KeyError; end
5
6
  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) { |leaf| leaf["$value"].freeze }
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.semantic_entry(leaf)
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
- (leaf["$themes"] || {}).each { |theme, override| themes[theme] = override["$value"].freeze }
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(leaf["$value"]&.freeze, themes.freeze)
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
- entry = @lookup.fetch(key) { raise UnknownTokenError, "unknown design token: #{key.inspect}" }
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,
@@ -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
- PRIMITIVES_LOOKUP = LookupBuilder.build_lookup(JSON.parse(File.read(PRIMITIVES_PATH))).freeze
31
- SEMANTIC_LOOKUP = LookupBuilder.build_semantic_lookup(JSON.parse(File.read(SEMANTIC_PATH))).freeze
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(primitives: Bucket.new(PRIMITIVES_LOOKUP), semantic: SemanticBucket.new(SEMANTIC_LOOKUP))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetcleo/design-tokens",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Canonical source of truth for Cleo's design tokens (DTCG JSON), plus a TypeScript reader.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
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.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-09-22 00:00:00.000000000 Z
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